├── .editorconfig ├── .eslintrc.cjs ├── .gitignore ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── package.json ├── playwright.config.ts ├── src ├── app.css ├── app.d.ts ├── app.html ├── index.test.ts ├── kanbanicon.png ├── lib │ ├── Kanban.svelte │ ├── class │ │ └── Lang.js │ ├── components │ │ ├── AddColumnBtn.svelte │ │ ├── Card.svelte │ │ └── Column │ │ │ ├── Column.svelte │ │ │ └── OptionsColumn.svelte │ ├── index.js │ ├── stores │ │ └── store.js │ └── styles │ │ └── colors.scss └── routes │ └── +page.svelte ├── static ├── favicon.png ├── kanbancapture.PNG └── kanbanicon.png ├── svelte.config.js ├── tsconfig.json └── vite.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:svelte/recommended', 7 | 'prettier' 8 | ], 9 | parser: '@typescript-eslint/parser', 10 | plugins: ['@typescript-eslint'], 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020, 14 | extraFileExtensions: ['.svelte'] 15 | }, 16 | env: { 17 | browser: true, 18 | es2017: true, 19 | node: true 20 | }, 21 | overrides: [ 22 | { 23 | files: ['*.svelte'], 24 | parser: 'svelte-eslint-parser', 25 | parserOptions: { 26 | parser: '@typescript-eslint/parser' 27 | } 28 | } 29 | ] 30 | }; 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ####### 2 | # MacOs 3 | .DS_Store 4 | 5 | ############## 6 | # Dependencies 7 | /node_modules 8 | 9 | ######################################### 10 | # Lock files aren't used by package users 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | 15 | ################### 16 | # Environment files 17 | .env 18 | .env.* 19 | !.env.example 20 | 21 | ###################### 22 | # Editor configuration 23 | *.swp 24 | /.vscode 25 | 26 | ######################################## 27 | # Created by scripts (dev/build/package) 28 | 29 | vite.config.js.timestamp-* 30 | vite.config.ts.timestamp-* 31 | 32 | # Source maps https://web.dev/source-maps/ 33 | *.map 34 | 35 | /.svelte-kit 36 | /build 37 | /dist 38 | /package 39 | 40 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | resolution-mode=highest 3 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Svelte Kanban 3 |
 Svelte Kanban 4 |

5 | 6 |

7 | 8 | 9 | 10 | 11 |

