├── .eslintrc.json ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── .yarnrc ├── CHANGELOG.md ├── README.md ├── assets ├── add-new-account.gif ├── icon.png ├── open-d1-ext.gif ├── run-query-d1.gif └── select-d1-database.gif ├── package.json ├── src ├── client.ts ├── connection-manager.ts ├── constants.ts ├── explorer-service.ts ├── explorer.ts ├── extension.ts ├── models │ ├── account.ts │ ├── cf │ │ ├── columnItem.ts │ │ ├── databaseItem.ts │ │ ├── index.ts │ │ ├── listResponse.ts │ │ ├── queryInfo.ts │ │ ├── response.ts │ │ ├── resultInfo.ts │ │ └── tableItem.ts │ └── node.ts ├── script-formatter.ts ├── sql-content.ejs ├── status-bar.ts ├── test │ ├── runTest.ts │ └── suite │ │ ├── extension.test.ts │ │ └── index.ts ├── views │ └── html │ │ └── css │ │ ├── pico.css │ │ └── styles.css └── webview-controller.ts ├── tsconfig.json ├── webpack.config.js └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint/recommended" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/naming-convention": "warn", 13 | "@typescript-eslint/semi": "warn", 14 | "curly": "warn", 15 | "eqeqeq": "warn", 16 | "no-throw-literal": "warn", 17 | "semi": "off" 18 | }, 19 | "ignorePatterns": [ 20 | "out", 21 | "dist", 22 | "**/*.d.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .env 4 | *.vsix 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": ["dbaeumer.vscode-eslint", "amodio.tsl-problem-matcher"] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/dist/**/*.js" 17 | ], 18 | "preLaunchTask": "${defaultBuildTask}" 19 | }, 20 | { 21 | "name": "Extension Tests", 22 | "type": "extensionHost", 23 | "request": "launch", 24 | "args": [ 25 | "--extensionDevelopmentPath=${workspaceFolder}", 26 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 27 | ], 28 | "outFiles": [ 29 | "${workspaceFolder}/out/**/*.js", 30 | "${workspaceFolder}/dist/**/*.js" 31 | ], 32 | "preLaunchTask": "tasks: watch-tests" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false, // set this to true to hide the "out" folder with the compiled JS files 5 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files 6 | }, 7 | "search.exclude": { 8 | "out": true, // set this to false to include "out" folder in search results 9 | "dist": true // set this to false to include "dist" folder in search results 10 | }, 11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 12 | "typescript.tsc.autoDetect": "off" 13 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$ts-webpack-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never", 13 | "group": "watchers" 14 | }, 15 | "group": { 16 | "kind": "build", 17 | "isDefault": true 18 | } 19 | }, 20 | { 21 | "type": "npm", 22 | "script": "watch-tests", 23 | "problemMatcher": "$tsc-watch", 24 | "isBackground": true, 25 | "presentation": { 26 | "reveal": "never", 27 | "group": "watchers" 28 | }, 29 | "group": "build" 30 | }, 31 | { 32 | "label": "tasks: watch-tests", 33 | "dependsOn": [ 34 | "npm: watch", 35 | "npm: watch-tests" 36 | ], 37 | "problemMatcher": [] 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/** 4 | node_modules/** 5 | src/** 6 | .gitignore 7 | .yarnrc 8 | webpack.config.js 9 | vsc-extension-quickstart.md 10 | **/tsconfig.json 11 | **/.eslintrc.json 12 | **/*.map 13 | **/*.ts 14 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | --ignore-engines true -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [1.0.3] 4 | 5 | ### Fixed 6 | - Fix display with proper styling for query results by copying assets to dist 7 | 8 | ## [1.0.2] 9 | 10 | ### Fixed 11 | - Fix display of query results when rendered 12 | 13 | ## [1.0.1] 14 | 15 | ### Docs 16 | 17 | - Updated readme 18 | 19 | ## [1.0.0] 20 | 21 | ### Added 22 | 23 | - Initial release -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cloudflare D1 Extension for Visual Studio Code 2 | 3 | The D1 Extension for Visual Studio Code provides a convenient interface for querying and viewing results from Cloudflare's D1 product. With this extension, you can easily run SQL queries against your D1 database across multiple accounts and see the results right in your editor. 4 | 5 | ## Getting Started 6 | 7 | 1. Install the extension from the Visual Studio Code Marketplace 8 | 2. Open a new or existing SQL file in Visual Studio Code 9 | 3. Open the D1 extension sidebar by clicking on the D1 logo in the left sidebar 10 | ![Open D1](assets/open-d1-ext.gif) 11 | 4. Add a new account 12 | ![Add Account](assets/add-new-account.gif) 13 | 5. Select the desired database 14 | ![Select Database](assets/select-d1-database.gif) 15 | 6. Run SQL queries by typing them into the query editor and clicking "Run Query" in the status panel below to see the results in the right pane 16 | ![Run Queries](assets/run-query-d1.gif) 17 | 18 | ## Features 19 | 20 | - Connect to your D1 database from within VS Code 21 | - Run SQL queries and see the results in a tabular format 22 | - Easily switch between multiple databases 23 | - Easily Explore table schemas 24 | - TBD features 25 | - Export query results to CSV or JSON 26 | - code completion integration 27 | - object explorer for views, indexes, etc 28 | 29 | ## Commands 30 | The extension provides several commands in the Command Palette (prefixed with "D1"): 31 | * **D1: Add Account**: Connect to a Cloudflare account. Your email and API key is required. 32 | * **D1: Remove Account**: Remove an account from the object explorer. 33 | * **D1: Use Database**: Switch to a database within an Cloudflare account, to use as the desired database to run queries against. 34 | * **D1: Execute Query**: Execute Query script, D1 statements in the editor. 35 | -------------------------------------------------------------------------------- /assets/add-new-account.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yj7o5/d1-vscode/269f31de3cda57df5324e991d1c4d728bed0b522/assets/add-new-account.gif -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yj7o5/d1-vscode/269f31de3cda57df5324e991d1c4d728bed0b522/assets/icon.png -------------------------------------------------------------------------------- /assets/open-d1-ext.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yj7o5/d1-vscode/269f31de3cda57df5324e991d1c4d728bed0b522/assets/open-d1-ext.gif -------------------------------------------------------------------------------- /assets/run-query-d1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yj7o5/d1-vscode/269f31de3cda57df5324e991d1c4d728bed0b522/assets/run-query-d1.gif -------------------------------------------------------------------------------- /assets/select-d1-database.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yj7o5/d1-vscode/269f31de3cda57df5324e991d1c4d728bed0b522/assets/select-d1-database.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cf-d1", 3 | "displayName": "Cloudflare D1", 4 | "description": "Explore and query Cloudflare D1 databases", 5 | "version": "1.0.3", 6 | "publisher": "yawarjamal", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/yj7o5/d1-vscode.git" 10 | }, 11 | "engines": { 12 | "vscode": "^1.69.0" 13 | }, 14 | "categories": [ 15 | "Other" 16 | ], 17 | "activationEvents": [], 18 | "main": "./dist/extension.js", 19 | "contributes": { 20 | "viewsContainers": { 21 | "activitybar": [ 22 | { 23 | "id": "d1-explorer", 24 | "title": "Cloudflare D1", 25 | "icon": "$(server)" 26 | } 27 | ] 28 | }, 29 | "views": { 30 | "d1-explorer": [ 31 | { 32 | "id": "objects", 33 | "name": "Accounts" 34 | }, 35 | { 36 | "id": "queries", 37 | "name": "Queries" 38 | } 39 | ] 40 | }, 41 | "menus": { 42 | "view/title": [ 43 | { 44 | "command": "d1.addProfile", 45 | "when": "view == objects", 46 | "group": "navigation" 47 | } 48 | ], 49 | "view/item/context": [ 50 | { 51 | "command": "d1.removeProfile", 52 | "when": "view == objects && viewItem == account", 53 | "group": "D1_SQL@1" 54 | }, 55 | { 56 | "command": "d1.createDatabase", 57 | "when": "view == objects && viewItem == account", 58 | "group": "D1_SQL@2" 59 | }, 60 | { 61 | "command": "d1.selectScript", 62 | "when": "view == objects && viewItem == table", 63 | "group": "D1_SQL@1" 64 | }, 65 | { 66 | "command": "d1.dropScript", 67 | "when": "view == objects && viewItem == table", 68 | "group": "D1_SQL@2" 69 | }, 70 | { 71 | "command": "d1.createScript", 72 | "when": "view == objects && viewItem == table", 73 | "group": "D1_SQL@3" 74 | } 75 | ] 76 | }, 77 | "commands": [ 78 | { 79 | "command": "d1.addProfile", 80 | "title": "Add account" 81 | }, 82 | { 83 | "command": "d1.removeProfile", 84 | "title": "Remove account" 85 | }, 86 | { 87 | "command": "d1.createDatabase", 88 | "title": "Create database" 89 | }, 90 | { 91 | "command": "d1.executeQuery", 92 | "title": "D1: Execute Query" 93 | }, 94 | { 95 | "command": "d1.expandObject", 96 | "title": "D1: Expand Object" 97 | }, 98 | { 99 | "command": "d1.addProfile", 100 | "title": "D1: Add Account", 101 | "icon": "$(add)" 102 | }, 103 | { 104 | "command": "d1.chooseDatabase", 105 | "title": "D1: Use Database", 106 | "icon": "$(selection)" 107 | }, 108 | { 109 | "command": "d1.selectScript", 110 | "title": "Select top 100 rows" 111 | }, 112 | { 113 | "command": "d1.dropScript", 114 | "title": "Script as drop" 115 | }, 116 | { 117 | "command": "d1.createScript", 118 | "title": "Script as create" 119 | } 120 | ] 121 | }, 122 | "scripts": { 123 | "vscode:prepublish": "yarn run package", 124 | "compile": "webpack", 125 | "watch": "webpack --watch", 126 | "package": "webpack --mode production --devtool hidden-source-map", 127 | "compile-tests": "tsc -p . --outDir out", 128 | "watch-tests": "tsc -p . -w --outDir out", 129 | "pretest": "yarn run compile-tests && yarn run compile && yarn run lint", 130 | "lint": "eslint src --ext ts", 131 | "test": "node ./out/test/runTest.js" 132 | }, 133 | "devDependencies": { 134 | "@types/ejs": "^3.1.1", 135 | "@types/glob": "^7.2.0", 136 | "@types/mocha": "^9.1.1", 137 | "@types/node": "16.x", 138 | "@types/vscode": "^1.69.0", 139 | "@typescript-eslint/eslint-plugin": "^5.59.2", 140 | "@typescript-eslint/parser": "^5.59.2", 141 | "@vscode/test-electron": "^2.1.5", 142 | "copy-webpack-plugin": "^11.0.0", 143 | "eslint": "^8.40.0", 144 | "glob": "^8.0.3", 145 | "mocha": "^10.0.0", 146 | "node-fetch": "^3.2.8", 147 | "ts-loader": "^9.3.1", 148 | "typescript": "^5.0.4", 149 | "webpack": "^5.73.0", 150 | "webpack-cli": "^4.10.0" 151 | }, 152 | "dependencies": { 153 | "dotenv": "^16.0.3", 154 | "ejs": "^3.1.8" 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/client.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DatabaseItem, 3 | ListResponse, 4 | QueryInfo, 5 | Response 6 | } from "./models/cf"; 7 | import fetch, { Headers } from "node-fetch"; 8 | 9 | const BASE_URL = `https://api.cloudflare.com/client/v4/accounts`; 10 | 11 | export class Client { 12 | static executeQuery(acctId: string, apiKey: string, email: string, dbId: string, sql: string): Promise>> { 13 | return Client.post>>(acctId, apiKey, email, `/d1/database/${dbId}/query`, { sql: sql }); 14 | } 15 | 16 | static async listDatabases(acctId: string, apiKey: string, email: string, page: number, size: number): Promise { 17 | const response = await Client.get>(acctId, apiKey, email, "/d1/database", { page: page, per_page: size }); 18 | 19 | return response.result; 20 | } 21 | 22 | static async createDatabase(acctId: string, apiKey: string, email: string, database: string): Promise { 23 | const response = await Client.post>(acctId, apiKey, email, "/d1/database", { name: database }); 24 | 25 | return [response.result]; 26 | } 27 | 28 | private static get(acctId: string, apiKey: string, email: string, path: string, query: {[key:string]: any}): Promise { 29 | path = `/${acctId}/${path}`; 30 | 31 | const keys = Object.keys(query); 32 | const queryParams = keys.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(query[key])}`).join("&"); 33 | const url = `${BASE_URL}${path}?${queryParams}`; 34 | 35 | return fetch(url, { 36 | method: "GET", 37 | headers: [ 38 | ["content-type", "application/json"], 39 | ["x-auth-email", email], 40 | ["x-auth-key", apiKey], 41 | ] 42 | }).then(response => response.json()) as Promise; 43 | } 44 | 45 | private static post(acctId: string, apiKey: string, email: string, path: string, body: any): Promise { 46 | path = `/${acctId}/${path}`; 47 | 48 | const headers = new Headers(); 49 | 50 | headers.append("content-type", "application/json"); 51 | headers.append("x-auth-email", email); 52 | headers.append("x-auth-key", apiKey); 53 | 54 | return fetch(BASE_URL + path, { 55 | method: "POST", 56 | headers: [ 57 | ["content-type", "application/json"], 58 | ["x-auth-email", email], 59 | ["x-auth-key", apiKey], 60 | ], 61 | body: JSON.stringify(body) 62 | }).then(response => response.json()) as Promise; 63 | } 64 | } -------------------------------------------------------------------------------- /src/connection-manager.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import * as constants from "./constants"; 3 | import { Client } from "./client"; 4 | import { DatabaseItem } from "./models/cf"; 5 | import { TreeNode } from "./models/node"; 6 | 7 | export interface IConnectionProfile { 8 | name: string; 9 | accountId: string; 10 | apiKey: string; 11 | email: string; 12 | } 13 | 14 | type ActiveProfile = { 15 | connection: IConnectionProfile, 16 | database: DatabaseItem 17 | }; 18 | 19 | export class ConnectionManager { 20 | private _activeProfile: ActiveProfile|undefined; 21 | 22 | constructor(private _context: vscode.ExtensionContext) {} 23 | 24 | store(conn: IConnectionProfile) { 25 | const connections = this.loadAll(); 26 | connections.push(conn); 27 | 28 | this._context.globalState.update(constants.connectionProfiles, connections); 29 | } 30 | 31 | async remove(idx: number) { 32 | let conns = this._context.globalState.get(constants.connectionProfiles); 33 | if (conns && idx < conns.length) { 34 | conns.splice(idx, 1); 35 | await this._context.globalState.update(constants.connectionProfiles, conns); 36 | } 37 | } 38 | 39 | loadAll(): IConnectionProfile[] { 40 | let conns = this._context.globalState.get(constants.connectionProfiles); 41 | if (conns === undefined) { 42 | return []; 43 | } 44 | 45 | return conns; 46 | } 47 | 48 | async addProfile(): Promise { 49 | const apiKey = await vscode.window.showInputBox({ 50 | prompt: "Provide a Cloudflare API key (which has access to D1)", 51 | value: process.env.CF_API_KEY, 52 | password: true, 53 | ignoreFocusOut: true, 54 | }); 55 | 56 | const accountId = await vscode.window.showInputBox({ 57 | prompt: "Provide the account ID", 58 | value: process.env.CF_ACCOUNT_ID, 59 | password: true, 60 | ignoreFocusOut: true 61 | }); 62 | 63 | const email = await vscode.window.showInputBox({ 64 | prompt: "Provide your account email", 65 | value: process.env.CF_EMAIL, 66 | ignoreFocusOut: true 67 | }); 68 | 69 | const name = await vscode.window.showInputBox({ 70 | prompt: "Provide a friendly name", 71 | placeHolder: "[optional] Enter a display name for this account profile", 72 | value: "My D1 Databases", 73 | ignoreFocusOut: true 74 | }); 75 | 76 | //TODO: check we can at-least make the connection with the given input fields 77 | const conn: IConnectionProfile = { 78 | name: name!, 79 | accountId: accountId!, 80 | apiKey: apiKey!, 81 | email: email! 82 | }; 83 | 84 | this.store(conn); 85 | 86 | return conn; 87 | } 88 | 89 | public async createDatabase(node: TreeNode): Promise { 90 | const conn = node.data(); 91 | const { accountId, apiKey, email } = conn; 92 | const databaseName = await vscode.window.showInputBox({ 93 | prompt: "Provide the new database name", 94 | // TODO: place some validation 95 | // validateInput: function(value: string) {} 96 | }); 97 | 98 | await Client.createDatabase(accountId, apiKey, email, databaseName!); 99 | } 100 | 101 | public async chooseDatabase(): Promise { 102 | const profiles = this.loadAll(); 103 | const options = profiles.map(x => x.name); 104 | 105 | let selectedProfile: IConnectionProfile|undefined = profiles[0]; 106 | if (profiles.length > 1) { 107 | const selectedOption = await vscode.window.showQuickPick(options, { 108 | canPickMany: false, 109 | placeHolder: "Select an account: " 110 | }); 111 | 112 | selectedProfile = profiles.find(x => x.name === selectedOption); 113 | if (!selectedProfile) { 114 | return false; 115 | } 116 | } 117 | 118 | const databases = await Client.listDatabases(selectedProfile.accountId, selectedProfile.apiKey, selectedProfile.email, 1, 10); 119 | const databasesOptions = databases.map(x => x.name); 120 | 121 | const selectedDatabaseOption = await vscode.window.showQuickPick(databasesOptions, { 122 | canPickMany: false, 123 | placeHolder: `Choose a database from the list below(${selectedProfile.name} - Account): ` 124 | }); 125 | 126 | const selectedDatabase = databases.find(x => x.name === selectedDatabaseOption); 127 | if (!selectedDatabase) { 128 | return false; 129 | } 130 | 131 | this._activeProfile = { 132 | connection: selectedProfile, 133 | database: selectedDatabase 134 | }; 135 | 136 | return true; 137 | } 138 | 139 | public get activeProfile(): ActiveProfile|undefined { 140 | return this._activeProfile; 141 | } 142 | } -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const cmdAddProfile = "d1.addProfile"; 2 | export const cmdRemoveProfile = "d1.removeProfile"; 3 | export const cmdSelectScript = "d1.selectScript"; 4 | export const cmdExecuteQuery = "d1.executeQuery"; 5 | export const cmdCreateScript = "d1.createScript"; 6 | export const cmdChooseDatabase = "d1.chooseDatabase"; 7 | export const cmdCreateDatabase = "d1.createDatabase"; 8 | export const cmdInternalVscodeFormatDocument = "vscode.executeFormatDocumentProvider"; 9 | 10 | export const connectionProfiles = "d1.connectionProfiles"; -------------------------------------------------------------------------------- /src/explorer-service.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { Client } from "./client"; 3 | import { TreeNode } from "./models/node"; 4 | import { DatabaseItem } from "./models/cf"; 5 | import { TableItem } from "./models/cf/tableItem"; 6 | import { ColumnItem } from "./models/cf/columnItem"; 7 | import { ConnectionManager, IConnectionProfile as IConnectionProfile } from "./connection-manager"; 8 | 9 | type OnNodeChange = (_: TreeNode) => void; 10 | 11 | const COLLAPSED_STATE = vscode.TreeItemCollapsibleState.Collapsed; 12 | 13 | export class ExplorerService { 14 | public rootNode: TreeNode | undefined; 15 | public currentNode: TreeNode[] | undefined; 16 | 17 | private _children: Map; 18 | private _connManager: ConnectionManager; 19 | 20 | private _onChange: OnNodeChange; 21 | 22 | constructor(onChange: OnNodeChange, connManager: ConnectionManager) { 23 | this.currentNode = undefined; 24 | this._children = new Map(); 25 | this._connManager = connManager; 26 | this._onChange = onChange; 27 | } 28 | 29 | public async getChildren(node: TreeNode | undefined): Promise { 30 | if (node) { 31 | this.currentNode = [node]; 32 | const cachedNode = this._children.get(node); 33 | if (cachedNode) { 34 | return cachedNode; 35 | } else { 36 | await this.expandNode(node); 37 | return this._children.get(node)!; 38 | } 39 | } else { 40 | if (!this.currentNode) { 41 | const conns = this._connManager.loadAll(); 42 | let currConn: IConnectionProfile[]; 43 | if (conns !== undefined && conns.length > 0) { 44 | currConn = conns; 45 | } else { 46 | currConn = [await this._connManager.addProfile()]; 47 | } 48 | 49 | this.currentNode = currConn.map((x, idx) => 50 | new TreeNode(idx, "account", x.name, "account", x, COLLAPSED_STATE)); 51 | } 52 | 53 | return this.currentNode; 54 | } 55 | } 56 | 57 | public async addNode(): Promise { 58 | await this._connManager.addProfile(); 59 | 60 | const nodes = this._connManager.loadAll(); 61 | 62 | this.currentNode = nodes.map((x, idx) => 63 | new TreeNode(idx, "account", x.name, "account", x, COLLAPSED_STATE) 64 | ); 65 | } 66 | 67 | public async removeNode(node?: TreeNode): Promise { 68 | if (!node) { 69 | console.warn("no node found to remove"); 70 | } 71 | this._connManager.remove(node!.position); 72 | this._children.delete(node!); 73 | const idx = this.currentNode?.findIndex(x => x.label === node?.title); 74 | if (idx !== undefined && idx > -1) { 75 | this.currentNode?.splice(idx, 1); 76 | } 77 | } 78 | 79 | private async expandNode(node: TreeNode): Promise { 80 | switch (node.type) { 81 | case "account": 82 | await this.expandProfile(node); 83 | break; 84 | case "database": 85 | await this.expandDatabase(node); 86 | break; 87 | case "table": 88 | await this.expandTable(node); 89 | break; 90 | default: 91 | return; 92 | } 93 | 94 | this._onChange(node); 95 | } 96 | 97 | private async expandProfile(node: TreeNode): Promise { 98 | const profile = node.data(); 99 | const databases = await Client.listDatabases(profile.accountId, profile.apiKey, profile.email, 1, 10); 100 | const children = databases.map((db, idx) => new TreeNode(idx, "database", db.name, "[contextValue]", db, vscode.TreeItemCollapsibleState.Collapsed, node)); 101 | 102 | this._children.set(node, children); 103 | } 104 | 105 | private async expandDatabase(node: TreeNode): Promise { 106 | const db = node.data(); 107 | const sql = `select name from sqlite_schema where type='table' and name not like 'sqlite_%'`; 108 | 109 | const conn = node.root()!; 110 | const response = await Client.executeQuery(conn.accountId, conn.apiKey, conn.email, db.uuid, sql); 111 | 112 | const tables = response.result.flatMap(x => x.results); 113 | const children = tables.map((table, idx) => new TreeNode(idx, "table", table.name, "table", table, vscode.TreeItemCollapsibleState.Collapsed, node)); 114 | 115 | this._children.set(node, children); 116 | } 117 | 118 | private async expandTable(node: TreeNode): Promise { 119 | const table = node.data(); 120 | const sql = `select * from pragma_table_info('${table.name}')`; 121 | 122 | const dbId = node.root("database")!.uuid; 123 | const conn = node.root()!; 124 | const response = await Client.executeQuery(conn.accountId, conn.apiKey, conn.email, dbId, sql); 125 | 126 | const columns = response.result.flatMap(x => x.results); 127 | const children = columns.map((column, idx) => new TreeNode(idx, "column", this.columnLabel(column), "column", column, vscode.TreeItemCollapsibleState.None, node)); 128 | 129 | this._children.set(node, children); 130 | } 131 | 132 | private columnLabel(item: ColumnItem): string { 133 | const type = item.type.toLowerCase(); 134 | const nullText = item.notnull === 0 ? "null" : ""; 135 | 136 | let display = item.name; 137 | const props = [type]; 138 | 139 | if (nullText !== "") { 140 | props.push(nullText); 141 | } 142 | if (item.pk === 1) { 143 | props.push("pk"); 144 | } 145 | 146 | display = `${display} (${props.join(", ")})`; 147 | 148 | return display; 149 | } 150 | } -------------------------------------------------------------------------------- /src/explorer.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { ExplorerService } from "./explorer-service"; 3 | import { TreeNode } from "./models/node"; 4 | 5 | export class ExplorerProvider implements vscode.TreeDataProvider { 6 | private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); 7 | readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; 8 | 9 | private _explorerService: ExplorerService; 10 | 11 | constructor(explorerService: ExplorerService) { 12 | this._explorerService = explorerService; 13 | } 14 | 15 | refresh(node?: TreeNode): void { 16 | this._onDidChangeTreeData.fire(node); 17 | } 18 | 19 | getTreeItem(node: TreeNode): TreeNode { 20 | return node; 21 | } 22 | 23 | async getChildren(node?: TreeNode): Promise { 24 | const children = await this._explorerService.getChildren(node); 25 | if (children) { 26 | const items = children.map(child => this.getTreeItem(child)); 27 | return items; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { ExplorerProvider } from './explorer'; 3 | import { ExplorerService } from './explorer-service'; 4 | import { TreeNode } from './models/node'; 5 | import { ScriptFormatter } from './script-formatter'; 6 | import { WebviewController } from './webview-controller'; 7 | import { config as configureDotEnv } from "dotenv"; 8 | 9 | import { 10 | cmdAddProfile, 11 | cmdSelectScript, 12 | cmdExecuteQuery, 13 | cmdCreateScript, 14 | cmdRemoveProfile, 15 | cmdCreateDatabase, 16 | cmdChooseDatabase 17 | // cmdInternalVscodeFormatDocument 18 | } from './constants'; 19 | import { Client } from './client'; 20 | import { DatabaseItem } from './models/cf'; 21 | import { ConnectionManager, IConnectionProfile } from './connection-manager'; 22 | import StatusBar from './status-bar'; 23 | 24 | configureDotEnv({ path: ".env" }); 25 | 26 | class EntryController implements vscode.Disposable { 27 | private _disposables: vscode.Disposable[]; 28 | private _explorer: ExplorerProvider; 29 | private _explorerService: ExplorerService; 30 | private _connManager: ConnectionManager; 31 | private _formatter: ScriptFormatter; 32 | private _view: WebviewController; 33 | private _statusBar: StatusBar; 34 | 35 | private refreshNode(node: TreeNode) { this._explorer.refresh(node); } 36 | 37 | constructor(context: vscode.ExtensionContext) { 38 | this._connManager = new ConnectionManager(context); 39 | this._explorerService = new ExplorerService(this.refreshNode.bind(this), this._connManager); 40 | this._explorer = new ExplorerProvider(this._explorerService); 41 | this._formatter = new ScriptFormatter(this._explorerService); 42 | this._view = new WebviewController(context.extensionUri); 43 | this._statusBar = new StatusBar(); 44 | 45 | this._disposables = [ 46 | vscode.window.registerTreeDataProvider("objects", this._explorer), 47 | vscode.commands.registerCommand(cmdAddProfile, async () => this.addProfile()), 48 | vscode.commands.registerCommand(cmdRemoveProfile, (node: TreeNode) => this.removeProfile(node)), 49 | vscode.commands.registerCommand(cmdSelectScript, (node: TreeNode) => this.selectScript(node)), 50 | vscode.commands.registerCommand(cmdExecuteQuery, () => this.executeQuery(this._explorerService.currentNode![0])), 51 | vscode.commands.registerCommand(cmdChooseDatabase, () => this.chooseDatabase()), 52 | vscode.commands.registerCommand(cmdCreateDatabase, (node: TreeNode) => this.createDatabase(node)), 53 | vscode.commands.registerCommand(cmdCreateScript, () => this.createScript()) 54 | ]; 55 | } 56 | 57 | private async chooseDatabase(): Promise { 58 | const result = await this._connManager.chooseDatabase(); 59 | if (!result) { 60 | vscode.window.showWarningMessage("Unable to select a database"); 61 | } 62 | const profile = this._connManager.activeProfile; 63 | const text = `${profile?.connection.name} - ${profile?.database.name}`; 64 | this._statusBar.setDbItemText(text); 65 | return result; 66 | } 67 | 68 | private async createDatabase(node: TreeNode): Promise { 69 | await this._connManager.createDatabase(node!); 70 | this._explorer.refresh(); 71 | } 72 | 73 | private async addProfile(): Promise { 74 | await this._explorerService.addNode(); 75 | this._explorer.refresh(); 76 | } 77 | 78 | private removeProfile(node?: TreeNode): void { 79 | if (!node) { 80 | return; 81 | } 82 | this._explorerService.removeNode(node); 83 | this._explorer.refresh(); 84 | } 85 | 86 | private async executeQuery(node?: TreeNode): Promise { 87 | if (node === undefined) { 88 | vscode.window.showInformationMessage(`${cmdExecuteQuery}: no table selected`); 89 | return; 90 | } 91 | 92 | const sqlText = vscode.window.activeTextEditor?.document.getText()!; 93 | 94 | if (!this._connManager.activeProfile) { 95 | if (!await this.chooseDatabase()) { 96 | return; 97 | } 98 | } 99 | 100 | 101 | const conn = this._connManager.activeProfile!.connection; // node.root()!; 102 | const db = this._connManager.activeProfile!.database; // node.root("database")!; 103 | 104 | const response = await Client.executeQuery(conn.accountId, conn.apiKey, conn.email, db.uuid, sqlText); 105 | 106 | const data = response.result; 107 | 108 | this._view.render(node.title, response); 109 | } 110 | 111 | private async createScript(): Promise { 112 | await this.createDocument("CREATE TABLE"); 113 | } 114 | 115 | private async selectScript(node: TreeNode): Promise { 116 | const scriptText = await this._formatter.selectScript(node); 117 | 118 | await this.createDocument(scriptText); 119 | 120 | await this.executeQuery(node); 121 | } 122 | 123 | private async createDocument(content: string): Promise { 124 | const options = { viewColumn: vscode.ViewColumn.One, preserveFocus: false, preview: false }; 125 | 126 | const document = await vscode.workspace.openTextDocument({ language: "sql", content: content }); 127 | const editor = await vscode.window.showTextDocument(document, options); 128 | 129 | return editor; 130 | } 131 | 132 | /** 133 | * disposes the controller 134 | */ 135 | dispose(): void { 136 | for(let disposable of this._disposables) { 137 | disposable.dispose(); 138 | } 139 | } 140 | } 141 | 142 | export function activate(context: vscode.ExtensionContext) { 143 | context.subscriptions.push( 144 | new EntryController(context) 145 | ); 146 | } 147 | 148 | // this method is called when your extension is deactivated 149 | export function deactivate() {} 150 | -------------------------------------------------------------------------------- /src/models/account.ts: -------------------------------------------------------------------------------- 1 | export interface Account { 2 | label: string; 3 | apiKey: string; 4 | accountId: string; 5 | } -------------------------------------------------------------------------------- /src/models/cf/columnItem.ts: -------------------------------------------------------------------------------- 1 | export interface ColumnItem { 2 | name: string; 3 | pk: number; 4 | type: string; 5 | notnull: number; 6 | } -------------------------------------------------------------------------------- /src/models/cf/databaseItem.ts: -------------------------------------------------------------------------------- 1 | export interface DatabaseItem { 2 | name: string; 3 | uuid: string; 4 | } -------------------------------------------------------------------------------- /src/models/cf/index.ts: -------------------------------------------------------------------------------- 1 | import { DatabaseItem } from "./databaseItem"; 2 | import { ListResponse } from "./listResponse"; 3 | import { QueryInfo } from "./queryInfo"; 4 | import { Response } from "./response"; 5 | import { ResultInfo } from "./resultInfo"; 6 | import { ColumnItem } from "./columnItem"; 7 | 8 | export { 9 | DatabaseItem, 10 | ListResponse, 11 | QueryInfo, 12 | Response, 13 | ResultInfo 14 | }; -------------------------------------------------------------------------------- /src/models/cf/listResponse.ts: -------------------------------------------------------------------------------- 1 | import { ResultInfo } from "./resultInfo"; 2 | 3 | export interface ListResponse { 4 | result: T[]; 5 | result_info: ResultInfo; 6 | success: boolean; 7 | errors: string[] 8 | messages: string[] 9 | } -------------------------------------------------------------------------------- /src/models/cf/queryInfo.ts: -------------------------------------------------------------------------------- 1 | interface QueryMeta { 2 | duration: number; 3 | changes?: number; 4 | lastRowId?: string; 5 | } 6 | 7 | export interface QueryInfo { 8 | results: T[]; 9 | meta: QueryMeta; 10 | success: boolean; 11 | } -------------------------------------------------------------------------------- /src/models/cf/response.ts: -------------------------------------------------------------------------------- 1 | import { ResultInfo } from "./resultInfo"; 2 | 3 | export interface Response { 4 | result: T; 5 | result_info: ResultInfo; 6 | success: boolean; 7 | errors: Array; 8 | messages: Array; 9 | } -------------------------------------------------------------------------------- /src/models/cf/resultInfo.ts: -------------------------------------------------------------------------------- 1 | export interface ResultInfo { 2 | page:number 3 | per_page:number 4 | count:number 5 | total_count:number 6 | } -------------------------------------------------------------------------------- /src/models/cf/tableItem.ts: -------------------------------------------------------------------------------- 1 | export interface TableItem { 2 | name: string; 3 | } -------------------------------------------------------------------------------- /src/models/node.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | export class TreeNode extends vscode.TreeItem { 4 | private _type: string; 5 | private _data: any; 6 | private _parent: TreeNode | undefined; 7 | private _index: number; 8 | 9 | constructor( 10 | index: number, 11 | type: string, 12 | label: string, 13 | contextValue: string, 14 | data: any, 15 | state: vscode.TreeItemCollapsibleState, 16 | parent?: TreeNode) 17 | { 18 | super(label, state); 19 | this._type = type; 20 | this._data = data; 21 | this._parent = parent; 22 | this._index = index; 23 | 24 | this.contextValue = contextValue; 25 | this.setIconPath(type); 26 | } 27 | 28 | setIconPath(type: string): void { 29 | switch (type) { 30 | case "account": 31 | this.iconPath = new vscode.ThemeIcon("account"); 32 | break; 33 | case "database": 34 | this.iconPath = new vscode.ThemeIcon("database"); 35 | break; 36 | case "table": 37 | this.iconPath = new vscode.ThemeIcon("table"); 38 | break; 39 | case "column": 40 | this.iconPath = new vscode.ThemeIcon("output"); 41 | break; 42 | default: 43 | this.iconPath = vscode.ThemeIcon.File; 44 | } 45 | } 46 | 47 | public get type(): string { 48 | return this._type; 49 | } 50 | 51 | public get position(): number { 52 | return this._index; 53 | } 54 | 55 | public get parent(): TreeNode | undefined { 56 | return this._parent; 57 | } 58 | 59 | public data(): T { 60 | return this._data as T; 61 | } 62 | 63 | public root(type: string|undefined = undefined): T|undefined { 64 | if (type !== undefined) { 65 | if (this.type === type) { 66 | return this.data(); 67 | } 68 | let _parent = this._parent; 69 | while (_parent !== undefined && _parent.type !== type) { 70 | _parent = _parent.parent; 71 | } 72 | return _parent?.data(); 73 | } 74 | else { 75 | let _parent = this._parent; 76 | while (_parent !== undefined) { 77 | if(!_parent.parent) { 78 | return _parent.data(); 79 | } 80 | _parent = _parent.parent; 81 | } 82 | } 83 | } 84 | 85 | public get title(): string { 86 | const tokens = [(this._data as any).name]; 87 | 88 | let parent = this._parent; 89 | while(parent !== undefined) { 90 | if (parent?.data !== undefined) { 91 | tokens.push(parent?.data().name); 92 | } 93 | parent = parent?._data; 94 | } 95 | 96 | return tokens.join("."); 97 | } 98 | } -------------------------------------------------------------------------------- /src/script-formatter.ts: -------------------------------------------------------------------------------- 1 | import { ExplorerService } from "./explorer-service"; 2 | import { ColumnItem } from "./models/cf/columnItem"; 3 | import { TableItem } from "./models/cf/tableItem"; 4 | import { TreeNode } from "./models/node"; 5 | 6 | export class ScriptFormatter { 7 | private _explorer; 8 | 9 | constructor(explorer: ExplorerService) { 10 | this._explorer = explorer; 11 | } 12 | 13 | async selectScript(node: TreeNode): Promise { 14 | const table = node.data(); 15 | const columnNodes = await this._explorer.getChildren(node); 16 | const columnNames = columnNodes.map(x => x.data().name); 17 | const columnSpace = ",\n "; 18 | 19 | return this.dedent( 20 | `SELECT 21 | ${columnNames.join(columnSpace)} 22 | FROM 23 | ${table.name} 24 | LIMIT 100; 25 | `, 6); 26 | } 27 | 28 | private dedent(text: string, len: number): string { 29 | const lines = text.split("\n"); 30 | 31 | return lines.map(line => line[0] !== " " ? line : line.substring(len)).join("\n"); 32 | } 33 | } -------------------------------------------------------------------------------- /src/sql-content.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | 18 |
19 | <% queries.forEach(function(query) { %> 20 |
21 | results (<%= query.rows.length %>) 22 | 23 | 24 | <% if (query.headers) { %> 25 | 26 | 27 | <% query.headers.forEach(function(key) { %> 28 | 31 | <% }) %> 32 | 33 | <% } %> 34 | 35 | 36 | <% query.rows.forEach(function(row, i) { %> 37 | 38 | 39 | <% Object.values(row).forEach(function(value) { %> 40 | 43 | <% }) %> 44 | 45 | <% }) %> 46 | 47 |
29 | <%= key %> 30 |
<%= (i + 1) %> 41 | <%= value %> 42 |
48 |
49 |
50 |
51 | messages 52 |

[<%= now %>] Total execution time: <%= query.duration %> secs

53 | <% if (query.changes) { %> 54 |

[<%= now %>] Total changes: <%= query.changes %>

55 | <% } %> 56 |
57 |
58 | <% }) %> 59 | <% if (errors.length > 0) { %> 60 |
61 | errors (<%= errors.length %>) 62 | <% errors.forEach(function(error) { %> 63 |

code <%= error.code %>: <%= error.message %>

64 | <% }) %> 65 |
66 | <% } %> 67 |
68 | 69 | 70 | -------------------------------------------------------------------------------- /src/status-bar.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { cmdChooseDatabase, cmdExecuteQuery } from "./constants"; 3 | 4 | class StatusBar { 5 | private _dbItem: vscode.StatusBarItem; 6 | private _runItem: vscode.StatusBarItem; 7 | 8 | constructor() { 9 | this._dbItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 1); 10 | this._runItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 2); 11 | 12 | this._dbItem.command = cmdChooseDatabase; 13 | this._runItem.command = cmdExecuteQuery; 14 | 15 | this._dbItem.show(); 16 | 17 | this._runItem.show(); 18 | this.setRunItemText("Run Query"); 19 | } 20 | 21 | setDbItemText(text: string) { 22 | this._dbItem.text = `$(server) ${text} (D1)`; 23 | } 24 | 25 | setRunItemText(text: string) { 26 | this._runItem.text = `$(play) ${text} (D1)`; 27 | } 28 | } 29 | 30 | export default StatusBar; -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from '@vscode/test-electron'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from 'vscode'; 6 | // import * as myExtension from '../../extension'; 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5)); 13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /src/views/html/css/pico.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*! 3 | * Pico.css v1.5.5 (https://picocss.com) 4 | * Copyright 2019-2022 - Licensed under MIT 5 | */ 6 | /** 7 | * Theme: default 8 | */ 9 | :root { 10 | --font-family: system-ui, -apple-system, "Segoe UI", "Roboto", "Ubuntu", 11 | "Cantarell", "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", 12 | "Segoe UI Symbol", "Noto Color Emoji"; 13 | --line-height: 1.5; 14 | --font-weight: 400; 15 | --font-size: 16px; 16 | --border-radius: 0.25rem; 17 | --border-width: 1px; 18 | --outline-width: 3px; 19 | --spacing: 1rem; 20 | --typography-spacing-vertical: 1.5rem; 21 | --block-spacing-vertical: calc(var(--spacing) * 2); 22 | --block-spacing-horizontal: var(--spacing); 23 | --grid-spacing-vertical: 0; 24 | --grid-spacing-horizontal: var(--spacing); 25 | --form-element-spacing-vertical: 0.75rem; 26 | --form-element-spacing-horizontal: 1rem; 27 | --nav-element-spacing-vertical: 1rem; 28 | --nav-element-spacing-horizontal: 0.5rem; 29 | --nav-link-spacing-vertical: 0.5rem; 30 | --nav-link-spacing-horizontal: 0.5rem; 31 | --form-label-font-weight: var(--font-weight); 32 | --transition: 0.2s ease-in-out; 33 | --modal-overlay-backdrop-filter: blur(0.25rem); 34 | } 35 | @media (min-width: 576px) { 36 | :root { 37 | --font-size: 17px; 38 | } 39 | } 40 | @media (min-width: 768px) { 41 | :root { 42 | --font-size: 18px; 43 | } 44 | } 45 | @media (min-width: 992px) { 46 | :root { 47 | --font-size: 19px; 48 | } 49 | } 50 | @media (min-width: 1200px) { 51 | :root { 52 | --font-size: 20px; 53 | } 54 | } 55 | 56 | @media (min-width: 576px) { 57 | body > header, 58 | body > main, 59 | body > footer, 60 | section { 61 | --block-spacing-vertical: calc(var(--spacing) * 2.5); 62 | } 63 | } 64 | @media (min-width: 768px) { 65 | body > header, 66 | body > main, 67 | body > footer, 68 | section { 69 | --block-spacing-vertical: calc(var(--spacing) * 3); 70 | } 71 | } 72 | @media (min-width: 992px) { 73 | body > header, 74 | body > main, 75 | body > footer, 76 | section { 77 | --block-spacing-vertical: calc(var(--spacing) * 3.5); 78 | } 79 | } 80 | @media (min-width: 1200px) { 81 | body > header, 82 | body > main, 83 | body > footer, 84 | section { 85 | --block-spacing-vertical: calc(var(--spacing) * 4); 86 | } 87 | } 88 | 89 | @media (min-width: 576px) { 90 | article { 91 | --block-spacing-horizontal: calc(var(--spacing) * 1.25); 92 | } 93 | } 94 | @media (min-width: 768px) { 95 | article { 96 | --block-spacing-horizontal: calc(var(--spacing) * 1.5); 97 | } 98 | } 99 | @media (min-width: 992px) { 100 | article { 101 | --block-spacing-horizontal: calc(var(--spacing) * 1.75); 102 | } 103 | } 104 | @media (min-width: 1200px) { 105 | article { 106 | --block-spacing-horizontal: calc(var(--spacing) * 2); 107 | } 108 | } 109 | 110 | dialog > article { 111 | --block-spacing-vertical: calc(var(--spacing) * 2); 112 | --block-spacing-horizontal: var(--spacing); 113 | } 114 | @media (min-width: 576px) { 115 | dialog > article { 116 | --block-spacing-vertical: calc(var(--spacing) * 2.5); 117 | --block-spacing-horizontal: calc(var(--spacing) * 1.25); 118 | } 119 | } 120 | @media (min-width: 768px) { 121 | dialog > article { 122 | --block-spacing-vertical: calc(var(--spacing) * 3); 123 | --block-spacing-horizontal: calc(var(--spacing) * 1.5); 124 | } 125 | } 126 | 127 | a { 128 | --text-decoration: none; 129 | } 130 | a.secondary, a.contrast { 131 | --text-decoration: underline; 132 | } 133 | 134 | small { 135 | --font-size: 0.875em; 136 | } 137 | 138 | h1, 139 | h2, 140 | h3, 141 | h4, 142 | h5, 143 | h6 { 144 | --font-weight: 700; 145 | } 146 | 147 | h1 { 148 | --font-size: 2rem; 149 | --typography-spacing-vertical: 3rem; 150 | } 151 | 152 | h2 { 153 | --font-size: 1.75rem; 154 | --typography-spacing-vertical: 2.625rem; 155 | } 156 | 157 | h3 { 158 | --font-size: 1.5rem; 159 | --typography-spacing-vertical: 2.25rem; 160 | } 161 | 162 | h4 { 163 | --font-size: 1.25rem; 164 | --typography-spacing-vertical: 1.874rem; 165 | } 166 | 167 | h5 { 168 | --font-size: 1.125rem; 169 | --typography-spacing-vertical: 1.6875rem; 170 | } 171 | 172 | [type=checkbox], 173 | [type=radio] { 174 | --border-width: 2px; 175 | } 176 | 177 | [type=checkbox][role=switch] { 178 | --border-width: 3px; 179 | } 180 | 181 | thead th, 182 | thead td, 183 | tfoot th, 184 | tfoot td { 185 | --border-width: 3px; 186 | } 187 | 188 | :not(thead, tfoot) > * > td { 189 | --font-size: 0.875em; 190 | } 191 | 192 | pre, 193 | code, 194 | kbd, 195 | samp { 196 | --font-family: "Menlo", "Consolas", "Roboto Mono", "Ubuntu Monospace", 197 | "Noto Mono", "Oxygen Mono", "Liberation Mono", monospace, 198 | "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 199 | } 200 | 201 | kbd { 202 | --font-weight: bolder; 203 | } 204 | 205 | [data-theme=light], 206 | :root:not([data-theme=dark]) { 207 | --background-color: #fff; 208 | --color: hsl(205deg, 20%, 32%); 209 | --h1-color: hsl(205deg, 30%, 15%); 210 | --h2-color: #24333e; 211 | --h3-color: hsl(205deg, 25%, 23%); 212 | --h4-color: #374956; 213 | --h5-color: hsl(205deg, 20%, 32%); 214 | --h6-color: #4d606d; 215 | --muted-color: hsl(205deg, 10%, 50%); 216 | --muted-border-color: hsl(205deg, 20%, 94%); 217 | --primary: hsl(195deg, 85%, 41%); 218 | --primary-hover: hsl(195deg, 90%, 32%); 219 | --primary-focus: rgba(16, 149, 193, 0.125); 220 | --primary-inverse: #fff; 221 | --secondary: hsl(205deg, 15%, 41%); 222 | --secondary-hover: hsl(205deg, 20%, 32%); 223 | --secondary-focus: rgba(89, 107, 120, 0.125); 224 | --secondary-inverse: #fff; 225 | --contrast: hsl(205deg, 30%, 15%); 226 | --contrast-hover: #000; 227 | --contrast-focus: rgba(89, 107, 120, 0.125); 228 | --contrast-inverse: #fff; 229 | --mark-background-color: #fff2ca; 230 | --mark-color: #543a26; 231 | --ins-color: #388e3c; 232 | --del-color: #c62828; 233 | --blockquote-border-color: var(--muted-border-color); 234 | --blockquote-footer-color: var(--muted-color); 235 | --button-box-shadow: 0 0 0 rgba(0, 0, 0, 0); 236 | --button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0); 237 | --form-element-background-color: transparent; 238 | --form-element-border-color: hsl(205deg, 14%, 68%); 239 | --form-element-color: var(--color); 240 | --form-element-placeholder-color: var(--muted-color); 241 | --form-element-active-background-color: transparent; 242 | --form-element-active-border-color: var(--primary); 243 | --form-element-focus-color: var(--primary-focus); 244 | --form-element-disabled-background-color: hsl(205deg, 18%, 86%); 245 | --form-element-disabled-border-color: hsl(205deg, 14%, 68%); 246 | --form-element-disabled-opacity: 0.5; 247 | --form-element-invalid-border-color: #c62828; 248 | --form-element-invalid-active-border-color: #d32f2f; 249 | --form-element-invalid-focus-color: rgba(211, 47, 47, 0.125); 250 | --form-element-valid-border-color: #388e3c; 251 | --form-element-valid-active-border-color: #43a047; 252 | --form-element-valid-focus-color: rgba(67, 160, 71, 0.125); 253 | --switch-background-color: hsl(205deg, 16%, 77%); 254 | --switch-color: var(--primary-inverse); 255 | --switch-checked-background-color: var(--primary); 256 | --range-border-color: hsl(205deg, 18%, 86%); 257 | --range-active-border-color: hsl(205deg, 16%, 77%); 258 | --range-thumb-border-color: var(--background-color); 259 | --range-thumb-color: var(--secondary); 260 | --range-thumb-hover-color: var(--secondary-hover); 261 | --range-thumb-active-color: var(--primary); 262 | --table-border-color: var(--muted-border-color); 263 | --table-row-stripped-background-color: #f6f8f9; 264 | --code-background-color: hsl(205deg, 20%, 94%); 265 | --code-color: var(--muted-color); 266 | --code-kbd-background-color: var(--contrast); 267 | --code-kbd-color: var(--contrast-inverse); 268 | --code-tag-color: hsl(330deg, 40%, 50%); 269 | --code-property-color: hsl(185deg, 40%, 40%); 270 | --code-value-color: hsl(40deg, 20%, 50%); 271 | --code-comment-color: hsl(205deg, 14%, 68%); 272 | --accordion-border-color: var(--muted-border-color); 273 | --accordion-close-summary-color: var(--color); 274 | --accordion-open-summary-color: var(--muted-color); 275 | --card-background-color: var(--background-color); 276 | --card-border-color: var(--muted-border-color); 277 | --card-box-shadow: 278 | 0.0145rem 0.029rem 0.174rem rgba(27, 40, 50, 0.01698), 279 | 0.0335rem 0.067rem 0.402rem rgba(27, 40, 50, 0.024), 280 | 0.0625rem 0.125rem 0.75rem rgba(27, 40, 50, 0.03), 281 | 0.1125rem 0.225rem 1.35rem rgba(27, 40, 50, 0.036), 282 | 0.2085rem 0.417rem 2.502rem rgba(27, 40, 50, 0.04302), 283 | 0.5rem 1rem 6rem rgba(27, 40, 50, 0.06), 284 | 0 0 0 0.0625rem rgba(27, 40, 50, 0.015); 285 | --card-sectionning-background-color: #fbfbfc; 286 | --dropdown-background-color: #fbfbfc; 287 | --dropdown-border-color: #e1e6eb; 288 | --dropdown-box-shadow: var(--card-box-shadow); 289 | --dropdown-color: var(--color); 290 | --dropdown-hover-background-color: hsl(205deg, 20%, 94%); 291 | --modal-overlay-background-color: rgba(213, 220, 226, 0.7); 292 | --progress-background-color: hsl(205deg, 18%, 86%); 293 | --progress-color: var(--primary); 294 | --loading-spinner-opacity: 0.5; 295 | --tooltip-background-color: var(--contrast); 296 | --tooltip-color: var(--contrast-inverse); 297 | --icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); 298 | --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); 299 | --icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); 300 | --icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); 301 | --icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(115, 130, 140)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E"); 302 | --icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E"); 303 | --icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(198, 40, 40)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E"); 304 | --icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E"); 305 | --icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E"); 306 | --icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E"); 307 | --icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(56, 142, 60)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); 308 | color-scheme: light; 309 | } 310 | 311 | @media only screen and (prefers-color-scheme: dark) { 312 | :root:not([data-theme=light]) { 313 | --background-color: #11191f; 314 | --color: hsl(205deg, 16%, 77%); 315 | --h1-color: hsl(205deg, 20%, 94%); 316 | --h2-color: #e1e6eb; 317 | --h3-color: hsl(205deg, 18%, 86%); 318 | --h4-color: #c8d1d8; 319 | --h5-color: hsl(205deg, 16%, 77%); 320 | --h6-color: #afbbc4; 321 | --muted-color: hsl(205deg, 10%, 50%); 322 | --muted-border-color: #1f2d38; 323 | --primary: hsl(195deg, 85%, 41%); 324 | --primary-hover: hsl(195deg, 80%, 50%); 325 | --primary-focus: rgba(16, 149, 193, 0.25); 326 | --primary-inverse: #fff; 327 | --secondary: hsl(205deg, 15%, 41%); 328 | --secondary-hover: hsl(205deg, 10%, 50%); 329 | --secondary-focus: rgba(115, 130, 140, 0.25); 330 | --secondary-inverse: #fff; 331 | --contrast: hsl(205deg, 20%, 94%); 332 | --contrast-hover: #fff; 333 | --contrast-focus: rgba(115, 130, 140, 0.25); 334 | --contrast-inverse: #000; 335 | --mark-background-color: #d1c284; 336 | --mark-color: #11191f; 337 | --ins-color: #388e3c; 338 | --del-color: #c62828; 339 | --blockquote-border-color: var(--muted-border-color); 340 | --blockquote-footer-color: var(--muted-color); 341 | --button-box-shadow: 0 0 0 rgba(0, 0, 0, 0); 342 | --button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0); 343 | --form-element-background-color: #11191f; 344 | --form-element-border-color: #374956; 345 | --form-element-color: var(--color); 346 | --form-element-placeholder-color: var(--muted-color); 347 | --form-element-active-background-color: var(--form-element-background-color); 348 | --form-element-active-border-color: var(--primary); 349 | --form-element-focus-color: var(--primary-focus); 350 | --form-element-disabled-background-color: hsl(205deg, 25%, 23%); 351 | --form-element-disabled-border-color: hsl(205deg, 20%, 32%); 352 | --form-element-disabled-opacity: 0.5; 353 | --form-element-invalid-border-color: #b71c1c; 354 | --form-element-invalid-active-border-color: #c62828; 355 | --form-element-invalid-focus-color: rgba(198, 40, 40, 0.25); 356 | --form-element-valid-border-color: #2e7d32; 357 | --form-element-valid-active-border-color: #388e3c; 358 | --form-element-valid-focus-color: rgba(56, 142, 60, 0.25); 359 | --switch-background-color: #374956; 360 | --switch-color: var(--primary-inverse); 361 | --switch-checked-background-color: var(--primary); 362 | --range-border-color: #24333e; 363 | --range-active-border-color: hsl(205deg, 25%, 23%); 364 | --range-thumb-border-color: var(--background-color); 365 | --range-thumb-color: var(--secondary); 366 | --range-thumb-hover-color: var(--secondary-hover); 367 | --range-thumb-active-color: var(--primary); 368 | --table-border-color: var(--muted-border-color); 369 | --table-row-stripped-background-color: rgba(115, 130, 140, 0.05); 370 | --code-background-color: #18232c; 371 | --code-color: var(--muted-color); 372 | --code-kbd-background-color: var(--contrast); 373 | --code-kbd-color: var(--contrast-inverse); 374 | --code-tag-color: hsl(330deg, 30%, 50%); 375 | --code-property-color: hsl(185deg, 30%, 50%); 376 | --code-value-color: hsl(40deg, 10%, 50%); 377 | --code-comment-color: #4d606d; 378 | --accordion-border-color: var(--muted-border-color); 379 | --accordion-active-summary-color: var(--primary); 380 | --accordion-close-summary-color: var(--color); 381 | --accordion-open-summary-color: var(--muted-color); 382 | --card-background-color: #141e26; 383 | --card-border-color: var(--card-background-color); 384 | --card-box-shadow: 385 | 0.0145rem 0.029rem 0.174rem rgba(0, 0, 0, 0.01698), 386 | 0.0335rem 0.067rem 0.402rem rgba(0, 0, 0, 0.024), 387 | 0.0625rem 0.125rem 0.75rem rgba(0, 0, 0, 0.03), 388 | 0.1125rem 0.225rem 1.35rem rgba(0, 0, 0, 0.036), 389 | 0.2085rem 0.417rem 2.502rem rgba(0, 0, 0, 0.04302), 390 | 0.5rem 1rem 6rem rgba(0, 0, 0, 0.06), 391 | 0 0 0 0.0625rem rgba(0, 0, 0, 0.015); 392 | --card-sectionning-background-color: #18232c; 393 | --dropdown-background-color: hsl(205deg, 30%, 15%); 394 | --dropdown-border-color: #24333e; 395 | --dropdown-box-shadow: var(--card-box-shadow); 396 | --dropdown-color: var(--color); 397 | --dropdown-hover-background-color: rgba(36, 51, 62, 0.75); 398 | --modal-overlay-background-color: rgba(36, 51, 62, 0.8); 399 | --progress-background-color: #24333e; 400 | --progress-color: var(--primary); 401 | --loading-spinner-opacity: 0.5; 402 | --tooltip-background-color: var(--contrast); 403 | --tooltip-color: var(--contrast-inverse); 404 | --icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); 405 | --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); 406 | --icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); 407 | --icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(0, 0, 0)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); 408 | --icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(115, 130, 140)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E"); 409 | --icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E"); 410 | --icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(183, 28, 28)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E"); 411 | --icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E"); 412 | --icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E"); 413 | --icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E"); 414 | --icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(46, 125, 50)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); 415 | color-scheme: dark; 416 | } 417 | } 418 | [data-theme=dark] { 419 | --background-color: #11191f; 420 | --color: hsl(205deg, 16%, 77%); 421 | --h1-color: hsl(205deg, 20%, 94%); 422 | --h2-color: #e1e6eb; 423 | --h3-color: hsl(205deg, 18%, 86%); 424 | --h4-color: #c8d1d8; 425 | --h5-color: hsl(205deg, 16%, 77%); 426 | --h6-color: #afbbc4; 427 | --muted-color: hsl(205deg, 10%, 50%); 428 | --muted-border-color: #1f2d38; 429 | --primary: hsl(195deg, 85%, 41%); 430 | --primary-hover: hsl(195deg, 80%, 50%); 431 | --primary-focus: rgba(16, 149, 193, 0.25); 432 | --primary-inverse: #fff; 433 | --secondary: hsl(205deg, 15%, 41%); 434 | --secondary-hover: hsl(205deg, 10%, 50%); 435 | --secondary-focus: rgba(115, 130, 140, 0.25); 436 | --secondary-inverse: #fff; 437 | --contrast: hsl(205deg, 20%, 94%); 438 | --contrast-hover: #fff; 439 | --contrast-focus: rgba(115, 130, 140, 0.25); 440 | --contrast-inverse: #000; 441 | --mark-background-color: #d1c284; 442 | --mark-color: #11191f; 443 | --ins-color: #388e3c; 444 | --del-color: #c62828; 445 | --blockquote-border-color: var(--muted-border-color); 446 | --blockquote-footer-color: var(--muted-color); 447 | --button-box-shadow: 0 0 0 rgba(0, 0, 0, 0); 448 | --button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0); 449 | --form-element-background-color: #11191f; 450 | --form-element-border-color: #374956; 451 | --form-element-color: var(--color); 452 | --form-element-placeholder-color: var(--muted-color); 453 | --form-element-active-background-color: var(--form-element-background-color); 454 | --form-element-active-border-color: var(--primary); 455 | --form-element-focus-color: var(--primary-focus); 456 | --form-element-disabled-background-color: hsl(205deg, 25%, 23%); 457 | --form-element-disabled-border-color: hsl(205deg, 20%, 32%); 458 | --form-element-disabled-opacity: 0.5; 459 | --form-element-invalid-border-color: #b71c1c; 460 | --form-element-invalid-active-border-color: #c62828; 461 | --form-element-invalid-focus-color: rgba(198, 40, 40, 0.25); 462 | --form-element-valid-border-color: #2e7d32; 463 | --form-element-valid-active-border-color: #388e3c; 464 | --form-element-valid-focus-color: rgba(56, 142, 60, 0.25); 465 | --switch-background-color: #374956; 466 | --switch-color: var(--primary-inverse); 467 | --switch-checked-background-color: var(--primary); 468 | --range-border-color: #24333e; 469 | --range-active-border-color: hsl(205deg, 25%, 23%); 470 | --range-thumb-border-color: var(--background-color); 471 | --range-thumb-color: var(--secondary); 472 | --range-thumb-hover-color: var(--secondary-hover); 473 | --range-thumb-active-color: var(--primary); 474 | --table-border-color: var(--muted-border-color); 475 | --table-row-stripped-background-color: rgba(115, 130, 140, 0.05); 476 | --code-background-color: #18232c; 477 | --code-color: var(--muted-color); 478 | --code-kbd-background-color: var(--contrast); 479 | --code-kbd-color: var(--contrast-inverse); 480 | --code-tag-color: hsl(330deg, 30%, 50%); 481 | --code-property-color: hsl(185deg, 30%, 50%); 482 | --code-value-color: hsl(40deg, 10%, 50%); 483 | --code-comment-color: #4d606d; 484 | --accordion-border-color: var(--muted-border-color); 485 | --accordion-active-summary-color: var(--primary); 486 | --accordion-close-summary-color: var(--color); 487 | --accordion-open-summary-color: var(--muted-color); 488 | --card-background-color: #141e26; 489 | --card-border-color: var(--card-background-color); 490 | --card-box-shadow: 491 | 0.0145rem 0.029rem 0.174rem rgba(0, 0, 0, 0.01698), 492 | 0.0335rem 0.067rem 0.402rem rgba(0, 0, 0, 0.024), 493 | 0.0625rem 0.125rem 0.75rem rgba(0, 0, 0, 0.03), 494 | 0.1125rem 0.225rem 1.35rem rgba(0, 0, 0, 0.036), 495 | 0.2085rem 0.417rem 2.502rem rgba(0, 0, 0, 0.04302), 496 | 0.5rem 1rem 6rem rgba(0, 0, 0, 0.06), 497 | 0 0 0 0.0625rem rgba(0, 0, 0, 0.015); 498 | --card-sectionning-background-color: #18232c; 499 | --dropdown-background-color: hsl(205deg, 30%, 15%); 500 | --dropdown-border-color: #24333e; 501 | --dropdown-box-shadow: var(--card-box-shadow); 502 | --dropdown-color: var(--color); 503 | --dropdown-hover-background-color: rgba(36, 51, 62, 0.75); 504 | --modal-overlay-background-color: rgba(36, 51, 62, 0.8); 505 | --progress-background-color: #24333e; 506 | --progress-color: var(--primary); 507 | --loading-spinner-opacity: 0.5; 508 | --tooltip-background-color: var(--contrast); 509 | --tooltip-color: var(--contrast-inverse); 510 | --icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); 511 | --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); 512 | --icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); 513 | --icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(0, 0, 0)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); 514 | --icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(115, 130, 140)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E"); 515 | --icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E"); 516 | --icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(183, 28, 28)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E"); 517 | --icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E"); 518 | --icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E"); 519 | --icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E"); 520 | --icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(46, 125, 50)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); 521 | color-scheme: dark; 522 | } 523 | 524 | progress, 525 | [type=checkbox], 526 | [type=radio], 527 | [type=range] { 528 | accent-color: var(--primary); 529 | } 530 | 531 | /** 532 | * Document 533 | * Content-box & Responsive typography 534 | */ 535 | *, 536 | *::before, 537 | *::after { 538 | box-sizing: border-box; 539 | background-repeat: no-repeat; 540 | } 541 | 542 | ::before, 543 | ::after { 544 | text-decoration: inherit; 545 | vertical-align: inherit; 546 | } 547 | 548 | :where(:root) { 549 | -webkit-tap-highlight-color: transparent; 550 | -webkit-text-size-adjust: 100%; 551 | -moz-text-size-adjust: 100%; 552 | text-size-adjust: 100%; 553 | background-color: var(--background-color); 554 | color: var(--color); 555 | font-weight: var(--font-weight); 556 | font-size: var(--font-size); 557 | line-height: var(--line-height); 558 | font-family: var(--font-family); 559 | text-rendering: optimizeLegibility; 560 | overflow-wrap: break-word; 561 | cursor: default; 562 | -moz-tab-size: 4; 563 | -o-tab-size: 4; 564 | tab-size: 4; 565 | } 566 | 567 | /** 568 | * Sectioning 569 | * Container and responsive spacings for header, main, footer 570 | */ 571 | main { 572 | display: block; 573 | } 574 | 575 | body { 576 | width: 100%; 577 | margin: 0; 578 | } 579 | body > header, 580 | body > main, 581 | body > footer { 582 | width: 100%; 583 | margin-right: auto; 584 | margin-left: auto; 585 | padding: var(--block-spacing-vertical) 0; 586 | } 587 | 588 | /** 589 | * Container 590 | */ 591 | .container, 592 | .container-fluid { 593 | width: 100%; 594 | margin-right: auto; 595 | margin-left: auto; 596 | padding-right: var(--spacing); 597 | padding-left: var(--spacing); 598 | } 599 | 600 | @media (min-width: 576px) { 601 | .container { 602 | max-width: 510px; 603 | padding-right: 0; 604 | padding-left: 0; 605 | } 606 | } 607 | @media (min-width: 768px) { 608 | .container { 609 | max-width: 700px; 610 | } 611 | } 612 | @media (min-width: 992px) { 613 | .container { 614 | max-width: 920px; 615 | } 616 | } 617 | @media (min-width: 1200px) { 618 | .container { 619 | max-width: 1130px; 620 | } 621 | } 622 | 623 | /** 624 | * Section 625 | * Responsive spacings for section 626 | */ 627 | section { 628 | margin-bottom: var(--block-spacing-vertical); 629 | } 630 | 631 | /** 632 | * Grid 633 | * Minimal grid system with auto-layout columns 634 | */ 635 | .grid { 636 | grid-column-gap: var(--grid-spacing-horizontal); 637 | grid-row-gap: var(--grid-spacing-vertical); 638 | display: grid; 639 | grid-template-columns: 1fr; 640 | margin: 0; 641 | } 642 | @media (min-width: 992px) { 643 | .grid { 644 | grid-template-columns: repeat(auto-fit, minmax(0%, 1fr)); 645 | } 646 | } 647 | .grid > * { 648 | min-width: 0; 649 | } 650 | 651 | /** 652 | * Horizontal scroller (
) 653 | */ 654 | figure { 655 | display: block; 656 | margin: 0; 657 | padding: 0; 658 | overflow-x: auto; 659 | } 660 | figure figcaption { 661 | padding: calc(var(--spacing) * 0.5) 0; 662 | color: var(--muted-color); 663 | } 664 | 665 | /** 666 | * Typography 667 | */ 668 | b, 669 | strong { 670 | font-weight: bolder; 671 | } 672 | 673 | sub, 674 | sup { 675 | position: relative; 676 | font-size: 0.75em; 677 | line-height: 0; 678 | vertical-align: baseline; 679 | } 680 | 681 | sub { 682 | bottom: -0.25em; 683 | } 684 | 685 | sup { 686 | top: -0.5em; 687 | } 688 | 689 | address, 690 | blockquote, 691 | dl, 692 | figure, 693 | form, 694 | ol, 695 | p, 696 | pre, 697 | table, 698 | ul { 699 | margin-top: 0; 700 | margin-bottom: var(--typography-spacing-vertical); 701 | color: var(--color); 702 | font-style: normal; 703 | font-weight: var(--font-weight); 704 | font-size: var(--font-size); 705 | } 706 | 707 | a, 708 | [role=link] { 709 | --color: var(--primary); 710 | --background-color: transparent; 711 | outline: none; 712 | background-color: var(--background-color); 713 | color: var(--color); 714 | -webkit-text-decoration: var(--text-decoration); 715 | text-decoration: var(--text-decoration); 716 | transition: background-color var(--transition), color var(--transition), box-shadow var(--transition), -webkit-text-decoration var(--transition); 717 | transition: background-color var(--transition), color var(--transition), text-decoration var(--transition), box-shadow var(--transition); 718 | transition: background-color var(--transition), color var(--transition), text-decoration var(--transition), box-shadow var(--transition), -webkit-text-decoration var(--transition); 719 | } 720 | a:is([aria-current], :hover, :active, :focus), 721 | [role=link]:is([aria-current], :hover, :active, :focus) { 722 | --color: var(--primary-hover); 723 | --text-decoration: underline; 724 | } 725 | a:focus, 726 | [role=link]:focus { 727 | --background-color: var(--primary-focus); 728 | } 729 | a.secondary, 730 | [role=link].secondary { 731 | --color: var(--secondary); 732 | } 733 | a.secondary:is([aria-current], :hover, :active, :focus), 734 | [role=link].secondary:is([aria-current], :hover, :active, :focus) { 735 | --color: var(--secondary-hover); 736 | } 737 | a.secondary:focus, 738 | [role=link].secondary:focus { 739 | --background-color: var(--secondary-focus); 740 | } 741 | a.contrast, 742 | [role=link].contrast { 743 | --color: var(--contrast); 744 | } 745 | a.contrast:is([aria-current], :hover, :active, :focus), 746 | [role=link].contrast:is([aria-current], :hover, :active, :focus) { 747 | --color: var(--contrast-hover); 748 | } 749 | a.contrast:focus, 750 | [role=link].contrast:focus { 751 | --background-color: var(--contrast-focus); 752 | } 753 | 754 | h1, 755 | h2, 756 | h3, 757 | h4, 758 | h5, 759 | h6 { 760 | margin-top: 0; 761 | margin-bottom: var(--typography-spacing-vertical); 762 | color: var(--color); 763 | font-weight: var(--font-weight); 764 | font-size: var(--font-size); 765 | font-family: var(--font-family); 766 | } 767 | 768 | h1 { 769 | --color: var(--h1-color); 770 | } 771 | 772 | h2 { 773 | --color: var(--h2-color); 774 | } 775 | 776 | h3 { 777 | --color: var(--h3-color); 778 | } 779 | 780 | h4 { 781 | --color: var(--h4-color); 782 | } 783 | 784 | h5 { 785 | --color: var(--h5-color); 786 | } 787 | 788 | h6 { 789 | --color: var(--h6-color); 790 | } 791 | 792 | :where(address, blockquote, dl, figure, form, ol, p, pre, table, ul) ~ :is(h1, h2, h3, h4, h5, h6) { 793 | margin-top: var(--typography-spacing-vertical); 794 | } 795 | 796 | hgroup, 797 | .headings { 798 | margin-bottom: var(--typography-spacing-vertical); 799 | } 800 | hgroup > *, 801 | .headings > * { 802 | margin-bottom: 0; 803 | } 804 | hgroup > *:last-child, 805 | .headings > *:last-child { 806 | --color: var(--muted-color); 807 | --font-weight: unset; 808 | font-size: 1rem; 809 | font-family: unset; 810 | } 811 | 812 | p { 813 | margin-bottom: var(--typography-spacing-vertical); 814 | } 815 | 816 | small { 817 | font-size: var(--font-size); 818 | } 819 | 820 | :where(dl, ol, ul) { 821 | padding-right: 0; 822 | padding-left: var(--spacing); 823 | -webkit-padding-start: var(--spacing); 824 | padding-inline-start: var(--spacing); 825 | -webkit-padding-end: 0; 826 | padding-inline-end: 0; 827 | } 828 | :where(dl, ol, ul) li { 829 | margin-bottom: calc(var(--typography-spacing-vertical) * 0.25); 830 | } 831 | 832 | :where(dl, ol, ul) :is(dl, ol, ul) { 833 | margin: 0; 834 | margin-top: calc(var(--typography-spacing-vertical) * 0.25); 835 | } 836 | 837 | ul li { 838 | list-style: square; 839 | } 840 | 841 | mark { 842 | padding: 0.125rem 0.25rem; 843 | background-color: var(--mark-background-color); 844 | color: var(--mark-color); 845 | vertical-align: baseline; 846 | } 847 | 848 | blockquote { 849 | display: block; 850 | margin: var(--typography-spacing-vertical) 0; 851 | padding: var(--spacing); 852 | border-right: none; 853 | border-left: 0.25rem solid var(--blockquote-border-color); 854 | -webkit-border-start: 0.25rem solid var(--blockquote-border-color); 855 | border-inline-start: 0.25rem solid var(--blockquote-border-color); 856 | -webkit-border-end: none; 857 | border-inline-end: none; 858 | } 859 | blockquote footer { 860 | margin-top: calc(var(--typography-spacing-vertical) * 0.5); 861 | color: var(--blockquote-footer-color); 862 | } 863 | 864 | abbr[title] { 865 | border-bottom: 1px dotted; 866 | text-decoration: none; 867 | cursor: help; 868 | } 869 | 870 | ins { 871 | color: var(--ins-color); 872 | text-decoration: none; 873 | } 874 | 875 | del { 876 | color: var(--del-color); 877 | } 878 | 879 | ::-moz-selection { 880 | background-color: var(--primary-focus); 881 | } 882 | 883 | ::selection { 884 | background-color: var(--primary-focus); 885 | } 886 | 887 | /** 888 | * Embedded content 889 | */ 890 | :where(audio, canvas, iframe, img, svg, video) { 891 | vertical-align: middle; 892 | } 893 | 894 | audio, 895 | video { 896 | display: inline-block; 897 | } 898 | 899 | audio:not([controls]) { 900 | display: none; 901 | height: 0; 902 | } 903 | 904 | :where(iframe) { 905 | border-style: none; 906 | } 907 | 908 | img { 909 | max-width: 100%; 910 | height: auto; 911 | border-style: none; 912 | } 913 | 914 | :where(svg:not([fill])) { 915 | fill: currentColor; 916 | } 917 | 918 | svg:not(:root) { 919 | overflow: hidden; 920 | } 921 | 922 | /** 923 | * Button 924 | */ 925 | button { 926 | margin: 0; 927 | overflow: visible; 928 | font-family: inherit; 929 | text-transform: none; 930 | } 931 | 932 | button, 933 | [type=button], 934 | [type=reset], 935 | [type=submit] { 936 | -webkit-appearance: button; 937 | } 938 | 939 | button { 940 | display: block; 941 | width: 100%; 942 | margin-bottom: var(--spacing); 943 | } 944 | 945 | [role=button] { 946 | display: inline-block; 947 | text-decoration: none; 948 | } 949 | 950 | button, 951 | input[type=submit], 952 | input[type=button], 953 | input[type=reset], 954 | [role=button] { 955 | --background-color: var(--primary); 956 | --border-color: var(--primary); 957 | --color: var(--primary-inverse); 958 | --box-shadow: var(--button-box-shadow, 0 0 0 rgba(0, 0, 0, 0)); 959 | padding: var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal); 960 | border: var(--border-width) solid var(--border-color); 961 | border-radius: var(--border-radius); 962 | outline: none; 963 | background-color: var(--background-color); 964 | box-shadow: var(--box-shadow); 965 | color: var(--color); 966 | font-weight: var(--font-weight); 967 | font-size: 1rem; 968 | line-height: var(--line-height); 969 | text-align: center; 970 | cursor: pointer; 971 | transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition); 972 | } 973 | button:is([aria-current], :hover, :active, :focus), 974 | input[type=submit]:is([aria-current], :hover, :active, :focus), 975 | input[type=button]:is([aria-current], :hover, :active, :focus), 976 | input[type=reset]:is([aria-current], :hover, :active, :focus), 977 | [role=button]:is([aria-current], :hover, :active, :focus) { 978 | --background-color: var(--primary-hover); 979 | --border-color: var(--primary-hover); 980 | --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)); 981 | --color: var(--primary-inverse); 982 | } 983 | button:focus, 984 | input[type=submit]:focus, 985 | input[type=button]:focus, 986 | input[type=reset]:focus, 987 | [role=button]:focus { 988 | --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)), 989 | 0 0 0 var(--outline-width) var(--primary-focus); 990 | } 991 | 992 | :is(button, input[type=submit], input[type=button], [role=button]).secondary, 993 | input[type=reset] { 994 | --background-color: var(--secondary); 995 | --border-color: var(--secondary); 996 | --color: var(--secondary-inverse); 997 | cursor: pointer; 998 | } 999 | :is(button, input[type=submit], input[type=button], [role=button]).secondary:is([aria-current], :hover, :active, :focus), 1000 | input[type=reset]:is([aria-current], :hover, :active, :focus) { 1001 | --background-color: var(--secondary-hover); 1002 | --border-color: var(--secondary-hover); 1003 | --color: var(--secondary-inverse); 1004 | } 1005 | :is(button, input[type=submit], input[type=button], [role=button]).secondary:focus, 1006 | input[type=reset]:focus { 1007 | --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)), 1008 | 0 0 0 var(--outline-width) var(--secondary-focus); 1009 | } 1010 | 1011 | :is(button, input[type=submit], input[type=button], [role=button]).contrast { 1012 | --background-color: var(--contrast); 1013 | --border-color: var(--contrast); 1014 | --color: var(--contrast-inverse); 1015 | } 1016 | :is(button, input[type=submit], input[type=button], [role=button]).contrast:is([aria-current], :hover, :active, :focus) { 1017 | --background-color: var(--contrast-hover); 1018 | --border-color: var(--contrast-hover); 1019 | --color: var(--contrast-inverse); 1020 | } 1021 | :is(button, input[type=submit], input[type=button], [role=button]).contrast:focus { 1022 | --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)), 1023 | 0 0 0 var(--outline-width) var(--contrast-focus); 1024 | } 1025 | 1026 | :is(button, input[type=submit], input[type=button], [role=button]).outline, 1027 | input[type=reset].outline { 1028 | --background-color: transparent; 1029 | --color: var(--primary); 1030 | } 1031 | :is(button, input[type=submit], input[type=button], [role=button]).outline:is([aria-current], :hover, :active, :focus), 1032 | input[type=reset].outline:is([aria-current], :hover, :active, :focus) { 1033 | --background-color: transparent; 1034 | --color: var(--primary-hover); 1035 | } 1036 | 1037 | :is(button, input[type=submit], input[type=button], [role=button]).outline.secondary, 1038 | input[type=reset].outline { 1039 | --color: var(--secondary); 1040 | } 1041 | :is(button, input[type=submit], input[type=button], [role=button]).outline.secondary:is([aria-current], :hover, :active, :focus), 1042 | input[type=reset].outline:is([aria-current], :hover, :active, :focus) { 1043 | --color: var(--secondary-hover); 1044 | } 1045 | 1046 | :is(button, input[type=submit], input[type=button], [role=button]).outline.contrast { 1047 | --color: var(--contrast); 1048 | } 1049 | :is(button, input[type=submit], input[type=button], [role=button]).outline.contrast:is([aria-current], :hover, :active, :focus) { 1050 | --color: var(--contrast-hover); 1051 | } 1052 | 1053 | :where(button, [type=submit], [type=button], [type=reset], [role=button])[disabled], 1054 | :where(fieldset[disabled]) :is(button, [type=submit], [type=button], [type=reset], [role=button]), 1055 | a[role=button]:not([href]) { 1056 | opacity: 0.5; 1057 | pointer-events: none; 1058 | } 1059 | 1060 | /** 1061 | * Form elements 1062 | */ 1063 | input, 1064 | optgroup, 1065 | select, 1066 | textarea { 1067 | margin: 0; 1068 | font-size: 1rem; 1069 | line-height: var(--line-height); 1070 | font-family: inherit; 1071 | letter-spacing: inherit; 1072 | } 1073 | 1074 | input { 1075 | overflow: visible; 1076 | } 1077 | 1078 | select { 1079 | text-transform: none; 1080 | } 1081 | 1082 | legend { 1083 | max-width: 100%; 1084 | padding: 0; 1085 | color: inherit; 1086 | white-space: normal; 1087 | } 1088 | 1089 | textarea { 1090 | overflow: auto; 1091 | } 1092 | 1093 | [type=checkbox], 1094 | [type=radio] { 1095 | padding: 0; 1096 | } 1097 | 1098 | ::-webkit-inner-spin-button, 1099 | ::-webkit-outer-spin-button { 1100 | height: auto; 1101 | } 1102 | 1103 | [type=search] { 1104 | -webkit-appearance: textfield; 1105 | outline-offset: -2px; 1106 | } 1107 | 1108 | [type=search]::-webkit-search-decoration { 1109 | -webkit-appearance: none; 1110 | } 1111 | 1112 | ::-webkit-file-upload-button { 1113 | -webkit-appearance: button; 1114 | font: inherit; 1115 | } 1116 | 1117 | ::-moz-focus-inner { 1118 | padding: 0; 1119 | border-style: none; 1120 | } 1121 | 1122 | :-moz-focusring { 1123 | outline: none; 1124 | } 1125 | 1126 | :-moz-ui-invalid { 1127 | box-shadow: none; 1128 | } 1129 | 1130 | ::-ms-expand { 1131 | display: none; 1132 | } 1133 | 1134 | [type=file], 1135 | [type=range] { 1136 | padding: 0; 1137 | border-width: 0; 1138 | } 1139 | 1140 | input:not([type=checkbox], [type=radio], [type=range]) { 1141 | height: calc(1rem * var(--line-height) + var(--form-element-spacing-vertical) * 2 + var(--border-width) * 2); 1142 | } 1143 | 1144 | fieldset { 1145 | margin: 0; 1146 | margin-bottom: var(--spacing); 1147 | padding: 0; 1148 | border: 0; 1149 | } 1150 | 1151 | label, 1152 | fieldset legend { 1153 | display: block; 1154 | margin-bottom: calc(var(--spacing) * 0.25); 1155 | font-weight: var(--form-label-font-weight, var(--font-weight)); 1156 | } 1157 | 1158 | input:not([type=checkbox], [type=radio]), 1159 | select, 1160 | textarea { 1161 | width: 100%; 1162 | } 1163 | 1164 | input:not([type=checkbox], [type=radio], [type=range], [type=file]), 1165 | select, 1166 | textarea { 1167 | -webkit-appearance: none; 1168 | -moz-appearance: none; 1169 | appearance: none; 1170 | padding: var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal); 1171 | } 1172 | 1173 | input, 1174 | select, 1175 | textarea { 1176 | --background-color: var(--form-element-background-color); 1177 | --border-color: var(--form-element-border-color); 1178 | --color: var(--form-element-color); 1179 | --box-shadow: none; 1180 | border: var(--border-width) solid var(--border-color); 1181 | border-radius: var(--border-radius); 1182 | outline: none; 1183 | background-color: var(--background-color); 1184 | box-shadow: var(--box-shadow); 1185 | color: var(--color); 1186 | font-weight: var(--font-weight); 1187 | transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition); 1188 | } 1189 | 1190 | input:not([type=submit], [type=button], [type=reset], [type=checkbox], [type=radio], [readonly]):is(:active, :focus), 1191 | :where(select, textarea):is(:active, :focus) { 1192 | --background-color: var(--form-element-active-background-color); 1193 | } 1194 | 1195 | input:not([type=submit], [type=button], [type=reset], [role=switch], [readonly]):is(:active, :focus), 1196 | :where(select, textarea):is(:active, :focus) { 1197 | --border-color: var(--form-element-active-border-color); 1198 | } 1199 | 1200 | input:not([type=submit], [type=button], [type=reset], [type=range], [type=file], [readonly]):focus, 1201 | select:focus, 1202 | textarea:focus { 1203 | --box-shadow: 0 0 0 var(--outline-width) var(--form-element-focus-color); 1204 | } 1205 | 1206 | input:not([type=submit], [type=button], [type=reset])[disabled], 1207 | select[disabled], 1208 | textarea[disabled], 1209 | :where(fieldset[disabled]) :is(input:not([type=submit], [type=button], [type=reset]), select, textarea) { 1210 | --background-color: var(--form-element-disabled-background-color); 1211 | --border-color: var(--form-element-disabled-border-color); 1212 | opacity: var(--form-element-disabled-opacity); 1213 | pointer-events: none; 1214 | } 1215 | 1216 | :where(input, select, textarea):not([type=checkbox], [type=radio])[aria-invalid] { 1217 | padding-right: calc(var(--form-element-spacing-horizontal) + 1.5rem) !important; 1218 | padding-left: var(--form-element-spacing-horizontal); 1219 | -webkit-padding-start: var(--form-element-spacing-horizontal) !important; 1220 | padding-inline-start: var(--form-element-spacing-horizontal) !important; 1221 | -webkit-padding-end: calc(var(--form-element-spacing-horizontal) + 1.5rem) !important; 1222 | padding-inline-end: calc(var(--form-element-spacing-horizontal) + 1.5rem) !important; 1223 | background-position: center right 0.75rem; 1224 | background-size: 1rem auto; 1225 | background-repeat: no-repeat; 1226 | } 1227 | :where(input, select, textarea):not([type=checkbox], [type=radio])[aria-invalid=false] { 1228 | background-image: var(--icon-valid); 1229 | } 1230 | :where(input, select, textarea):not([type=checkbox], [type=radio])[aria-invalid=true] { 1231 | background-image: var(--icon-invalid); 1232 | } 1233 | :where(input, select, textarea)[aria-invalid=false] { 1234 | --border-color: var(--form-element-valid-border-color); 1235 | } 1236 | :where(input, select, textarea)[aria-invalid=false]:is(:active, :focus) { 1237 | --border-color: var(--form-element-valid-active-border-color) !important; 1238 | --box-shadow: 0 0 0 var(--outline-width) var(--form-element-valid-focus-color) !important; 1239 | } 1240 | :where(input, select, textarea)[aria-invalid=true] { 1241 | --border-color: var(--form-element-invalid-border-color); 1242 | } 1243 | :where(input, select, textarea)[aria-invalid=true]:is(:active, :focus) { 1244 | --border-color: var(--form-element-invalid-active-border-color) !important; 1245 | --box-shadow: 0 0 0 var(--outline-width) var(--form-element-invalid-focus-color) !important; 1246 | } 1247 | 1248 | [dir=rtl] :where(input, select, textarea):not([type=checkbox], [type=radio]):is([aria-invalid], [aria-invalid=true], [aria-invalid=false]) { 1249 | background-position: center left 0.75rem; 1250 | } 1251 | 1252 | input::placeholder, 1253 | input::-webkit-input-placeholder, 1254 | textarea::placeholder, 1255 | textarea::-webkit-input-placeholder, 1256 | select:invalid { 1257 | color: var(--form-element-placeholder-color); 1258 | opacity: 1; 1259 | } 1260 | 1261 | input:not([type=checkbox], [type=radio]), 1262 | select, 1263 | textarea { 1264 | margin-bottom: var(--spacing); 1265 | } 1266 | 1267 | select::-ms-expand { 1268 | border: 0; 1269 | background-color: transparent; 1270 | } 1271 | select:not([multiple], [size]) { 1272 | padding-right: calc(var(--form-element-spacing-horizontal) + 1.5rem); 1273 | padding-left: var(--form-element-spacing-horizontal); 1274 | -webkit-padding-start: var(--form-element-spacing-horizontal); 1275 | padding-inline-start: var(--form-element-spacing-horizontal); 1276 | -webkit-padding-end: calc(var(--form-element-spacing-horizontal) + 1.5rem); 1277 | padding-inline-end: calc(var(--form-element-spacing-horizontal) + 1.5rem); 1278 | background-image: var(--icon-chevron); 1279 | background-position: center right 0.75rem; 1280 | background-size: 1rem auto; 1281 | background-repeat: no-repeat; 1282 | } 1283 | 1284 | [dir=rtl] select:not([multiple], [size]) { 1285 | background-position: center left 0.75rem; 1286 | } 1287 | 1288 | :where(input, select, textarea) + small { 1289 | display: block; 1290 | width: 100%; 1291 | margin-top: calc(var(--spacing) * -0.75); 1292 | margin-bottom: var(--spacing); 1293 | color: var(--muted-color); 1294 | } 1295 | 1296 | label > :where(input, select, textarea) { 1297 | margin-top: calc(var(--spacing) * 0.25); 1298 | } 1299 | 1300 | /** 1301 | * Form elements 1302 | * Checkboxes & Radios 1303 | */ 1304 | [type=checkbox], 1305 | [type=radio] { 1306 | -webkit-appearance: none; 1307 | -moz-appearance: none; 1308 | appearance: none; 1309 | width: 1.25em; 1310 | height: 1.25em; 1311 | margin-top: -0.125em; 1312 | margin-right: 0.375em; 1313 | margin-left: 0; 1314 | -webkit-margin-start: 0; 1315 | margin-inline-start: 0; 1316 | -webkit-margin-end: 0.375em; 1317 | margin-inline-end: 0.375em; 1318 | border-width: var(--border-width); 1319 | font-size: inherit; 1320 | vertical-align: middle; 1321 | cursor: pointer; 1322 | } 1323 | [type=checkbox]::-ms-check, 1324 | [type=radio]::-ms-check { 1325 | display: none; 1326 | } 1327 | [type=checkbox]:checked, [type=checkbox]:checked:active, [type=checkbox]:checked:focus, 1328 | [type=radio]:checked, 1329 | [type=radio]:checked:active, 1330 | [type=radio]:checked:focus { 1331 | --background-color: var(--primary); 1332 | --border-color: var(--primary); 1333 | background-image: var(--icon-checkbox); 1334 | background-position: center; 1335 | background-size: 0.75em auto; 1336 | background-repeat: no-repeat; 1337 | } 1338 | [type=checkbox] ~ label, 1339 | [type=radio] ~ label { 1340 | display: inline-block; 1341 | margin-right: 0.375em; 1342 | margin-bottom: 0; 1343 | cursor: pointer; 1344 | } 1345 | 1346 | [type=checkbox]:indeterminate { 1347 | --background-color: var(--primary); 1348 | --border-color: var(--primary); 1349 | background-image: var(--icon-minus); 1350 | background-position: center; 1351 | background-size: 0.75em auto; 1352 | background-repeat: no-repeat; 1353 | } 1354 | 1355 | [type=radio] { 1356 | border-radius: 50%; 1357 | } 1358 | [type=radio]:checked, [type=radio]:checked:active, [type=radio]:checked:focus { 1359 | --background-color: var(--primary-inverse); 1360 | border-width: 0.35em; 1361 | background-image: none; 1362 | } 1363 | 1364 | [type=checkbox][role=switch] { 1365 | --background-color: var(--switch-background-color); 1366 | --border-color: var(--switch-background-color); 1367 | --color: var(--switch-color); 1368 | width: 2.25em; 1369 | height: 1.25em; 1370 | border: var(--border-width) solid var(--border-color); 1371 | border-radius: 1.25em; 1372 | background-color: var(--background-color); 1373 | line-height: 1.25em; 1374 | } 1375 | [type=checkbox][role=switch]:focus { 1376 | --background-color: var(--switch-background-color); 1377 | --border-color: var(--switch-background-color); 1378 | } 1379 | [type=checkbox][role=switch]:checked { 1380 | --background-color: var(--switch-checked-background-color); 1381 | --border-color: var(--switch-checked-background-color); 1382 | } 1383 | [type=checkbox][role=switch]:before { 1384 | display: block; 1385 | width: calc(1.25em - (var(--border-width) * 2)); 1386 | height: 100%; 1387 | border-radius: 50%; 1388 | background-color: var(--color); 1389 | content: ""; 1390 | transition: margin 0.1s ease-in-out; 1391 | } 1392 | [type=checkbox][role=switch]:checked { 1393 | background-image: none; 1394 | } 1395 | [type=checkbox][role=switch]:checked::before { 1396 | margin-left: calc(1.125em - var(--border-width)); 1397 | -webkit-margin-start: calc(1.125em - var(--border-width)); 1398 | margin-inline-start: calc(1.125em - var(--border-width)); 1399 | } 1400 | 1401 | [type=checkbox][aria-invalid=false], 1402 | [type=checkbox]:checked[aria-invalid=false], 1403 | [type=radio][aria-invalid=false], 1404 | [type=radio]:checked[aria-invalid=false], 1405 | [type=checkbox][role=switch][aria-invalid=false], 1406 | [type=checkbox][role=switch]:checked[aria-invalid=false] { 1407 | --border-color: var(--form-element-valid-border-color); 1408 | } 1409 | [type=checkbox][aria-invalid=true], 1410 | [type=checkbox]:checked[aria-invalid=true], 1411 | [type=radio][aria-invalid=true], 1412 | [type=radio]:checked[aria-invalid=true], 1413 | [type=checkbox][role=switch][aria-invalid=true], 1414 | [type=checkbox][role=switch]:checked[aria-invalid=true] { 1415 | --border-color: var(--form-element-invalid-border-color); 1416 | } 1417 | 1418 | /** 1419 | * Form elements 1420 | * Alternatives input types (Not Checkboxes & Radios) 1421 | */ 1422 | [type=color]::-webkit-color-swatch-wrapper { 1423 | padding: 0; 1424 | } 1425 | [type=color]::-moz-focus-inner { 1426 | padding: 0; 1427 | } 1428 | [type=color]::-webkit-color-swatch { 1429 | border: 0; 1430 | border-radius: calc(var(--border-radius) * 0.5); 1431 | } 1432 | [type=color]::-moz-color-swatch { 1433 | border: 0; 1434 | border-radius: calc(var(--border-radius) * 0.5); 1435 | } 1436 | 1437 | input:not([type=checkbox], [type=radio], [type=range], [type=file]):is([type=date], [type=datetime-local], [type=month], [type=time], [type=week]) { 1438 | --icon-position: 0.75rem; 1439 | --icon-width: 1rem; 1440 | padding-right: calc(var(--icon-width) + var(--icon-position)); 1441 | background-image: var(--icon-date); 1442 | background-position: center right var(--icon-position); 1443 | background-size: var(--icon-width) auto; 1444 | background-repeat: no-repeat; 1445 | } 1446 | input:not([type=checkbox], [type=radio], [type=range], [type=file])[type=time] { 1447 | background-image: var(--icon-time); 1448 | } 1449 | 1450 | [type=date]::-webkit-calendar-picker-indicator, 1451 | [type=datetime-local]::-webkit-calendar-picker-indicator, 1452 | [type=month]::-webkit-calendar-picker-indicator, 1453 | [type=time]::-webkit-calendar-picker-indicator, 1454 | [type=week]::-webkit-calendar-picker-indicator { 1455 | width: var(--icon-width); 1456 | margin-right: calc(var(--icon-width) * -1); 1457 | margin-left: var(--icon-position); 1458 | opacity: 0; 1459 | } 1460 | 1461 | [dir=rtl] :is([type=date], [type=datetime-local], [type=month], [type=time], [type=week]) { 1462 | text-align: right; 1463 | } 1464 | 1465 | [type=file] { 1466 | --color: var(--muted-color); 1467 | padding: calc(var(--form-element-spacing-vertical) * 0.5) 0; 1468 | border: 0; 1469 | border-radius: 0; 1470 | background: none; 1471 | } 1472 | [type=file]::-webkit-file-upload-button { 1473 | --background-color: var(--secondary); 1474 | --border-color: var(--secondary); 1475 | --color: var(--secondary-inverse); 1476 | margin-right: calc(var(--spacing) / 2); 1477 | margin-left: 0; 1478 | -webkit-margin-start: 0; 1479 | margin-inline-start: 0; 1480 | -webkit-margin-end: calc(var(--spacing) / 2); 1481 | margin-inline-end: calc(var(--spacing) / 2); 1482 | padding: calc(var(--form-element-spacing-vertical) * 0.5) calc(var(--form-element-spacing-horizontal) * 0.5); 1483 | border: var(--border-width) solid var(--border-color); 1484 | border-radius: var(--border-radius); 1485 | outline: none; 1486 | background-color: var(--background-color); 1487 | box-shadow: var(--box-shadow); 1488 | color: var(--color); 1489 | font-weight: var(--font-weight); 1490 | font-size: 1rem; 1491 | line-height: var(--line-height); 1492 | text-align: center; 1493 | cursor: pointer; 1494 | -webkit-transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition); 1495 | transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition); 1496 | } 1497 | [type=file]::file-selector-button { 1498 | --background-color: var(--secondary); 1499 | --border-color: var(--secondary); 1500 | --color: var(--secondary-inverse); 1501 | margin-right: calc(var(--spacing) / 2); 1502 | margin-left: 0; 1503 | -webkit-margin-start: 0; 1504 | margin-inline-start: 0; 1505 | -webkit-margin-end: calc(var(--spacing) / 2); 1506 | margin-inline-end: calc(var(--spacing) / 2); 1507 | padding: calc(var(--form-element-spacing-vertical) * 0.5) calc(var(--form-element-spacing-horizontal) * 0.5); 1508 | border: var(--border-width) solid var(--border-color); 1509 | border-radius: var(--border-radius); 1510 | outline: none; 1511 | background-color: var(--background-color); 1512 | box-shadow: var(--box-shadow); 1513 | color: var(--color); 1514 | font-weight: var(--font-weight); 1515 | font-size: 1rem; 1516 | line-height: var(--line-height); 1517 | text-align: center; 1518 | cursor: pointer; 1519 | transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition); 1520 | } 1521 | [type=file]::-webkit-file-upload-button:is(:hover, :active, :focus) { 1522 | --background-color: var(--secondary-hover); 1523 | --border-color: var(--secondary-hover); 1524 | } 1525 | [type=file]::file-selector-button:is(:hover, :active, :focus) { 1526 | --background-color: var(--secondary-hover); 1527 | --border-color: var(--secondary-hover); 1528 | } 1529 | [type=file]::-webkit-file-upload-button { 1530 | --background-color: var(--secondary); 1531 | --border-color: var(--secondary); 1532 | --color: var(--secondary-inverse); 1533 | margin-right: calc(var(--spacing) / 2); 1534 | margin-left: 0; 1535 | -webkit-margin-start: 0; 1536 | margin-inline-start: 0; 1537 | -webkit-margin-end: calc(var(--spacing) / 2); 1538 | margin-inline-end: calc(var(--spacing) / 2); 1539 | padding: calc(var(--form-element-spacing-vertical) * 0.5) calc(var(--form-element-spacing-horizontal) * 0.5); 1540 | border: var(--border-width) solid var(--border-color); 1541 | border-radius: var(--border-radius); 1542 | outline: none; 1543 | background-color: var(--background-color); 1544 | box-shadow: var(--box-shadow); 1545 | color: var(--color); 1546 | font-weight: var(--font-weight); 1547 | font-size: 1rem; 1548 | line-height: var(--line-height); 1549 | text-align: center; 1550 | cursor: pointer; 1551 | -webkit-transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition); 1552 | transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition); 1553 | } 1554 | [type=file]::-webkit-file-upload-button:is(:hover, :active, :focus) { 1555 | --background-color: var(--secondary-hover); 1556 | --border-color: var(--secondary-hover); 1557 | } 1558 | [type=file]::-ms-browse { 1559 | --background-color: var(--secondary); 1560 | --border-color: var(--secondary); 1561 | --color: var(--secondary-inverse); 1562 | margin-right: calc(var(--spacing) / 2); 1563 | margin-left: 0; 1564 | margin-inline-start: 0; 1565 | margin-inline-end: calc(var(--spacing) / 2); 1566 | padding: calc(var(--form-element-spacing-vertical) * 0.5) calc(var(--form-element-spacing-horizontal) * 0.5); 1567 | border: var(--border-width) solid var(--border-color); 1568 | border-radius: var(--border-radius); 1569 | outline: none; 1570 | background-color: var(--background-color); 1571 | box-shadow: var(--box-shadow); 1572 | color: var(--color); 1573 | font-weight: var(--font-weight); 1574 | font-size: 1rem; 1575 | line-height: var(--line-height); 1576 | text-align: center; 1577 | cursor: pointer; 1578 | -ms-transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition); 1579 | transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition); 1580 | } 1581 | [type=file]::-ms-browse:is(:hover, :active, :focus) { 1582 | --background-color: var(--secondary-hover); 1583 | --border-color: var(--secondary-hover); 1584 | } 1585 | 1586 | [type=range] { 1587 | -webkit-appearance: none; 1588 | -moz-appearance: none; 1589 | appearance: none; 1590 | width: 100%; 1591 | height: 1.25rem; 1592 | background: none; 1593 | } 1594 | [type=range]::-webkit-slider-runnable-track { 1595 | width: 100%; 1596 | height: 0.25rem; 1597 | border-radius: var(--border-radius); 1598 | background-color: var(--range-border-color); 1599 | -webkit-transition: background-color var(--transition), box-shadow var(--transition); 1600 | transition: background-color var(--transition), box-shadow var(--transition); 1601 | } 1602 | [type=range]::-moz-range-track { 1603 | width: 100%; 1604 | height: 0.25rem; 1605 | border-radius: var(--border-radius); 1606 | background-color: var(--range-border-color); 1607 | -moz-transition: background-color var(--transition), box-shadow var(--transition); 1608 | transition: background-color var(--transition), box-shadow var(--transition); 1609 | } 1610 | [type=range]::-ms-track { 1611 | width: 100%; 1612 | height: 0.25rem; 1613 | border-radius: var(--border-radius); 1614 | background-color: var(--range-border-color); 1615 | -ms-transition: background-color var(--transition), box-shadow var(--transition); 1616 | transition: background-color var(--transition), box-shadow var(--transition); 1617 | } 1618 | [type=range]::-webkit-slider-thumb { 1619 | -webkit-appearance: none; 1620 | width: 1.25rem; 1621 | height: 1.25rem; 1622 | margin-top: -0.5rem; 1623 | border: 2px solid var(--range-thumb-border-color); 1624 | border-radius: 50%; 1625 | background-color: var(--range-thumb-color); 1626 | cursor: pointer; 1627 | -webkit-transition: background-color var(--transition), transform var(--transition); 1628 | transition: background-color var(--transition), transform var(--transition); 1629 | } 1630 | [type=range]::-moz-range-thumb { 1631 | -webkit-appearance: none; 1632 | width: 1.25rem; 1633 | height: 1.25rem; 1634 | margin-top: -0.5rem; 1635 | border: 2px solid var(--range-thumb-border-color); 1636 | border-radius: 50%; 1637 | background-color: var(--range-thumb-color); 1638 | cursor: pointer; 1639 | -moz-transition: background-color var(--transition), transform var(--transition); 1640 | transition: background-color var(--transition), transform var(--transition); 1641 | } 1642 | [type=range]::-ms-thumb { 1643 | -webkit-appearance: none; 1644 | width: 1.25rem; 1645 | height: 1.25rem; 1646 | margin-top: -0.5rem; 1647 | border: 2px solid var(--range-thumb-border-color); 1648 | border-radius: 50%; 1649 | background-color: var(--range-thumb-color); 1650 | cursor: pointer; 1651 | -ms-transition: background-color var(--transition), transform var(--transition); 1652 | transition: background-color var(--transition), transform var(--transition); 1653 | } 1654 | [type=range]:hover, [type=range]:focus { 1655 | --range-border-color: var(--range-active-border-color); 1656 | --range-thumb-color: var(--range-thumb-hover-color); 1657 | } 1658 | [type=range]:active { 1659 | --range-thumb-color: var(--range-thumb-active-color); 1660 | } 1661 | [type=range]:active::-webkit-slider-thumb { 1662 | transform: scale(1.25); 1663 | } 1664 | [type=range]:active::-moz-range-thumb { 1665 | transform: scale(1.25); 1666 | } 1667 | [type=range]:active::-ms-thumb { 1668 | transform: scale(1.25); 1669 | } 1670 | 1671 | input:not([type=checkbox], [type=radio], [type=range], [type=file])[type=search] { 1672 | -webkit-padding-start: calc(var(--form-element-spacing-horizontal) + 1.75rem); 1673 | padding-inline-start: calc(var(--form-element-spacing-horizontal) + 1.75rem); 1674 | border-radius: 5rem; 1675 | background-image: var(--icon-search); 1676 | background-position: center left 1.125rem; 1677 | background-size: 1rem auto; 1678 | background-repeat: no-repeat; 1679 | } 1680 | input:not([type=checkbox], [type=radio], [type=range], [type=file])[type=search][aria-invalid] { 1681 | -webkit-padding-start: calc(var(--form-element-spacing-horizontal) + 1.75rem) !important; 1682 | padding-inline-start: calc(var(--form-element-spacing-horizontal) + 1.75rem) !important; 1683 | background-position: center left 1.125rem, center right 0.75rem; 1684 | } 1685 | input:not([type=checkbox], [type=radio], [type=range], [type=file])[type=search][aria-invalid=false] { 1686 | background-image: var(--icon-search), var(--icon-valid); 1687 | } 1688 | input:not([type=checkbox], [type=radio], [type=range], [type=file])[type=search][aria-invalid=true] { 1689 | background-image: var(--icon-search), var(--icon-invalid); 1690 | } 1691 | 1692 | [type=search]::-webkit-search-cancel-button { 1693 | -webkit-appearance: none; 1694 | display: none; 1695 | } 1696 | 1697 | [dir=rtl] :where(input):not([type=checkbox], [type=radio], [type=range], [type=file])[type=search] { 1698 | background-position: center right 1.125rem; 1699 | } 1700 | [dir=rtl] :where(input):not([type=checkbox], [type=radio], [type=range], [type=file])[type=search][aria-invalid] { 1701 | background-position: center right 1.125rem, center left 0.75rem; 1702 | } 1703 | 1704 | /** 1705 | * Table 1706 | */ 1707 | :where(table) { 1708 | width: 100%; 1709 | border-collapse: collapse; 1710 | border-spacing: 0; 1711 | text-indent: 0; 1712 | } 1713 | 1714 | th, 1715 | td { 1716 | padding: calc(var(--spacing) / 2) var(--spacing); 1717 | border-bottom: var(--border-width) solid var(--table-border-color); 1718 | color: var(--color); 1719 | font-weight: var(--font-weight); 1720 | font-size: var(--font-size); 1721 | text-align: left; 1722 | text-align: start; 1723 | } 1724 | 1725 | tfoot th, 1726 | tfoot td { 1727 | border-top: var(--border-width) solid var(--table-border-color); 1728 | border-bottom: 0; 1729 | } 1730 | 1731 | table[role=grid] tbody tr:nth-child(odd) { 1732 | background-color: var(--table-row-stripped-background-color); 1733 | } 1734 | 1735 | /** 1736 | * Code 1737 | */ 1738 | pre, 1739 | code, 1740 | kbd, 1741 | samp { 1742 | font-size: 0.875em; 1743 | font-family: var(--font-family); 1744 | } 1745 | 1746 | pre { 1747 | -ms-overflow-style: scrollbar; 1748 | overflow: auto; 1749 | } 1750 | 1751 | pre, 1752 | code, 1753 | kbd { 1754 | border-radius: var(--border-radius); 1755 | background: var(--code-background-color); 1756 | color: var(--code-color); 1757 | font-weight: var(--font-weight); 1758 | line-height: initial; 1759 | } 1760 | 1761 | code, 1762 | kbd { 1763 | display: inline-block; 1764 | padding: 0.375rem 0.5rem; 1765 | } 1766 | 1767 | pre { 1768 | display: block; 1769 | margin-bottom: var(--spacing); 1770 | overflow-x: auto; 1771 | } 1772 | pre > code { 1773 | display: block; 1774 | padding: var(--spacing); 1775 | background: none; 1776 | font-size: 14px; 1777 | line-height: var(--line-height); 1778 | } 1779 | 1780 | code b { 1781 | color: var(--code-tag-color); 1782 | font-weight: var(--font-weight); 1783 | } 1784 | code i { 1785 | color: var(--code-property-color); 1786 | font-style: normal; 1787 | } 1788 | code u { 1789 | color: var(--code-value-color); 1790 | text-decoration: none; 1791 | } 1792 | code em { 1793 | color: var(--code-comment-color); 1794 | font-style: normal; 1795 | } 1796 | 1797 | kbd { 1798 | background-color: var(--code-kbd-background-color); 1799 | color: var(--code-kbd-color); 1800 | vertical-align: baseline; 1801 | } 1802 | 1803 | /** 1804 | * Miscs 1805 | */ 1806 | hr { 1807 | height: 0; 1808 | border: 0; 1809 | border-top: 1px solid var(--muted-border-color); 1810 | color: inherit; 1811 | } 1812 | 1813 | [hidden], 1814 | template { 1815 | display: none !important; 1816 | } 1817 | 1818 | canvas { 1819 | display: inline-block; 1820 | } 1821 | 1822 | /** 1823 | * Accordion (
) 1824 | */ 1825 | details { 1826 | display: block; 1827 | margin-bottom: var(--spacing); 1828 | padding-bottom: var(--spacing); 1829 | border-bottom: var(--border-width) solid var(--accordion-border-color); 1830 | } 1831 | details summary { 1832 | line-height: 1rem; 1833 | list-style-type: none; 1834 | cursor: pointer; 1835 | transition: color var(--transition); 1836 | } 1837 | details summary:not([role]) { 1838 | color: var(--accordion-close-summary-color); 1839 | } 1840 | details summary::-webkit-details-marker { 1841 | display: none; 1842 | } 1843 | details summary::marker { 1844 | display: none; 1845 | } 1846 | details summary::-moz-list-bullet { 1847 | list-style-type: none; 1848 | } 1849 | details summary::after { 1850 | display: block; 1851 | width: 1rem; 1852 | height: 1rem; 1853 | -webkit-margin-start: calc(var(--spacing, 1rem) * 0.5); 1854 | margin-inline-start: calc(var(--spacing, 1rem) * 0.5); 1855 | float: right; 1856 | transform: rotate(-90deg); 1857 | background-image: var(--icon-chevron); 1858 | background-position: right center; 1859 | background-size: 1rem auto; 1860 | background-repeat: no-repeat; 1861 | content: ""; 1862 | transition: transform var(--transition); 1863 | } 1864 | details summary:focus { 1865 | outline: none; 1866 | } 1867 | details summary:focus:not([role=button]) { 1868 | color: var(--accordion-active-summary-color); 1869 | } 1870 | details summary[role=button] { 1871 | width: 100%; 1872 | text-align: left; 1873 | } 1874 | details summary[role=button]::after { 1875 | height: calc(1rem * var(--line-height, 1.5)); 1876 | background-image: var(--icon-chevron-button); 1877 | } 1878 | details summary[role=button]:not(.outline).contrast::after { 1879 | background-image: var(--icon-chevron-button-inverse); 1880 | } 1881 | details[open] > summary { 1882 | margin-bottom: calc(var(--spacing)); 1883 | } 1884 | details[open] > summary:not([role]):not(:focus) { 1885 | color: var(--accordion-open-summary-color); 1886 | } 1887 | details[open] > summary::after { 1888 | transform: rotate(0); 1889 | } 1890 | 1891 | [dir=rtl] details summary { 1892 | text-align: right; 1893 | } 1894 | [dir=rtl] details summary::after { 1895 | float: left; 1896 | background-position: left center; 1897 | } 1898 | 1899 | /** 1900 | * Card (
) 1901 | */ 1902 | article { 1903 | margin: var(--block-spacing-vertical) 0; 1904 | padding: var(--block-spacing-vertical) var(--block-spacing-horizontal); 1905 | border-radius: var(--border-radius); 1906 | background: var(--card-background-color); 1907 | box-shadow: var(--card-box-shadow); 1908 | } 1909 | article > header, 1910 | article > footer { 1911 | margin-right: calc(var(--block-spacing-horizontal) * -1); 1912 | margin-left: calc(var(--block-spacing-horizontal) * -1); 1913 | padding: calc(var(--block-spacing-vertical) * 0.66) var(--block-spacing-horizontal); 1914 | background-color: var(--card-sectionning-background-color); 1915 | } 1916 | article > header { 1917 | margin-top: calc(var(--block-spacing-vertical) * -1); 1918 | margin-bottom: var(--block-spacing-vertical); 1919 | border-bottom: var(--border-width) solid var(--card-border-color); 1920 | border-top-right-radius: var(--border-radius); 1921 | border-top-left-radius: var(--border-radius); 1922 | } 1923 | article > footer { 1924 | margin-top: var(--block-spacing-vertical); 1925 | margin-bottom: calc(var(--block-spacing-vertical) * -1); 1926 | border-top: var(--border-width) solid var(--card-border-color); 1927 | border-bottom-right-radius: var(--border-radius); 1928 | border-bottom-left-radius: var(--border-radius); 1929 | } 1930 | 1931 | /** 1932 | * Modal () 1933 | */ 1934 | :root { 1935 | --scrollbar-width: 0px; 1936 | } 1937 | 1938 | dialog { 1939 | display: flex; 1940 | z-index: 999; 1941 | position: fixed; 1942 | top: 0; 1943 | right: 0; 1944 | bottom: 0; 1945 | left: 0; 1946 | align-items: center; 1947 | justify-content: center; 1948 | width: inherit; 1949 | min-width: 100%; 1950 | height: inherit; 1951 | min-height: 100%; 1952 | padding: var(--spacing); 1953 | border: 0; 1954 | -webkit-backdrop-filter: var(--modal-overlay-backdrop-filter); 1955 | backdrop-filter: var(--modal-overlay-backdrop-filter); 1956 | background-color: var(--modal-overlay-background-color); 1957 | color: var(--color); 1958 | } 1959 | dialog article { 1960 | max-height: calc(100vh - var(--spacing) * 2); 1961 | overflow: auto; 1962 | } 1963 | @media (min-width: 576px) { 1964 | dialog article { 1965 | max-width: 510px; 1966 | } 1967 | } 1968 | @media (min-width: 768px) { 1969 | dialog article { 1970 | max-width: 700px; 1971 | } 1972 | } 1973 | dialog article > header, 1974 | dialog article > footer { 1975 | padding: calc(var(--block-spacing-vertical) * 0.5) var(--block-spacing-horizontal); 1976 | } 1977 | dialog article > header .close { 1978 | margin: 0; 1979 | margin-left: var(--spacing); 1980 | float: right; 1981 | } 1982 | dialog article > footer { 1983 | text-align: right; 1984 | } 1985 | dialog article > footer [role=button] { 1986 | margin-bottom: 0; 1987 | } 1988 | dialog article > footer [role=button]:not(:first-of-type) { 1989 | margin-left: calc(var(--spacing) * 0.5); 1990 | } 1991 | dialog article p:last-of-type { 1992 | margin: 0; 1993 | } 1994 | dialog article .close { 1995 | display: block; 1996 | width: 1rem; 1997 | height: 1rem; 1998 | margin-top: calc(var(--block-spacing-vertical) * -0.5); 1999 | margin-bottom: var(--typography-spacing-vertical); 2000 | margin-left: auto; 2001 | background-image: var(--icon-close); 2002 | background-position: center; 2003 | background-size: auto 1rem; 2004 | background-repeat: no-repeat; 2005 | opacity: 0.5; 2006 | transition: opacity var(--transition); 2007 | } 2008 | dialog article .close:is([aria-current], :hover, :active, :focus) { 2009 | opacity: 1; 2010 | } 2011 | dialog:not([open]), dialog[open=false] { 2012 | display: none; 2013 | } 2014 | 2015 | .modal-is-open { 2016 | padding-right: var(--scrollbar-width, 0px); 2017 | overflow: hidden; 2018 | pointer-events: none; 2019 | } 2020 | .modal-is-open dialog { 2021 | pointer-events: auto; 2022 | } 2023 | 2024 | :where(.modal-is-opening, .modal-is-closing) dialog, 2025 | :where(.modal-is-opening, .modal-is-closing) dialog > article { 2026 | -webkit-animation-duration: 0.2s; 2027 | animation-duration: 0.2s; 2028 | -webkit-animation-timing-function: ease-in-out; 2029 | animation-timing-function: ease-in-out; 2030 | -webkit-animation-fill-mode: both; 2031 | animation-fill-mode: both; 2032 | } 2033 | :where(.modal-is-opening, .modal-is-closing) dialog { 2034 | -webkit-animation-duration: 0.8s; 2035 | animation-duration: 0.8s; 2036 | -webkit-animation-name: modal-overlay; 2037 | animation-name: modal-overlay; 2038 | } 2039 | :where(.modal-is-opening, .modal-is-closing) dialog > article { 2040 | -webkit-animation-delay: 0.2s; 2041 | animation-delay: 0.2s; 2042 | -webkit-animation-name: modal; 2043 | animation-name: modal; 2044 | } 2045 | 2046 | .modal-is-closing dialog, 2047 | .modal-is-closing dialog > article { 2048 | -webkit-animation-delay: 0s; 2049 | animation-delay: 0s; 2050 | animation-direction: reverse; 2051 | } 2052 | 2053 | @-webkit-keyframes modal-overlay { 2054 | from { 2055 | -webkit-backdrop-filter: none; 2056 | backdrop-filter: none; 2057 | background-color: transparent; 2058 | } 2059 | } 2060 | 2061 | @keyframes modal-overlay { 2062 | from { 2063 | -webkit-backdrop-filter: none; 2064 | backdrop-filter: none; 2065 | background-color: transparent; 2066 | } 2067 | } 2068 | @-webkit-keyframes modal { 2069 | from { 2070 | transform: translateY(-100%); 2071 | opacity: 0; 2072 | } 2073 | } 2074 | @keyframes modal { 2075 | from { 2076 | transform: translateY(-100%); 2077 | opacity: 0; 2078 | } 2079 | } 2080 | /** 2081 | * Nav 2082 | */ 2083 | :where(nav li)::before { 2084 | float: left; 2085 | content: "​"; 2086 | } 2087 | 2088 | nav, 2089 | nav ul { 2090 | display: flex; 2091 | } 2092 | 2093 | nav { 2094 | justify-content: space-between; 2095 | } 2096 | nav ol, 2097 | nav ul { 2098 | align-items: center; 2099 | margin-bottom: 0; 2100 | padding: 0; 2101 | list-style: none; 2102 | } 2103 | nav ol:first-of-type, 2104 | nav ul:first-of-type { 2105 | margin-left: calc(var(--nav-element-spacing-horizontal) * -1); 2106 | } 2107 | nav ol:last-of-type, 2108 | nav ul:last-of-type { 2109 | margin-right: calc(var(--nav-element-spacing-horizontal) * -1); 2110 | } 2111 | nav li { 2112 | display: inline-block; 2113 | margin: 0; 2114 | padding: var(--nav-element-spacing-vertical) var(--nav-element-spacing-horizontal); 2115 | } 2116 | nav li > * { 2117 | --spacing: 0; 2118 | } 2119 | nav :where(a, [role=link]) { 2120 | display: inline-block; 2121 | margin: calc(var(--nav-link-spacing-vertical) * -1) calc(var(--nav-link-spacing-horizontal) * -1); 2122 | padding: var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal); 2123 | border-radius: var(--border-radius); 2124 | text-decoration: none; 2125 | } 2126 | nav :where(a, [role=link]):is([aria-current], :hover, :active, :focus) { 2127 | text-decoration: none; 2128 | } 2129 | nav[aria-label=breadcrumb] { 2130 | align-items: center; 2131 | justify-content: start; 2132 | } 2133 | nav[aria-label=breadcrumb] ul li:not(:first-child) { 2134 | -webkit-margin-start: var(--nav-link-spacing-horizontal); 2135 | margin-inline-start: var(--nav-link-spacing-horizontal); 2136 | } 2137 | nav[aria-label=breadcrumb] ul li:not(:last-child) ::after { 2138 | position: absolute; 2139 | width: calc(var(--nav-link-spacing-horizontal) * 2); 2140 | -webkit-margin-start: calc(var(--nav-link-spacing-horizontal) / 2); 2141 | margin-inline-start: calc(var(--nav-link-spacing-horizontal) / 2); 2142 | content: "/"; 2143 | color: var(--muted-color); 2144 | text-align: center; 2145 | } 2146 | nav[aria-label=breadcrumb] a[aria-current] { 2147 | background-color: transparent; 2148 | color: inherit; 2149 | text-decoration: none; 2150 | pointer-events: none; 2151 | } 2152 | nav [role=button] { 2153 | margin-right: inherit; 2154 | margin-left: inherit; 2155 | padding: var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal); 2156 | } 2157 | 2158 | aside nav, 2159 | aside ol, 2160 | aside ul, 2161 | aside li { 2162 | display: block; 2163 | } 2164 | aside li { 2165 | padding: calc(var(--nav-element-spacing-vertical) * 0.5) var(--nav-element-spacing-horizontal); 2166 | } 2167 | aside li a { 2168 | display: block; 2169 | } 2170 | aside li [role=button] { 2171 | margin: inherit; 2172 | } 2173 | 2174 | [dir=rtl] nav[aria-label=breadcrumb] ul li:not(:last-child) ::after { 2175 | content: "\\"; 2176 | } 2177 | 2178 | /** 2179 | * Progress 2180 | */ 2181 | progress { 2182 | display: inline-block; 2183 | vertical-align: baseline; 2184 | } 2185 | 2186 | progress { 2187 | -webkit-appearance: none; 2188 | -moz-appearance: none; 2189 | display: inline-block; 2190 | appearance: none; 2191 | width: 100%; 2192 | height: 0.5rem; 2193 | margin-bottom: calc(var(--spacing) * 0.5); 2194 | overflow: hidden; 2195 | border: 0; 2196 | border-radius: var(--border-radius); 2197 | background-color: var(--progress-background-color); 2198 | color: var(--progress-color); 2199 | } 2200 | progress::-webkit-progress-bar { 2201 | border-radius: var(--border-radius); 2202 | background: none; 2203 | } 2204 | progress[value]::-webkit-progress-value { 2205 | background-color: var(--progress-color); 2206 | } 2207 | progress::-moz-progress-bar { 2208 | background-color: var(--progress-color); 2209 | } 2210 | @media (prefers-reduced-motion: no-preference) { 2211 | progress:indeterminate { 2212 | background: var(--progress-background-color) linear-gradient(to right, var(--progress-color) 30%, var(--progress-background-color) 30%) top left/150% 150% no-repeat; 2213 | -webkit-animation: progress-indeterminate 1s linear infinite; 2214 | animation: progress-indeterminate 1s linear infinite; 2215 | } 2216 | progress:indeterminate[value]::-webkit-progress-value { 2217 | background-color: transparent; 2218 | } 2219 | progress:indeterminate::-moz-progress-bar { 2220 | background-color: transparent; 2221 | } 2222 | } 2223 | 2224 | @media (prefers-reduced-motion: no-preference) { 2225 | [dir=rtl] progress:indeterminate { 2226 | animation-direction: reverse; 2227 | } 2228 | } 2229 | 2230 | @-webkit-keyframes progress-indeterminate { 2231 | 0% { 2232 | background-position: 200% 0; 2233 | } 2234 | 100% { 2235 | background-position: -200% 0; 2236 | } 2237 | } 2238 | 2239 | @keyframes progress-indeterminate { 2240 | 0% { 2241 | background-position: 200% 0; 2242 | } 2243 | 100% { 2244 | background-position: -200% 0; 2245 | } 2246 | } 2247 | /** 2248 | * Dropdown ([role="list"]) 2249 | */ 2250 | details[role=list], 2251 | li[role=list] { 2252 | position: relative; 2253 | } 2254 | 2255 | details[role=list] summary + ul, 2256 | li[role=list] > ul { 2257 | display: flex; 2258 | z-index: 99; 2259 | position: absolute; 2260 | top: auto; 2261 | right: 0; 2262 | left: 0; 2263 | flex-direction: column; 2264 | margin: 0; 2265 | padding: 0; 2266 | border: var(--border-width) solid var(--dropdown-border-color); 2267 | border-radius: var(--border-radius); 2268 | border-top-right-radius: 0; 2269 | border-top-left-radius: 0; 2270 | background-color: var(--dropdown-background-color); 2271 | box-shadow: var(--card-box-shadow); 2272 | color: var(--dropdown-color); 2273 | white-space: nowrap; 2274 | } 2275 | details[role=list] summary + ul li, 2276 | li[role=list] > ul li { 2277 | width: 100%; 2278 | margin-bottom: 0; 2279 | padding: calc(var(--form-element-spacing-vertical) * 0.5) var(--form-element-spacing-horizontal); 2280 | list-style: none; 2281 | } 2282 | details[role=list] summary + ul li:first-of-type, 2283 | li[role=list] > ul li:first-of-type { 2284 | margin-top: calc(var(--form-element-spacing-vertical) * 0.5); 2285 | } 2286 | details[role=list] summary + ul li:last-of-type, 2287 | li[role=list] > ul li:last-of-type { 2288 | margin-bottom: calc(var(--form-element-spacing-vertical) * 0.5); 2289 | } 2290 | details[role=list] summary + ul li a, 2291 | li[role=list] > ul li a { 2292 | display: block; 2293 | margin: calc(var(--form-element-spacing-vertical) * -0.5) calc(var(--form-element-spacing-horizontal) * -1); 2294 | padding: calc(var(--form-element-spacing-vertical) * 0.5) var(--form-element-spacing-horizontal); 2295 | overflow: hidden; 2296 | color: var(--dropdown-color); 2297 | text-decoration: none; 2298 | text-overflow: ellipsis; 2299 | } 2300 | details[role=list] summary + ul li a:hover, 2301 | li[role=list] > ul li a:hover { 2302 | background-color: var(--dropdown-hover-background-color); 2303 | } 2304 | 2305 | details[role=list] summary::after, 2306 | li[role=list] > a::after { 2307 | display: block; 2308 | width: 1rem; 2309 | height: calc(1rem * var(--line-height, 1.5)); 2310 | -webkit-margin-start: 0.5rem; 2311 | margin-inline-start: 0.5rem; 2312 | float: right; 2313 | transform: rotate(0deg); 2314 | background-position: right center; 2315 | background-size: 1rem auto; 2316 | background-repeat: no-repeat; 2317 | content: ""; 2318 | } 2319 | 2320 | details[role=list] { 2321 | padding: 0; 2322 | border-bottom: none; 2323 | } 2324 | details[role=list] summary { 2325 | margin-bottom: 0; 2326 | } 2327 | details[role=list] summary:not([role]) { 2328 | height: calc(1rem * var(--line-height) + var(--form-element-spacing-vertical) * 2 + var(--border-width) * 2); 2329 | padding: var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal); 2330 | border: var(--border-width) solid var(--form-element-border-color); 2331 | border-radius: var(--border-radius); 2332 | background-color: var(--form-element-background-color); 2333 | color: var(--form-element-placeholder-color); 2334 | line-height: inherit; 2335 | cursor: pointer; 2336 | transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition); 2337 | } 2338 | details[role=list] summary:not([role]):active, details[role=list] summary:not([role]):focus { 2339 | border-color: var(--form-element-active-border-color); 2340 | background-color: var(--form-element-active-background-color); 2341 | } 2342 | details[role=list] summary:not([role]):focus { 2343 | box-shadow: 0 0 0 var(--outline-width) var(--form-element-focus-color); 2344 | } 2345 | details[role=list][open] summary { 2346 | border-bottom-right-radius: 0; 2347 | border-bottom-left-radius: 0; 2348 | } 2349 | details[role=list][open] summary::before { 2350 | display: block; 2351 | z-index: 1; 2352 | position: fixed; 2353 | top: 0; 2354 | right: 0; 2355 | bottom: 0; 2356 | left: 0; 2357 | background: none; 2358 | content: ""; 2359 | cursor: default; 2360 | } 2361 | 2362 | nav details[role=list] summary, 2363 | nav li[role=list] a { 2364 | display: flex; 2365 | direction: ltr; 2366 | } 2367 | 2368 | nav details[role=list] summary + ul, 2369 | nav li[role=list] > ul { 2370 | min-width: -webkit-fit-content; 2371 | min-width: -moz-fit-content; 2372 | min-width: fit-content; 2373 | border-radius: var(--border-radius); 2374 | } 2375 | nav details[role=list] summary + ul li a, 2376 | nav li[role=list] > ul li a { 2377 | border-radius: 0; 2378 | } 2379 | 2380 | nav details[role=list] summary, 2381 | nav details[role=list] summary:not([role]) { 2382 | height: auto; 2383 | padding: var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal); 2384 | } 2385 | nav details[role=list][open] summary { 2386 | border-radius: var(--border-radius); 2387 | } 2388 | nav details[role=list] summary + ul { 2389 | margin-top: var(--outline-width); 2390 | -webkit-margin-start: 0; 2391 | margin-inline-start: 0; 2392 | } 2393 | nav details[role=list] summary[role=link] { 2394 | margin-bottom: calc(var(--nav-link-spacing-vertical) * -1); 2395 | line-height: var(--line-height); 2396 | } 2397 | nav details[role=list] summary[role=link] + ul { 2398 | margin-top: calc(var(--nav-link-spacing-vertical) + var(--outline-width)); 2399 | -webkit-margin-start: calc(var(--nav-link-spacing-horizontal) * -1); 2400 | margin-inline-start: calc(var(--nav-link-spacing-horizontal) * -1); 2401 | } 2402 | 2403 | li[role=list]:hover > ul, 2404 | li[role=list] a:active ~ ul, 2405 | li[role=list] a:focus ~ ul { 2406 | display: flex; 2407 | } 2408 | li[role=list] > ul { 2409 | display: none; 2410 | margin-top: calc(var(--nav-link-spacing-vertical) + var(--outline-width)); 2411 | -webkit-margin-start: calc(var(--nav-element-spacing-horizontal) - var(--nav-link-spacing-horizontal)); 2412 | margin-inline-start: calc(var(--nav-element-spacing-horizontal) - var(--nav-link-spacing-horizontal)); 2413 | } 2414 | li[role=list] > a::after { 2415 | background-image: var(--icon-chevron); 2416 | } 2417 | 2418 | /** 2419 | * Loading ([aria-busy=true]) 2420 | */ 2421 | [aria-busy=true] { 2422 | cursor: progress; 2423 | } 2424 | 2425 | [aria-busy=true]:not(input, select, textarea)::before { 2426 | display: inline-block; 2427 | width: 1em; 2428 | height: 1em; 2429 | border: 0.1875em solid currentColor; 2430 | border-radius: 1em; 2431 | border-right-color: transparent; 2432 | content: ""; 2433 | vertical-align: text-bottom; 2434 | vertical-align: -0.125em; 2435 | -webkit-animation: spinner 0.75s linear infinite; 2436 | animation: spinner 0.75s linear infinite; 2437 | opacity: var(--loading-spinner-opacity); 2438 | } 2439 | [aria-busy=true]:not(input, select, textarea):not(:empty)::before { 2440 | margin-right: calc(var(--spacing) * 0.5); 2441 | margin-left: 0; 2442 | -webkit-margin-start: 0; 2443 | margin-inline-start: 0; 2444 | -webkit-margin-end: calc(var(--spacing) * 0.5); 2445 | margin-inline-end: calc(var(--spacing) * 0.5); 2446 | } 2447 | [aria-busy=true]:not(input, select, textarea):empty { 2448 | text-align: center; 2449 | } 2450 | 2451 | button[aria-busy=true], 2452 | input[type=submit][aria-busy=true], 2453 | input[type=button][aria-busy=true], 2454 | input[type=reset][aria-busy=true], 2455 | a[aria-busy=true] { 2456 | pointer-events: none; 2457 | } 2458 | 2459 | @-webkit-keyframes spinner { 2460 | to { 2461 | transform: rotate(360deg); 2462 | } 2463 | } 2464 | 2465 | @keyframes spinner { 2466 | to { 2467 | transform: rotate(360deg); 2468 | } 2469 | } 2470 | /** 2471 | * Tooltip ([data-tooltip]) 2472 | */ 2473 | [data-tooltip] { 2474 | position: relative; 2475 | } 2476 | [data-tooltip]:not(a, button, input) { 2477 | border-bottom: 1px dotted; 2478 | text-decoration: none; 2479 | cursor: help; 2480 | } 2481 | [data-tooltip][data-placement=top]::before, [data-tooltip][data-placement=top]::after, [data-tooltip]::before, [data-tooltip]::after { 2482 | display: block; 2483 | z-index: 99; 2484 | position: absolute; 2485 | bottom: 100%; 2486 | left: 50%; 2487 | padding: 0.25rem 0.5rem; 2488 | overflow: hidden; 2489 | transform: translate(-50%, -0.25rem); 2490 | border-radius: var(--border-radius); 2491 | background: var(--tooltip-background-color); 2492 | content: attr(data-tooltip); 2493 | color: var(--tooltip-color); 2494 | font-style: normal; 2495 | font-weight: var(--font-weight); 2496 | font-size: 0.875rem; 2497 | text-decoration: none; 2498 | text-overflow: ellipsis; 2499 | white-space: nowrap; 2500 | opacity: 0; 2501 | pointer-events: none; 2502 | } 2503 | [data-tooltip][data-placement=top]::after, [data-tooltip]::after { 2504 | padding: 0; 2505 | transform: translate(-50%, 0rem); 2506 | border-top: 0.3rem solid; 2507 | border-right: 0.3rem solid transparent; 2508 | border-left: 0.3rem solid transparent; 2509 | border-radius: 0; 2510 | background-color: transparent; 2511 | content: ""; 2512 | color: var(--tooltip-background-color); 2513 | } 2514 | [data-tooltip][data-placement=bottom]::before, [data-tooltip][data-placement=bottom]::after { 2515 | top: 100%; 2516 | bottom: auto; 2517 | transform: translate(-50%, 0.25rem); 2518 | } 2519 | [data-tooltip][data-placement=bottom]:after { 2520 | transform: translate(-50%, -0.3rem); 2521 | border: 0.3rem solid transparent; 2522 | border-bottom: 0.3rem solid; 2523 | } 2524 | [data-tooltip][data-placement=left]::before, [data-tooltip][data-placement=left]::after { 2525 | top: 50%; 2526 | right: 100%; 2527 | bottom: auto; 2528 | left: auto; 2529 | transform: translate(-0.25rem, -50%); 2530 | } 2531 | [data-tooltip][data-placement=left]:after { 2532 | transform: translate(0.3rem, -50%); 2533 | border: 0.3rem solid transparent; 2534 | border-left: 0.3rem solid; 2535 | } 2536 | [data-tooltip][data-placement=right]::before, [data-tooltip][data-placement=right]::after { 2537 | top: 50%; 2538 | right: auto; 2539 | bottom: auto; 2540 | left: 100%; 2541 | transform: translate(0.25rem, -50%); 2542 | } 2543 | [data-tooltip][data-placement=right]:after { 2544 | transform: translate(-0.3rem, -50%); 2545 | border: 0.3rem solid transparent; 2546 | border-right: 0.3rem solid; 2547 | } 2548 | [data-tooltip]:focus::before, [data-tooltip]:focus::after, [data-tooltip]:hover::before, [data-tooltip]:hover::after { 2549 | opacity: 1; 2550 | } 2551 | @media (hover: hover) and (pointer: fine) { 2552 | [data-tooltip][data-placement=bottom]:focus::before, [data-tooltip][data-placement=bottom]:focus::after, [data-tooltip][data-placement=bottom]:hover [data-tooltip]:focus::before, [data-tooltip][data-placement=bottom]:hover [data-tooltip]:focus::after, [data-tooltip]:hover::before, [data-tooltip]:hover::after { 2553 | -webkit-animation-duration: 0.2s; 2554 | animation-duration: 0.2s; 2555 | -webkit-animation-name: tooltip-slide-top; 2556 | animation-name: tooltip-slide-top; 2557 | } 2558 | [data-tooltip][data-placement=bottom]:focus::after, [data-tooltip][data-placement=bottom]:hover [data-tooltip]:focus::after, [data-tooltip]:hover::after { 2559 | -webkit-animation-name: tooltip-caret-slide-top; 2560 | animation-name: tooltip-caret-slide-top; 2561 | } 2562 | [data-tooltip][data-placement=bottom]:focus::before, [data-tooltip][data-placement=bottom]:focus::after, [data-tooltip][data-placement=bottom]:hover::before, [data-tooltip][data-placement=bottom]:hover::after { 2563 | -webkit-animation-duration: 0.2s; 2564 | animation-duration: 0.2s; 2565 | -webkit-animation-name: tooltip-slide-bottom; 2566 | animation-name: tooltip-slide-bottom; 2567 | } 2568 | [data-tooltip][data-placement=bottom]:focus::after, [data-tooltip][data-placement=bottom]:hover::after { 2569 | -webkit-animation-name: tooltip-caret-slide-bottom; 2570 | animation-name: tooltip-caret-slide-bottom; 2571 | } 2572 | [data-tooltip][data-placement=left]:focus::before, [data-tooltip][data-placement=left]:focus::after, [data-tooltip][data-placement=left]:hover::before, [data-tooltip][data-placement=left]:hover::after { 2573 | -webkit-animation-duration: 0.2s; 2574 | animation-duration: 0.2s; 2575 | -webkit-animation-name: tooltip-slide-left; 2576 | animation-name: tooltip-slide-left; 2577 | } 2578 | [data-tooltip][data-placement=left]:focus::after, [data-tooltip][data-placement=left]:hover::after { 2579 | -webkit-animation-name: tooltip-caret-slide-left; 2580 | animation-name: tooltip-caret-slide-left; 2581 | } 2582 | [data-tooltip][data-placement=right]:focus::before, [data-tooltip][data-placement=right]:focus::after, [data-tooltip][data-placement=right]:hover::before, [data-tooltip][data-placement=right]:hover::after { 2583 | -webkit-animation-duration: 0.2s; 2584 | animation-duration: 0.2s; 2585 | -webkit-animation-name: tooltip-slide-right; 2586 | animation-name: tooltip-slide-right; 2587 | } 2588 | [data-tooltip][data-placement=right]:focus::after, [data-tooltip][data-placement=right]:hover::after { 2589 | -webkit-animation-name: tooltip-caret-slide-right; 2590 | animation-name: tooltip-caret-slide-right; 2591 | } 2592 | } 2593 | @-webkit-keyframes tooltip-slide-top { 2594 | from { 2595 | transform: translate(-50%, 0.75rem); 2596 | opacity: 0; 2597 | } 2598 | to { 2599 | transform: translate(-50%, -0.25rem); 2600 | opacity: 1; 2601 | } 2602 | } 2603 | @keyframes tooltip-slide-top { 2604 | from { 2605 | transform: translate(-50%, 0.75rem); 2606 | opacity: 0; 2607 | } 2608 | to { 2609 | transform: translate(-50%, -0.25rem); 2610 | opacity: 1; 2611 | } 2612 | } 2613 | @-webkit-keyframes tooltip-caret-slide-top { 2614 | from { 2615 | opacity: 0; 2616 | } 2617 | 50% { 2618 | transform: translate(-50%, -0.25rem); 2619 | opacity: 0; 2620 | } 2621 | to { 2622 | transform: translate(-50%, 0rem); 2623 | opacity: 1; 2624 | } 2625 | } 2626 | @keyframes tooltip-caret-slide-top { 2627 | from { 2628 | opacity: 0; 2629 | } 2630 | 50% { 2631 | transform: translate(-50%, -0.25rem); 2632 | opacity: 0; 2633 | } 2634 | to { 2635 | transform: translate(-50%, 0rem); 2636 | opacity: 1; 2637 | } 2638 | } 2639 | @-webkit-keyframes tooltip-slide-bottom { 2640 | from { 2641 | transform: translate(-50%, -0.75rem); 2642 | opacity: 0; 2643 | } 2644 | to { 2645 | transform: translate(-50%, 0.25rem); 2646 | opacity: 1; 2647 | } 2648 | } 2649 | @keyframes tooltip-slide-bottom { 2650 | from { 2651 | transform: translate(-50%, -0.75rem); 2652 | opacity: 0; 2653 | } 2654 | to { 2655 | transform: translate(-50%, 0.25rem); 2656 | opacity: 1; 2657 | } 2658 | } 2659 | @-webkit-keyframes tooltip-caret-slide-bottom { 2660 | from { 2661 | opacity: 0; 2662 | } 2663 | 50% { 2664 | transform: translate(-50%, -0.5rem); 2665 | opacity: 0; 2666 | } 2667 | to { 2668 | transform: translate(-50%, -0.3rem); 2669 | opacity: 1; 2670 | } 2671 | } 2672 | @keyframes tooltip-caret-slide-bottom { 2673 | from { 2674 | opacity: 0; 2675 | } 2676 | 50% { 2677 | transform: translate(-50%, -0.5rem); 2678 | opacity: 0; 2679 | } 2680 | to { 2681 | transform: translate(-50%, -0.3rem); 2682 | opacity: 1; 2683 | } 2684 | } 2685 | @-webkit-keyframes tooltip-slide-left { 2686 | from { 2687 | transform: translate(0.75rem, -50%); 2688 | opacity: 0; 2689 | } 2690 | to { 2691 | transform: translate(-0.25rem, -50%); 2692 | opacity: 1; 2693 | } 2694 | } 2695 | @keyframes tooltip-slide-left { 2696 | from { 2697 | transform: translate(0.75rem, -50%); 2698 | opacity: 0; 2699 | } 2700 | to { 2701 | transform: translate(-0.25rem, -50%); 2702 | opacity: 1; 2703 | } 2704 | } 2705 | @-webkit-keyframes tooltip-caret-slide-left { 2706 | from { 2707 | opacity: 0; 2708 | } 2709 | 50% { 2710 | transform: translate(0.05rem, -50%); 2711 | opacity: 0; 2712 | } 2713 | to { 2714 | transform: translate(0.3rem, -50%); 2715 | opacity: 1; 2716 | } 2717 | } 2718 | @keyframes tooltip-caret-slide-left { 2719 | from { 2720 | opacity: 0; 2721 | } 2722 | 50% { 2723 | transform: translate(0.05rem, -50%); 2724 | opacity: 0; 2725 | } 2726 | to { 2727 | transform: translate(0.3rem, -50%); 2728 | opacity: 1; 2729 | } 2730 | } 2731 | @-webkit-keyframes tooltip-slide-right { 2732 | from { 2733 | transform: translate(-0.75rem, -50%); 2734 | opacity: 0; 2735 | } 2736 | to { 2737 | transform: translate(0.25rem, -50%); 2738 | opacity: 1; 2739 | } 2740 | } 2741 | @keyframes tooltip-slide-right { 2742 | from { 2743 | transform: translate(-0.75rem, -50%); 2744 | opacity: 0; 2745 | } 2746 | to { 2747 | transform: translate(0.25rem, -50%); 2748 | opacity: 1; 2749 | } 2750 | } 2751 | @-webkit-keyframes tooltip-caret-slide-right { 2752 | from { 2753 | opacity: 0; 2754 | } 2755 | 50% { 2756 | transform: translate(-0.05rem, -50%); 2757 | opacity: 0; 2758 | } 2759 | to { 2760 | transform: translate(-0.3rem, -50%); 2761 | opacity: 1; 2762 | } 2763 | } 2764 | @keyframes tooltip-caret-slide-right { 2765 | from { 2766 | opacity: 0; 2767 | } 2768 | 50% { 2769 | transform: translate(-0.05rem, -50%); 2770 | opacity: 0; 2771 | } 2772 | to { 2773 | transform: translate(-0.3rem, -50%); 2774 | opacity: 1; 2775 | } 2776 | } 2777 | 2778 | /** 2779 | * Accessibility & User interaction 2780 | */ 2781 | [aria-controls] { 2782 | cursor: pointer; 2783 | } 2784 | 2785 | [aria-disabled=true], 2786 | [disabled] { 2787 | cursor: not-allowed; 2788 | } 2789 | 2790 | [aria-hidden=false][hidden] { 2791 | display: initial; 2792 | } 2793 | 2794 | [aria-hidden=false][hidden]:not(:focus) { 2795 | clip: rect(0, 0, 0, 0); 2796 | position: absolute; 2797 | } 2798 | 2799 | a, 2800 | area, 2801 | button, 2802 | input, 2803 | label, 2804 | select, 2805 | summary, 2806 | textarea, 2807 | [tabindex] { 2808 | -ms-touch-action: manipulation; 2809 | } 2810 | 2811 | [dir=rtl] { 2812 | direction: rtl; 2813 | } 2814 | 2815 | /** 2816 | * Reduce Motion Features 2817 | */ 2818 | @media (prefers-reduced-motion: reduce) { 2819 | *:not([aria-busy=true]), 2820 | :not([aria-busy=true])::before, 2821 | :not([aria-busy=true])::after { 2822 | background-attachment: initial !important; 2823 | -webkit-animation-duration: 1ms !important; 2824 | animation-duration: 1ms !important; 2825 | -webkit-animation-delay: -1ms !important; 2826 | animation-delay: -1ms !important; 2827 | -webkit-animation-iteration-count: 1 !important; 2828 | animation-iteration-count: 1 !important; 2829 | scroll-behavior: auto !important; 2830 | transition-delay: 0s !important; 2831 | transition-duration: 0s !important; 2832 | } 2833 | } 2834 | 2835 | /*# sourceMappingURL=pico.css.map */ -------------------------------------------------------------------------------- /src/views/html/css/styles.css: -------------------------------------------------------------------------------- 1 | details summary { 2 | text-transform: uppercase; 3 | } -------------------------------------------------------------------------------- /src/webview-controller.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as ejs from 'ejs'; 3 | import path = require('path'); 4 | import { readFile } from 'fs/promises'; 5 | import { ListResponse, QueryInfo } from './models/cf'; 6 | 7 | export class WebviewController implements vscode.Disposable { 8 | private _panel: vscode.WebviewPanel | undefined; 9 | 10 | private _panels: {[key: string]: vscode.WebviewPanel | undefined}; 11 | 12 | constructor(private baseUri: vscode.Uri) { 13 | this._panels = {}; 14 | } 15 | 16 | init(title: string): void { 17 | if (title in this._panels) { 18 | this._panel = this._panels[title]; 19 | } 20 | 21 | const viewType = "sql-content.output"; 22 | const panel = vscode.window.createWebviewPanel(viewType, title, { 23 | viewColumn: vscode.ViewColumn.Two, 24 | preserveFocus: true 25 | }); 26 | 27 | this._panel = this._panels[title] = panel; 28 | panel.onDidDispose(() => { 29 | console.info(`removing ${title} from panels`); 30 | delete this._panels[title]; 31 | }); 32 | } 33 | 34 | async render(title: string, response: ListResponse | QueryInfo[]>): Promise { 35 | this.init(title); 36 | 37 | let set: QueryInfo[] = []; 38 | 39 | const query = response.result; 40 | if (!Array.isArray(query)) { 41 | set = [query]; 42 | } else { 43 | set = query as QueryInfo[]; 44 | } 45 | 46 | const queries = set.map((query, idx) => { 47 | const index = idx, 48 | rows = query.results, 49 | duration = query.meta.duration, 50 | changes = query.meta.changes; 51 | 52 | let headers; 53 | if (query.results.length > 0) { 54 | headers = Object.keys(query.results[0]); 55 | } 56 | 57 | return { index, rows, headers, duration, changes }; 58 | }); 59 | 60 | const errors = response.errors; 61 | const now = new Date().toLocaleTimeString(); 62 | const basehref = `${this._panel!.webview.asWebviewUri(vscode.Uri.joinPath(this.baseUri, "dist"))}/`; 63 | const theme = vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark ? "dark" : "light"; 64 | const data = { 65 | now, 66 | queries, 67 | basehref, 68 | theme, 69 | errors 70 | }; 71 | 72 | // TODO: this is a hack, it won't work in debugger since dist won't exist 73 | const fileUri = vscode.Uri.joinPath(this.baseUri, "dist/sql-content.ejs"); 74 | const fileContent = await vscode.workspace.fs.readFile(fileUri); 75 | const formattedHTML = ejs.render(fileContent.toString(), data); 76 | 77 | if (!this._panel) { 78 | console.warn("no panel init for %s", title); 79 | return; 80 | } 81 | 82 | this._panel.webview.html = formattedHTML; 83 | this._panel.reveal(); 84 | } 85 | 86 | dispose() { 87 | if (this._panel !== undefined) { 88 | this._panel.dispose(); 89 | this._panel = undefined; 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "lib": [ 6 | "ES2020" 7 | ], 8 | "sourceMap": true, 9 | "rootDir": "src", 10 | "strict": true /* enable all strict type-checking options */ 11 | /* Additional Checks */ 12 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 13 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 14 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | 'use strict'; 4 | 5 | const CopyPlugin = require('copy-webpack-plugin'); 6 | const path = require('path'); 7 | 8 | //@ts-check 9 | /** @typedef {import('webpack').Configuration} WebpackConfig **/ 10 | 11 | /** @type WebpackConfig */ 12 | const extensionConfig = { 13 | target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ 14 | mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') 15 | 16 | entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 17 | output: { 18 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 19 | path: path.resolve(__dirname, 'dist'), 20 | filename: 'extension.js', 21 | libraryTarget: 'commonjs2' 22 | }, 23 | externals: { 24 | vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 25 | // modules added here also need to be added in the .vscodeignore file 26 | }, 27 | resolve: { 28 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 29 | extensions: ['.ts', '.js'] 30 | }, 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.ts$/, 35 | exclude: /node_modules/, 36 | use: [ 37 | { 38 | loader: 'ts-loader' 39 | } 40 | ] 41 | } 42 | ] 43 | }, 44 | plugins: [ 45 | new CopyPlugin({ 46 | patterns: [ 47 | { from: "src/sql-content.ejs" }, 48 | { from: "src/views", to: "views" } 49 | ] 50 | }) 51 | ], 52 | devtool: 'nosources-source-map', 53 | infrastructureLogging: { 54 | level: "log", // enables logging required for problem matchers 55 | }, 56 | }; 57 | module.exports = [ extensionConfig ]; --------------------------------------------------------------------------------