12 | 13 |
14 | 15 | **[Live demo](https://kanban-demo.vercel.app/)**. 16 | 17 |
18 | 19 | **A simple Svelte Kanban made in pure CSS** 20 | 21 | Svelte Kanban 22 | 23 | 24 | ## Installation 25 | 26 | ```sh 27 | npm i --save svelte-kanban 28 | npm i --save-dev sass 29 | ``` 30 | 31 | ## Usage 32 | 33 | ```svelte 34 | 37 | 38 |
39 | 40 |
41 | ``` 42 | 43 | ## Props 44 | 45 | Full list of props/bindable variables for this component: 46 | 47 |
48 | 49 | 50 | | name | default | description | 51 | | :--------------- | :--------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 52 | | `lang` | 'en' | String to specify the language of the kanban, only french and english supported atm (`en`/`fr`). | 53 | | `colsList` | `['Todo','Done']` | Array of string to define the default columns.| 54 | | `catsList` | `[{'new',color:'white', bgColor:"#0A99FF"},{label:'important',color:'white',bgColor:"#EA0B38"},{label:'task',color:'black',bgColor:"#00F5DC"},{label:'personal',color:'white',bgColor:"#629387"},{label:'work',color:'black',bgColor:"#13F644"}]` | Array of objects `(label:string, color:string, bgColor:string)`defining the categories available for the cards.| 55 | | `maxColumns` (unavailable yet) | `5` | Max number of columns the user can display on the kanban.| 56 | | `minimalist` (unavailable yet)| `false` | Boolean, if set to true, the card will be minimalist version with only a title and a delete button.| 57 | 58 |
59 | 60 | ## Styling props (update 08/03/2022 : AVAILABLE /!\) 61 | 62 | | name | default | description | 63 | | :-------------- | :------ | :---------------------------------------------------------------------------------------------------------- | 64 | | `theme` | 'light' | string: specify the theme you want to used light/dark | 65 | | `primary` | `null` | string : if you want to specify the primary background color (behind the kanbans columns / card background) | 66 | | `secondary` | `null` | string : if you want to specify the secondary background color (kanbans columns) | 67 | | `third` | `null` | string : if you want to specify the secondary background color (no usage atm) | 68 | | `fontPrimary` | `null` | string : if you want to specify the primary font color (col title, card title) | 69 | | `fontSecondary` | `null` | string : if you want to specify the secondary font color (cards count, new task, date text) | 70 | 71 | ## Events 72 | 73 | `Kanban.svelte` dispatches the following events: 74 | 75 | | name | detail | description | 76 | | ----------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | 77 | | `columnRemove` | `{ position:number, name:string }` | Triggers when a column is removed. | 78 | | `columnAdd` | `{ position: number }` | Triggers when a column is added. | 79 | | `columnMoved` | `{ old_pos:number, new_pos:number }` | Triggers when a column is moved. | 80 | | `cardDragStart` | `{card:number, col:number, event:Event}` | Triggers when a card start being dragged. `col` = position of the column, `card` = position of the card in the column, `event`: mousedown event props | 81 | | `cardDragMove` | `{card:number, col:number, event:Event}` | Triggers every time the card is moving (ie on every mousemove) | 82 | | `cardDragEnd` | `{card:number, col:number, event:Event}` | Triggers when the dragging of the card ended. | 83 | | `cardDragSuccess` | `{old_col:number, old_pos:number, new_col:number, new_pos:number}` | Triggers when the drop of the card is a success (ie : the card has been moved to another column). | 84 | | `cardDragFailed` | `{col:number, card:number}` | Triggers when the drop of the card is a fail (ie : the card has not been moved and stayed in the origin column). | 85 | | `cardAdd` | `{col:number}` | Triggers when a card is added to a column. | 86 | | `cardPropModify` | `{ card:number, col:number, prop:string, value:string}` | Triggers when a prop of a card is in edit mode. | 87 | | `cardPropSaved` | `{ card:number, col:number, prop:string, value:string}` | Triggers when a prop of a card is saved. | 88 | | `moveCardUp` | `{col:number, old_pos:number, new_pos:number}` | Triggers when a card is moved down inside a column. | 89 | | `moveCardDown` | `{col:number, old_pos:number, new_pos:number}` | Triggers when a card is moved up inside a column. | 90 | 91 | ### Examples 92 | 93 | 94 | - `on:columnAdd={(e) => console.log('columnAdd', e)}` 95 | - `on:cardAdd={(e) => console.log('cardAdd', e}`. 96 | - `on:moveCardUp={yourFunctionHere}` 97 | 98 | ```svelte 99 | alert(`You ${e.detail.type}ed '${e.detail.option.label}'`)} 101 | on:cardDragStart={(e) => alert(`You are moving the card at the '${e.detail.option.label}' position on the column n° ${}`)} 102 | /> 103 | ``` 104 | 105 | ## Dev Mode 106 | 107 | ```sh 108 | git clone https://github.com/V-Py/svelte-kanban 109 | cd svelte-kanban 110 | npm install 111 | npm run dev 112 | ``` 113 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-kanban", 3 | "version": "1.0.0", 4 | "engines": { 5 | "node": ">=16.14" 6 | }, 7 | "scripts": { 8 | "build": "vite build && npm run package", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 11 | "dev": "vite dev", 12 | "format": "prettier --write .", 13 | "lint": "prettier --check . && eslint --report-unused-disable-directives .", 14 | "package": "svelte-kit sync && svelte-package && publint", 15 | "prepublishOnly": "npm run package", 16 | "preview": "vite preview", 17 | "pub": "npm publish --access=public", 18 | "test": "npm run test:integration && npm run test:unit", 19 | "test:integration": "playwright test", 20 | "test:unit": "vitest" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/V-Py/svelte-kanban" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/V-Py/svelte-kanban/issues" 28 | }, 29 | "author": { 30 | "name": "Valentin Py", 31 | "email": "valentin.pyb@gmail.com", 32 | "url": "https://github.com/V-Py" 33 | }, 34 | "keywords": [ 35 | "svelte", 36 | "kanban", 37 | "svelte-kanban", 38 | "js-kanban", 39 | "drag-n-drop", 40 | "trello-like" 41 | ], 42 | "files": [ 43 | "./dist/**/*" 44 | ], 45 | "exports": { 46 | ".": { 47 | "types": "./dist/index.d.ts", 48 | "svelte": "./dist/index.js", 49 | "default": "./dist/index.js" 50 | } 51 | }, 52 | "peerDependencies": { 53 | "svelte": "^3.46.4 || ^4.0.0" 54 | }, 55 | "devDependencies": { 56 | "@playwright/test": "^1.37.0", 57 | "@sveltejs/adapter-auto": "^2.1.0", 58 | "@sveltejs/kit": "^1.22.5", 59 | "@sveltejs/package": "^2.2.1", 60 | "@typescript-eslint/eslint-plugin": "^6.3.0", 61 | "@typescript-eslint/parser": "^6.3.0", 62 | "eslint": "^8.47.0", 63 | "eslint-config-prettier": "^9.0.0", 64 | "eslint-plugin-svelte": "^2.32.4", 65 | "prettier": "^3.0.1", 66 | "prettier-plugin-svelte": "^3.0.3", 67 | "publint": "^0.1.16", 68 | "sass": "^1.65.1", 69 | "svelte": "^4.2.0", 70 | "svelte-check": "^3.5.0", 71 | "tslib": "^2.6.1", 72 | "typescript": "^5.1.6", 73 | "vite": "^4.4.9", 74 | "vitest": "^0.34.1" 75 | }, 76 | "type": "module", 77 | "svelte": "./dist/index.js", 78 | "types": "./dist/index.d.ts" 79 | } 80 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from '@playwright/test'; 2 | 3 | const config: PlaywrightTestConfig = { 4 | webServer: { 5 | command: 'npm run build && npm run preview', 6 | port: 4173 7 | }, 8 | testDir: 'tests', 9 | testMatch: /(.+\.)?(test|spec)\.[jt]s/ 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | width: 100%; 5 | } 6 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface Platform {} 9 | } 10 | } 11 | 12 | export {}; 13 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | describe('sum test', () => { 4 | it('adds 1 + 2 to equal 3', () => { 5 | expect(1 + 2).toBe(3); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/kanbanicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/V-Py/svelte-kanban/9726005e5c64cc6c3dbf054cfd495dbd34a18abd/src/kanbanicon.png -------------------------------------------------------------------------------- /src/lib/Kanban.svelte: -------------------------------------------------------------------------------- 1 | 345 | 346 |
347 |
348 |
349 | {#each $columns as column, index_col(column)} 350 | {dispatch('columnSaveTitle', {title:e.detail.title, columns:$columns})}} 361 | on:cardMouseDown={cardDragStart} 362 | on:removeColumn={removeColumn} 363 | on:addCard={(e) => {addCard(e.detail.index)}} 364 | on:cardPropSaved={(e) => {dispatch('cardPropSaved', {prop:e.detail.prop, col:e.detail.col, card:e.detail.card, value:e.detail.value, columns:$columns})}} 365 | on:cardPropModify 366 | on:cardRemove={()=>{dispatch('cardRemove', {columns:$columns})}} 367 | on:moveCardUp={moveCardUp} 368 | on:moveCardDown={moveCardDown} 369 | on:moveColumn={moveColumn} 370 | /> 371 | {/each} 372 | 380 | 381 |
382 | 383 |
384 |
385 | 386 | 387 | 388 | -------------------------------------------------------------------------------- /src/lib/class/Lang.js: -------------------------------------------------------------------------------- 1 | export class Lang { 2 | constructor(lang = 'en') { 3 | this.lang = lang; 4 | } 5 | getStr(str) { 6 | return Lang[this.lang][str]; 7 | } 8 | } 9 | Lang.en = { 10 | Yes: 'Yes', 11 | No: 'No', 12 | Done: 'Done', 13 | Todo: 'Todo', 14 | Card: 'Card', 15 | Cards: 'Cards', 16 | AddACard: 'Add a card', 17 | NewCard: 'New card', 18 | NewColumn: 'New column', 19 | new: 'new', 20 | task: 'task', 21 | personal: 'personal', 22 | work: 'work', 23 | important: 'important' 24 | }; 25 | Lang.fr = { 26 | Yes: 'Oui', 27 | No: 'Non', 28 | Done: 'Terminé', 29 | Todo: 'À faire', 30 | Card: 'Tâche', 31 | Cards: 'Tâches', 32 | AddACard: 'Nouvelle tâche', 33 | NewCard: 'Nouvelle tâche', 34 | NewColumn: 'Nouvelle colonne', 35 | new: 'nouveau', 36 | task: 'tâche', 37 | personal: 'personnel', 38 | work: 'travail', 39 | important: 'important' 40 | }; 41 | -------------------------------------------------------------------------------- /src/lib/components/AddColumnBtn.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /src/lib/components/Card.svelte: -------------------------------------------------------------------------------- 1 | 66 | 67 | 68 |
76 |
77 |
78 | 79 | {#if bool_show_cats_list} 80 |
81 | {#each catsList as cat_temp, cat_index} 82 | 86 | {/each} 87 |
88 | {/if} 89 |
90 |
91 | 94 |
95 |
96 |
97 | 98 | {handleKeyUp(e, 'title')}} id="input-title-{id}-col-{id_col}" value={title} type="text" style="display:none;" class="input-title"> 99 | 102 |
103 |
104 | 105 | {handleKeyUp(e, 'date')}} id="input-date-{id}-col-{id_col}" value={date} type="text" style="display:none;" class="input-date"> 106 | 109 |
110 | 111 | 114 | 115 | 118 |
119 | 120 | 317 | -------------------------------------------------------------------------------- /src/lib/components/Column/Column.svelte: -------------------------------------------------------------------------------- 1 | 51 | 52 |
53 |
54 | {#if bool_show_options} 55 | 56 | {:else} 57 | 58 | {/if} 59 | 60 | 68 | 71 | 74 |
75 | 76 |
77 | {slots.length} {$globalLang.getStr(slots.length === 1 ? 'Card':'Cards')} 78 |
79 | 80 |
81 | {#if slots.length > 0} 82 | {#each slots as slot, index} 83 |
84 | {#if slot.empty == false} 85 | 102 | {:else} 103 |
104 | {/if} 105 |
106 | {/each} 107 | {/if} 108 | 109 |
110 | 114 |
115 | 116 | 318 | -------------------------------------------------------------------------------- /src/lib/components/Column/OptionsColumn.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 |
18 | {#if bool_show_options} 19 | 22 | {:else} 23 | 26 | {/if} 27 |
28 | 29 | -------------------------------------------------------------------------------- /src/lib/index.js: -------------------------------------------------------------------------------- 1 | import Kanban from '$lib/Kanban.svelte'; 2 | 3 | export default Kanban; 4 | -------------------------------------------------------------------------------- /src/lib/stores/store.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | import { Lang } from '../class/Lang.js'; 3 | 4 | export const number_of_slots = writable(0); 5 | export const main_width = writable(''); 6 | export const main_height = writable(''); 7 | export const columns = writable([]); 8 | export const globalLang = writable(new Lang()); 9 | export const unique_id = writable(0); -------------------------------------------------------------------------------- /src/lib/styles/colors.scss: -------------------------------------------------------------------------------- 1 | $TEA_GREEN: hsl(101, 32%, 79%); 2 | $LIGHT_GRAY: rgb(243, 244, 246); 3 | 4 | // $MAIN_BG:$TEA_GREEN; //#fff 5 | $MAIN_BG: #fff; //#fff 6 | // HEADER 7 | // $HEADER_BG:$TEA_GREEN; //#fff 8 | $HEADER_BG: #fff; //#fff 9 | 10 | // COLUMNS 11 | // $COLUMN_CONTENT_BG :$TEA_GREEN; //#fff 12 | $COLUMN_CONTENT_BG: #fff; //#fff 13 | $COLUMN_TITLE_BG: #3de1b5; 14 | $COLUMN_TITLE_COLOR: #181f1c; 15 | 16 | // FOOTER 17 | // $FOOTER_BG:$TEA_GREEN; // #fff 18 | $FOOTER_BG: #fff; // #fff 19 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 25 | -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/V-Py/svelte-kanban/9726005e5c64cc6c3dbf054cfd495dbd34a18abd/static/favicon.png -------------------------------------------------------------------------------- /static/kanbancapture.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/V-Py/svelte-kanban/9726005e5c64cc6c3dbf054cfd495dbd34a18abd/static/kanbancapture.PNG -------------------------------------------------------------------------------- /static/kanbanicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/V-Py/svelte-kanban/9726005e5c64cc6c3dbf054cfd495dbd34a18abd/static/kanbanicon.png -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import { vitePreprocess } from '@sveltejs/kit/vite'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 12 | // If your environment is not supported or you settled on a specific environment, switch out the adapter. 13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 14 | adapter: adapter() 15 | } 16 | }; 17 | 18 | export default config; 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "NodeNext", 13 | "paths": { 14 | "$lib": ["./src/lib"], 15 | "$lib/*": ["./src/lib/*"], 16 | "$stores/*": ["./src/lib/stores/*"] 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()], 6 | test: { 7 | include: ['src/**/*.{test,spec}.{js,ts}'] 8 | }, 9 | // Source Maps 10 | // See https://web.dev/source-maps/ 11 | build: { 12 | // sourcemap: true // enable production source maps 13 | }, 14 | css: { 15 | devSourcemap: true // enable CSS source maps during development 16 | } 17 | }); 18 | --------------------------------------------------------------------------------