commit c7fe04e8e31d60d8d6e342910955e0bd81bdc663 Author: Giuliano Silvestro Date: Fri Feb 13 21:54:33 2026 +1000 Initial commit: kanban-elements-ui library Angular 19 component library for kanban boards: - kb-board: main board with toolbar, swimlanes, and pipeline view - kb-column: columns with WIP limits, collapse, and drag reorder - kb-card: draggable cards with labels, assignees, and priorities - kb-swimlane: horizontal grouping with collapsible rows - kb-toolbar: search, sort, filter, and view toggle - kb-quick-add: inline card creation - kb-wip-indicator: WIP limit status display - kb-card-def / kb-column-header-def: custom template directives Includes signal-based services (board, drag, filter), SCSS design tokens with dark mode, and full TypeScript type definitions. Co-Authored-By: Claude Opus 4.6 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d1996f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +dist/ +node_modules/ +.angular/ +*.tgz diff --git a/ng-package.json b/ng-package.json new file mode 100644 index 0000000..1775ca2 --- /dev/null +++ b/ng-package.json @@ -0,0 +1,12 @@ +{ + "$schema": "node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "src/index.ts", + "styleIncludePaths": ["src/styles"] + }, + "dest": "dist", + "deleteDestPath": true, + "assets": [ + "src/styles/**/*.scss" + ] +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..00fd24f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3767 @@ +{ + "name": "@sda/kanban-elements-ui", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@sda/kanban-elements-ui", + "version": "0.1.0", + "license": "MIT", + "devDependencies": { + "@angular/common": "^19.1.0", + "@angular/compiler": "^19.1.0", + "@angular/compiler-cli": "^19.1.0", + "@angular/core": "^19.1.0", + "@angular/forms": "^19.2.18", + "ng-packagr": "^19.1.0", + "typescript": "~5.7.2" + }, + "peerDependencies": { + "@angular/common": "^19.0.0", + "@angular/core": "^19.0.0", + "@sda/base-ui": "*" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular/common": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-19.2.18.tgz", + "integrity": "sha512-CrV02Omzw/QtfjlEVXVPJVXipdx83NuA+qSASZYrxrhKFusUZyK3P/Zznqg+wiAeNDbedQwMUVqoAARHf0xQrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/core": "19.2.18", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/compiler": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-19.2.18.tgz", + "integrity": "sha512-3MscvODxRVxc3Cs0ZlHI5Pk5rEvE80otfvxZTMksOZuPlv1B+S8MjWfc3X3jk9SbyUEzODBEH55iCaBHD48V3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + } + }, + "node_modules/@angular/compiler-cli": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-19.2.18.tgz", + "integrity": "sha512-N4TMtLfImJIoMaRL6mx7885UBeQidywptHH6ACZj71Ar6++DBc1mMlcwuvbeJCd3r3y8MQ5nLv5PZSN/tHr13w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "7.26.9", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^4.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.2.0", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js", + "ngcc": "bundles/ngcc/index.js" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/compiler": "19.2.18", + "typescript": ">=5.5 <5.9" + } + }, + "node_modules/@angular/core": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-19.2.18.tgz", + "integrity": "sha512-+QRrf0Igt8ccUWXHA+7doK5W6ODyhHdqVyblSlcQ8OciwkzIIGGEYNZom5OZyWMh+oI54lcSeyV2O3xaDepSrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.15.0" + } + }, + "node_modules/@angular/forms": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-19.2.18.tgz", + "integrity": "sha512-pe40934jWhoS7DyGl7jyZdoj1gvBgur2t1zrJD+csEkTitYnW14+La2Pv6SW1pNX5nIzFsgsS9Nex1KcH5S6Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/common": "19.2.18", + "@angular/core": "19.2.18", + "@angular/platform-browser": "19.2.18", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz", + "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.9", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.9", + "@babel/types": "^7.26.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/nice": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", + "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.1.1", + "@napi-rs/nice-android-arm64": "1.1.1", + "@napi-rs/nice-darwin-arm64": "1.1.1", + "@napi-rs/nice-darwin-x64": "1.1.1", + "@napi-rs/nice-freebsd-x64": "1.1.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", + "@napi-rs/nice-linux-arm64-gnu": "1.1.1", + "@napi-rs/nice-linux-arm64-musl": "1.1.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", + "@napi-rs/nice-linux-s390x-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-musl": "1.1.1", + "@napi-rs/nice-openharmony-arm64": "1.1.1", + "@napi-rs/nice-win32-arm64-msvc": "1.1.1", + "@napi-rs/nice-win32-ia32-msvc": "1.1.1", + "@napi-rs/nice-win32-x64-msvc": "1.1.1" + } + }, + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz", + "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz", + "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz", + "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz", + "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz", + "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz", + "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz", + "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz", + "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz", + "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz", + "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz", + "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz", + "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz", + "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-openharmony-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz", + "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz", + "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz", + "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz", + "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/wasm-node": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/wasm-node/-/wasm-node-4.57.1.tgz", + "integrity": "sha512-b0rcJH8ykEanfgTeDtlPubhphIUOx0oaAek+3hizTaFkoC1FBSTsY0GixwB4D5HZ5r3Gt2yI9c8M13OcW/kW5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dependency-graph": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", + "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/injection-js": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/injection-js/-/injection-js-2.6.1.tgz", + "integrity": "sha512-dbR5bdhi7TWDoCye9cByZqeg/gAfamm8Vu3G1KZOTYkOif8WkuM8CD0oeDPtZYMzT5YH76JAFB7bkmyY9OJi2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/less": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/less/-/less-4.5.1.tgz", + "integrity": "sha512-UKgI3/KON4u6ngSsnDADsUERqhZknsVZbnuzlRZXLQCmfC/MDld42fTydUE9B+Mla1AL6SJ/Pp6SlEFi/AVGfw==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/ng-packagr": { + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/ng-packagr/-/ng-packagr-19.2.2.tgz", + "integrity": "sha512-dFuwFsDJMBSd1YtmLLcX5bNNUCQUlRqgf34aXA+79PmkOP+0eF8GP2949wq3+jMjmFTNm80Oo8IUYiSLwklKCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-json": "^6.1.0", + "@rollup/wasm-node": "^4.24.0", + "ajv": "^8.17.1", + "ansi-colors": "^4.1.3", + "browserslist": "^4.22.1", + "chokidar": "^4.0.1", + "commander": "^13.0.0", + "convert-source-map": "^2.0.0", + "dependency-graph": "^1.0.0", + "esbuild": "^0.25.0", + "fast-glob": "^3.3.2", + "find-cache-dir": "^3.3.2", + "injection-js": "^2.4.0", + "jsonc-parser": "^3.3.1", + "less": "^4.2.0", + "ora": "^5.1.0", + "piscina": "^4.7.0", + "postcss": "^8.4.47", + "rxjs": "^7.8.1", + "sass": "^1.81.0" + }, + "bin": { + "ng-packagr": "cli/main.js" + }, + "engines": { + "node": "^18.19.1 || >=20.11.1" + }, + "optionalDependencies": { + "rollup": "^4.24.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^19.0.0 || ^19.1.0-next.0 || ^19.2.0-next.0", + "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "tslib": "^2.3.0", + "typescript": ">=5.5 <5.9" + }, + "peerDependenciesMeta": { + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/ng-packagr/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/piscina": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.9.2.tgz", + "integrity": "sha512-Fq0FERJWFEUpB4eSY59wSNwXD4RYqR+nR/WiEVcZW8IWfVBxJJafcgTEZDQo8k3w0sUarJ8RyVbbUF4GQ2LGbQ==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "@napi-rs/nice": "^1.0.1" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/sass": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz", + "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sax": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "dev": true, + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..35c6ae2 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "@sda/kanban-elements-ui", + "version": "0.1.0", + "description": "Angular components for kanban boards with drag-and-drop, swimlanes, WIP limits, and pipeline views powered by @sda/base-ui", + "keywords": [ + "angular", + "kanban", + "board", + "drag-and-drop", + "swimlanes", + "pipeline", + "components", + "ui" + ], + "repository": { + "type": "git", + "url": "https://git.sky-ai.com/ui-core-design/kanban-elements-ui.git" + }, + "license": "MIT", + "sideEffects": false, + "scripts": { + "build": "ng-packagr -p ng-package.json", + "build:dev": "./build-for-dev.sh" + }, + "peerDependencies": { + "@angular/common": "^19.0.0", + "@angular/core": "^19.0.0", + "@sda/base-ui": "*" + }, + "devDependencies": { + "@angular/common": "^19.1.0", + "@angular/compiler": "^19.1.0", + "@angular/compiler-cli": "^19.1.0", + "@angular/core": "^19.1.0", + "@angular/forms": "^19.2.18", + "ng-packagr": "^19.1.0", + "typescript": "~5.7.2" + } +} diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 0000000..9eeeed2 --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,9 @@ +export * from './kb-board'; +export * from './kb-column'; +export * from './kb-card'; +export * from './kb-swimlane'; +export * from './kb-toolbar'; +export * from './kb-quick-add'; +export * from './kb-wip-indicator'; +export * from './kb-card-def'; +export * from './kb-column-header-def'; diff --git a/src/components/kb-board/index.ts b/src/components/kb-board/index.ts new file mode 100644 index 0000000..d923137 --- /dev/null +++ b/src/components/kb-board/index.ts @@ -0,0 +1 @@ +export * from './kb-board.component'; diff --git a/src/components/kb-board/kb-board.component.html b/src/components/kb-board/kb-board.component.html new file mode 100644 index 0000000..2f23e43 --- /dev/null +++ b/src/components/kb-board/kb-board.component.html @@ -0,0 +1,97 @@ +
+ + @if (showToolbar()) { + + } + + + @if (loading()) { +
+ +
+ } + + +
+ @if (hasSwimlanes()) { + + @for (swimlane of sortedSwimlanes(); track swimlane.id) { + +
+ @for (column of sortedColumns(); track column.id) { + + } +
+
+ } + } @else { + +
+ @for (column of sortedColumns(); track column.id) { + @if (viewMode() === 'pipeline' && !$first) { +
+
+ + + +
+ } + + } +
+ } +
+
diff --git a/src/components/kb-board/kb-board.component.scss b/src/components/kb-board/kb-board.component.scss new file mode 100644 index 0000000..c90b7bd --- /dev/null +++ b/src/components/kb-board/kb-board.component.scss @@ -0,0 +1,115 @@ +:host { + display: block; +} + +.kb-board { + background: var(--kb-bg); + min-height: 200px; + position: relative; +} + +.kb-board__loading { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + background: var(--kb-glass-bg); + backdrop-filter: blur(var(--kb-glass-blur)); + -webkit-backdrop-filter: blur(var(--kb-glass-blur)); + z-index: 20; +} + +.kb-board__spinner { + width: 2rem; + height: 2rem; + border: 3px solid var(--kb-column-border); + border-top-color: var(--color-primary-500, #3b82f6); + border-radius: 50%; + animation: kb-spin 0.6s linear infinite; +} + +@keyframes kb-spin { + to { transform: rotate(360deg); } +} + +.kb-board__content { + overflow-x: auto; + padding: 1.25rem; + + &::-webkit-scrollbar { + height: 4px; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: var(--kb-card-meta-color); + border-radius: 4px; + + &:hover { + background: var(--kb-card-desc-color); + } + } + + scrollbar-width: thin; + scrollbar-color: var(--kb-card-meta-color) transparent; +} + +.kb-board__columns { + display: flex; + gap: var(--kb-column-gap); + align-items: flex-start; + min-height: 200px; +} + +// Pipeline mode +.kb-board--pipeline { + .kb-board__columns { + align-items: stretch; + } +} + +.kb-board__pipeline-connector { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 2rem; + position: relative; +} + +.kb-board__pipeline-svg { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: visible; +} + +.kb-board__pipeline-line { + position: absolute; + top: 50%; + left: 0; + right: 0; + height: var(--kb-pipeline-connector-width); + background: var(--kb-pipeline-connector-color); +} + +.kb-board__pipeline-arrow { + position: relative; + z-index: 1; + color: var(--kb-pipeline-connector-color); + display: inline-flex; + + svg { + width: 0.875rem; + height: 0.875rem; + } +} diff --git a/src/components/kb-board/kb-board.component.ts b/src/components/kb-board/kb-board.component.ts new file mode 100644 index 0000000..1dd355d --- /dev/null +++ b/src/components/kb-board/kb-board.component.ts @@ -0,0 +1,220 @@ +import { + Component, ChangeDetectionStrategy, input, output, computed, inject, + contentChildren, effect, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import type { + KbCard, KbColumn, KbSwimlane, KbViewMode, KbCardSort, +} from '../../types/kanban.types'; +import type { + KbCardMoveEvent, KbCardClickEvent, KbCardDblClickEvent, + KbCardContextMenuEvent, KbCardAddEvent, KbCardDeleteEvent, + KbColumnClickEvent, KbColumnToggleEvent, KbColumnReorderEvent, + KbSwimlaneToggleEvent, KbViewChangeEvent, KbSortChangeEvent, + KbFilterChangeEvent, KbWipExceededEvent, KbDragStartEvent, KbDragEndEvent, +} from '../../types/event.types'; +import { KANBAN_CONFIG } from '../../providers/kanban-config.provider'; +import { KanbanBoardService } from '../../services/kanban-board.service'; +import { KanbanDragService } from '../../services/kanban-drag.service'; +import { KanbanFilterService } from '../../services/kanban-filter.service'; +import { isWipExceeded } from '../../utils/wip.utils'; +import { KbColumnComponent } from '../kb-column/kb-column.component'; +import { KbSwimlaneComponent } from '../kb-swimlane/kb-swimlane.component'; +import { KbToolbarComponent } from '../kb-toolbar/kb-toolbar.component'; +import { KbCardDefDirective } from '../kb-card-def/kb-card-def.directive'; +import { KbColumnHeaderDefDirective } from '../kb-column-header-def/kb-column-header-def.directive'; + +@Component({ + selector: 'kb-board', + standalone: true, + imports: [CommonModule, KbColumnComponent, KbSwimlaneComponent, KbToolbarComponent], + templateUrl: './kb-board.component.html', + styleUrl: './kb-board.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [KanbanBoardService, KanbanDragService, KanbanFilterService], +}) +export class KbBoardComponent { + private config = inject(KANBAN_CONFIG); + protected boardService = inject(KanbanBoardService); + private dragService = inject(KanbanDragService); + protected filterService = inject(KanbanFilterService); + + // Content children + private cardDefs = contentChildren(KbCardDefDirective); + private headerDefs = contentChildren(KbColumnHeaderDefDirective); + + // --- Inputs --- + readonly cards = input.required(); + readonly columns = input.required(); + readonly swimlanes = input([]); + readonly viewMode = input(this.config.defaultView); + readonly loading = input(false); + readonly showToolbar = input(false); + readonly searchable = input(true); + readonly showViewToggle = input(true); + readonly allowColumnReorder = input(this.config.allowColumnReorder); + readonly allowQuickAdd = input(this.config.allowQuickAdd); + + // --- Outputs --- + readonly cardMove = output(); + readonly cardClick = output(); + readonly cardDblClick = output(); + readonly cardContextMenu = output(); + readonly cardAdd = output(); + readonly cardDelete = output(); + readonly columnClick = output(); + readonly columnToggle = output(); + readonly columnReorder = output(); + readonly swimlaneToggle = output(); + readonly viewChange = output(); + readonly sortChange = output(); + readonly filterChange = output(); + readonly wipExceeded = output(); + readonly dragStart = output(); + readonly dragEnd = output(); + + // --- Computed --- + readonly sortedColumns = computed(() => + [...this.columns()].sort((a, b) => a.order - b.order) + ); + + readonly sortedSwimlanes = computed(() => + [...this.swimlanes()].sort((a, b) => a.order - b.order) + ); + + readonly hasSwimlanes = computed(() => this.swimlanes().length > 0); + + readonly filteredCards = computed(() => + this.filterService.applyFilters(this.cards()) + ); + + protected cardTemplate = computed(() => { + const defs = this.cardDefs(); + return defs.length > 0 ? defs[0].templateRef : null; + }); + + constructor() { + // Watch for drag start/end to emit events + effect(() => { + const card = this.dragService.draggedCard(); + const isDragging = this.dragService.isDragging(); + if (isDragging && card) { + this.dragStart.emit({ + card, + columnId: this.dragService.sourceColumnId() ?? '', + swimlaneId: this.dragService.sourceSwimlaneId() ?? undefined, + }); + } + }); + } + + /** Get the header template for a specific column */ + protected getHeaderTemplate(columnId: string) { + const defs = this.headerDefs(); + // First check for column-specific template + const specific = defs.find(d => d.kbColumnHeaderDef() === columnId); + if (specific) return specific.templateRef; + // Then check for generic template (empty string) + const generic = defs.find(d => !d.kbColumnHeaderDef()); + return generic?.templateRef ?? null; + } + + /** Get cards for a specific column (optionally within a swimlane) */ + protected getColumnCards(columnId: string, swimlaneId?: string): KbCard[] { + return this.boardService.getCardsForColumn(this.filteredCards(), columnId, swimlaneId); + } + + /** Get total card count for a swimlane */ + protected getSwimlaneCardCount(swimlaneId: string): number { + return this.filteredCards().filter(c => c.swimlaneId === swimlaneId).length; + } + + /** Check if a column is collapsed */ + protected isColumnCollapsed(columnId: string): boolean { + return this.boardService.isColumnCollapsed(columnId); + } + + /** Check if a swimlane is collapsed */ + protected isSwimlaneCollapsed(swimlaneId: string): boolean { + return this.boardService.isSwimlaneCollapsed(swimlaneId); + } + + // --- Event handlers --- + protected onCardMove(event: KbCardMoveEvent): void { + // Check WIP limits on target column + const targetCol = this.columns().find(c => c.id === event.toColumnId); + if (targetCol && event.fromColumnId !== event.toColumnId) { + const currentCount = this.getColumnCards(event.toColumnId).length; + if (isWipExceeded(currentCount + 1, targetCol.wipLimit)) { + this.wipExceeded.emit({ + column: targetCol, + cardCount: currentCount + 1, + wipLimit: targetCol.wipLimit!, + }); + } + } + + this.cardMove.emit(event); + this.dragEnd.emit({ card: event.card, dropped: true }); + } + + protected onCardClick(event: KbCardClickEvent): void { + this.cardClick.emit(event); + } + + protected onCardDblClick(event: KbCardDblClickEvent): void { + this.cardDblClick.emit(event); + } + + protected onCardContextMenu(event: KbCardContextMenuEvent): void { + this.cardContextMenu.emit(event); + } + + protected onCardAdd(event: KbCardAddEvent): void { + this.cardAdd.emit(event); + } + + protected onColumnClick(event: KbColumnClickEvent): void { + this.columnClick.emit(event); + } + + protected onColumnToggle(event: KbColumnToggleEvent): void { + this.boardService.toggleColumn(event.column.id); + this.columnToggle.emit(event); + } + + protected onSwimlaneToggle(event: KbSwimlaneToggleEvent): void { + this.boardService.toggleSwimlane(event.swimlane.id); + this.swimlaneToggle.emit(event); + } + + protected onViewChange(event: KbViewChangeEvent): void { + this.viewChange.emit(event); + } + + protected onSortChange(event: KbSortChangeEvent): void { + this.boardService.setSort(event.sort); + this.sortChange.emit(event); + } + + protected onSearchChange(term: string): void { + this.filterService.setSearch(term); + this.filterChange.emit({ filter: this.filterService.currentFilter() }); + } + + // --- Column reorder DnD --- + protected onColumnDragOver(event: DragEvent): void { + if (!this.dragService.isDraggingColumn()) return; + event.preventDefault(); + } + + protected onColumnDrop(event: DragEvent, targetColumnId: string): void { + if (!this.dragService.isDraggingColumn()) return; + event.preventDefault(); + + const reorderEvent = this.dragService.dropColumn(this.columns(), targetColumnId); + if (reorderEvent) { + this.columnReorder.emit(reorderEvent); + } + } +} diff --git a/src/components/kb-card-def/index.ts b/src/components/kb-card-def/index.ts new file mode 100644 index 0000000..3dc8df2 --- /dev/null +++ b/src/components/kb-card-def/index.ts @@ -0,0 +1 @@ +export * from './kb-card-def.directive'; diff --git a/src/components/kb-card-def/kb-card-def.directive.ts b/src/components/kb-card-def/kb-card-def.directive.ts new file mode 100644 index 0000000..0a36593 --- /dev/null +++ b/src/components/kb-card-def/kb-card-def.directive.ts @@ -0,0 +1,27 @@ +import { Directive, TemplateRef, inject } from '@angular/core'; +import type { KbCard, KbPriorityMeta } from '../../types/kanban.types'; + +/** Template context for custom card rendering */ +export interface KbCardDefContext { + $implicit: KbCard; + card: KbCard; + index: number; + isDragging: boolean; + isOverdue: boolean; + priorityMeta: KbPriorityMeta | undefined; +} + +@Directive({ + selector: 'ng-template[kbCardDef]', + standalone: true, +}) +export class KbCardDefDirective { + readonly templateRef = inject(TemplateRef); + + static ngTemplateContextGuard( + _dir: KbCardDefDirective, + ctx: unknown, + ): ctx is KbCardDefContext { + return true; + } +} diff --git a/src/components/kb-card/index.ts b/src/components/kb-card/index.ts new file mode 100644 index 0000000..affd7a0 --- /dev/null +++ b/src/components/kb-card/index.ts @@ -0,0 +1 @@ +export * from './kb-card.component'; diff --git a/src/components/kb-card/kb-card.component.html b/src/components/kb-card/kb-card.component.html new file mode 100644 index 0000000..58f0436 --- /dev/null +++ b/src/components/kb-card/kb-card.component.html @@ -0,0 +1,137 @@ +@if (customTemplate()) { + +} @else { +
+ + @if (card().coverImage) { +
+ +
+ } + + + @if (card().labels?.length) { +
+ @for (label of card().labels; track label.id) { + + {{ label.text }} + + } +
+ } + + +
{{ card().title }}
+ + + @if (card().description) { +
+ {{ card().description }} +
+ } + + + +
+} diff --git a/src/components/kb-card/kb-card.component.scss b/src/components/kb-card/kb-card.component.scss new file mode 100644 index 0000000..3b81d12 --- /dev/null +++ b/src/components/kb-card/kb-card.component.scss @@ -0,0 +1,221 @@ +:host { + display: block; + min-width: 0; + max-width: 100%; +} + +.kb-card { + background: var(--kb-card-bg); + border: 1px solid var(--kb-card-border); + border-radius: var(--kb-card-radius); + padding: var(--kb-card-padding); + box-shadow: var(--kb-card-shadow); + cursor: grab; + transition: + box-shadow 200ms var(--kb-ease-smooth), + transform 250ms var(--kb-ease-spring), + opacity 200ms var(--kb-ease-smooth), + border-color var(--kb-transition); + display: flex; + flex-direction: column; + gap: var(--kb-card-gap); + min-width: 0; + max-width: 100%; + overflow: hidden; + overflow-wrap: break-word; + word-break: break-word; + + &:hover { + transform: translateY(-1px); + box-shadow: var(--kb-card-shadow-hover); + } + + &--dragging { + transform: scale(var(--kb-card-drag-scale)) rotate(var(--kb-card-drag-rotate)); + box-shadow: var(--kb-card-shadow-dragging); + opacity: var(--kb-card-dragging-opacity); + cursor: grabbing; + z-index: 100; + } + + &--blocked { + border-left: 3px solid var(--kb-card-blocked-border); + } + + &--overdue { + // Overdue styling handled via due date element + } +} + +.kb-card__cover { + margin: calc(var(--kb-card-padding) * -1) calc(var(--kb-card-padding) * -1) 0; + border-radius: var(--kb-card-radius) var(--kb-card-radius) 0 0; + overflow: hidden; + max-height: 120px; + + img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + } +} + +.kb-card__labels { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; +} + +.kb-card__label { + display: inline-block; + padding: var(--kb-label-padding); + border-radius: var(--kb-label-radius); + font-size: var(--kb-label-font-size); + font-weight: 600; + line-height: 1.4; + text-transform: uppercase; + letter-spacing: 0.025em; +} + +.kb-card__title { + font-size: var(--kb-card-title-font-size); + font-weight: var(--kb-card-title-font-weight); + color: var(--kb-card-title-color); + line-height: 1.4; + overflow-wrap: break-word; + word-break: break-word; + min-width: 0; +} + +.kb-card__description { + display: -webkit-box; + -webkit-box-orient: vertical; + overflow: hidden; + font-size: var(--kb-card-desc-font-size); + color: var(--kb-card-desc-color); + line-height: 1.5; + overflow-wrap: break-word; + word-break: break-word; + min-width: 0; +} + +.kb-card__footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; + margin-top: 0.25rem; + padding-top: 0.375rem; +} + +.kb-card__meta { + display: flex; + align-items: center; + gap: 0.625rem; + font-size: var(--kb-card-meta-font-size); + color: var(--kb-card-meta-color); +} + +.kb-card__priority { + display: inline-flex; + align-items: center; + line-height: 1; + + svg { + width: 0.875rem; + height: 0.875rem; + } +} + +.kb-card__due { + &--overdue { + color: var(--kb-card-overdue-color); + font-weight: 500; + } +} + +.kb-card__points { + padding: 0.0625rem 0.375rem; + border-radius: 999px; + background: var(--kb-bg); + font-weight: 500; + font-variant-numeric: tabular-nums; +} + +.kb-card__subtasks { + display: flex; + align-items: center; + gap: 0.375rem; +} + +.kb-card__subtasks-bar { + width: 3rem; + height: 3px; + border-radius: 2px; + background: var(--kb-bg); + overflow: hidden; +} + +.kb-card__subtasks-fill { + height: 100%; + border-radius: 2px; + background: var(--color-primary-500, #3b82f6); + transition: width 300ms var(--kb-ease-smooth); +} + +.kb-card__subtasks-text { + font-variant-numeric: tabular-nums; +} + +.kb-card__blocked { + display: inline-flex; + align-items: center; + + svg { + width: 0.875rem; + height: 0.875rem; + } +} + +.kb-card__assignees { + display: flex; + align-items: center; + flex-shrink: 0; + + &:hover .kb-card__avatar + .kb-card__avatar { + margin-left: 0.125rem; + } +} + +.kb-card__avatar { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.5rem; + height: 1.5rem; + border-radius: 50%; + background: var(--color-primary-100, #dbeafe); + color: var(--color-primary-700, #1d4ed8); + font-size: 0.625rem; + font-weight: 600; + border: 2px solid var(--kb-card-bg); + overflow: hidden; + transition: margin 200ms var(--kb-ease-spring); + + & + & { + margin-left: -0.375rem; + } + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + + &--more { + background: var(--kb-card-meta-color); + color: #fff; + font-size: 0.5rem; + } +} diff --git a/src/components/kb-card/kb-card.component.ts b/src/components/kb-card/kb-card.component.ts new file mode 100644 index 0000000..8f24b98 --- /dev/null +++ b/src/components/kb-card/kb-card.component.ts @@ -0,0 +1,102 @@ +import { + Component, ChangeDetectionStrategy, input, output, computed, inject, + TemplateRef, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import type { KbCard, KbPriorityMeta } from '../../types/kanban.types'; +import type { KbCardClickEvent, KbCardDblClickEvent, KbCardContextMenuEvent } from '../../types/event.types'; +import { KANBAN_CONFIG } from '../../providers/kanban-config.provider'; +import { KanbanDragService } from '../../services/kanban-drag.service'; +import { isOverdue, formatCardDate } from '../../utils/date.utils'; +import type { KbCardDefContext } from '../kb-card-def/kb-card-def.directive'; + +@Component({ + selector: 'kb-card', + standalone: true, + imports: [CommonModule], + templateUrl: './kb-card.component.html', + styleUrl: './kb-card.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class KbCardComponent { + private config = inject(KANBAN_CONFIG); + private dragService = inject(KanbanDragService); + + readonly card = input.required(); + readonly columnId = input.required(); + readonly index = input(0); + readonly swimlaneId = input(); + readonly customTemplate = input | null>(null); + + readonly cardClick = output(); + readonly cardDblClick = output(); + readonly cardContextMenu = output(); + + readonly priorityMeta = computed(() => { + const p = this.card().priority; + if (!p) return undefined; + return this.config.priorities.find(pm => pm.level === p); + }); + + readonly isCardOverdue = computed(() => isOverdue(this.card().dueDate)); + + readonly formattedDueDate = computed(() => + formatCardDate(this.card().dueDate, this.config.locale) + ); + + readonly isDragging = computed(() => + this.dragService.draggedCard()?.id === this.card().id + ); + + readonly descriptionLines = this.config.cardDescriptionLines; + + readonly templateContext = computed(() => ({ + $implicit: this.card(), + card: this.card(), + index: this.index(), + isDragging: this.isDragging(), + isOverdue: this.isCardOverdue(), + priorityMeta: this.priorityMeta(), + })); + + protected onDragStart(event: DragEvent): void { + if (!event.dataTransfer) return; + event.dataTransfer.effectAllowed = 'move'; + event.dataTransfer.setData('text/plain', this.card().id); + + // Custom ghost image + const el = event.target as HTMLElement; + const ghost = el.cloneNode(true) as HTMLElement; + ghost.style.width = `${el.offsetWidth}px`; + ghost.style.opacity = '0.8'; + ghost.style.position = 'absolute'; + ghost.style.top = '-9999px'; + document.body.appendChild(ghost); + event.dataTransfer.setDragImage(ghost, event.offsetX, event.offsetY); + setTimeout(() => document.body.removeChild(ghost)); + + this.dragService.startDrag( + this.card(), + this.columnId(), + this.index(), + this.swimlaneId(), + ); + } + + protected onDragEnd(): void { + this.dragService.endDrag(); + } + + protected onClick(event: MouseEvent): void { + this.cardClick.emit({ card: this.card(), columnId: this.columnId(), event }); + } + + protected onDblClick(event: MouseEvent): void { + this.cardDblClick.emit({ card: this.card(), columnId: this.columnId(), event }); + } + + protected onContextMenu(event: MouseEvent): void { + event.preventDefault(); + this.cardContextMenu.emit({ card: this.card(), columnId: this.columnId(), event }); + } +} diff --git a/src/components/kb-column-header-def/index.ts b/src/components/kb-column-header-def/index.ts new file mode 100644 index 0000000..04a26ea --- /dev/null +++ b/src/components/kb-column-header-def/index.ts @@ -0,0 +1 @@ +export * from './kb-column-header-def.directive'; diff --git a/src/components/kb-column-header-def/kb-column-header-def.directive.ts b/src/components/kb-column-header-def/kb-column-header-def.directive.ts new file mode 100644 index 0000000..a769aee --- /dev/null +++ b/src/components/kb-column-header-def/kb-column-header-def.directive.ts @@ -0,0 +1,27 @@ +import { Directive, TemplateRef, inject, input } from '@angular/core'; +import type { KbColumn, KbWipStatus } from '../../types/kanban.types'; + +/** Template context for custom column header rendering */ +export interface KbColumnHeaderDefContext { + $implicit: KbColumn; + column: KbColumn; + cardCount: number; + wipStatus: KbWipStatus; +} + +@Directive({ + selector: 'ng-template[kbColumnHeaderDef]', + standalone: true, +}) +export class KbColumnHeaderDefDirective { + /** Optionally bind to a specific column ID */ + readonly kbColumnHeaderDef = input(''); + readonly templateRef = inject(TemplateRef); + + static ngTemplateContextGuard( + _dir: KbColumnHeaderDefDirective, + ctx: unknown, + ): ctx is KbColumnHeaderDefContext { + return true; + } +} diff --git a/src/components/kb-column/index.ts b/src/components/kb-column/index.ts new file mode 100644 index 0000000..8beb6ba --- /dev/null +++ b/src/components/kb-column/index.ts @@ -0,0 +1 @@ +export * from './kb-column.component'; diff --git a/src/components/kb-column/kb-column.component.html b/src/components/kb-column/kb-column.component.html new file mode 100644 index 0000000..21bd538 --- /dev/null +++ b/src/components/kb-column/kb-column.component.html @@ -0,0 +1,99 @@ +
+ + @if (column().color) { +
+ } + + + @if (headerTemplate()) { + + } @else { +
+
+ {{ column().title }} + @if (showCardCount()) { + {{ cards().length }} + } + @if (showWipLimits() && column().wipLimit) { + + } +
+
+ +
+
+ } + + + @if (!collapsed()) { +
+ @for (card of cards(); track card.id; let i = $index) { + + } @empty { +
+ @if (isOver()) { +
Drop here
+ } @else { + No cards + } +
+ } + + + @if (isOver() && cards().length > 0) { +
+ } +
+ + + @if (allowQuickAdd() && column().allowAdd !== false) { + + } + } +
diff --git a/src/components/kb-column/kb-column.component.scss b/src/components/kb-column/kb-column.component.scss new file mode 100644 index 0000000..f0d76fd --- /dev/null +++ b/src/components/kb-column/kb-column.component.scss @@ -0,0 +1,185 @@ +:host { + display: block; + flex-shrink: 0; + width: fit-content; +} + +.kb-column { + background: transparent; + border: none; + border-radius: var(--kb-column-radius); + width: var(--kb-column-width, 380px); + min-width: var(--kb-column-min-width, 340px); + max-width: var(--kb-column-max-width, 480px); + display: flex; + flex-direction: column; + max-height: 100%; + overflow: hidden; + transition: + width var(--kb-transition-slow), + min-width var(--kb-transition-slow), + border-color 200ms var(--kb-ease-smooth), + box-shadow 200ms var(--kb-ease-smooth); + + &--collapsed { + width: var(--kb-column-collapsed-width); + min-width: var(--kb-column-collapsed-width); + max-width: var(--kb-column-collapsed-width); + overflow: hidden; + } + + &--over { + border-color: var(--kb-drop-zone-border); + background: var(--kb-drop-zone-bg); + box-shadow: + 0 0 0 1px var(--kb-drop-zone-border), + 0 0 var(--kb-glow-spread) rgba(59, 130, 246, var(--kb-glow-opacity)); + } + + &--wip-exceeded { + border-color: var(--kb-wip-exceeded-color); + } +} + +.kb-column__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--kb-column-header-padding); + cursor: pointer; + user-select: none; + flex-shrink: 0; +} + +.kb-column__header-left { + display: flex; + align-items: center; + gap: 0.625rem; + min-width: 0; +} + +.kb-column__header-right { + display: flex; + align-items: center; + gap: 0.25rem; + flex-shrink: 0; +} + +.kb-column__accent-bar { + height: var(--kb-column-accent-height); + border-radius: var(--kb-column-radius) var(--kb-column-radius) 0 0; + flex-shrink: 0; +} + +.kb-column__title { + font-size: var(--kb-column-header-font-size); + font-weight: var(--kb-column-header-font-weight); + color: var(--kb-column-header-color); + text-transform: uppercase; + letter-spacing: 0.05em; + font-size: 0.75rem; + overflow-wrap: break-word; + word-break: break-word; +} + +.kb-column__count { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 1.25rem; + height: 1.25rem; + padding: 0 0.375rem; + border-radius: 999px; + background: var(--kb-bg); + color: var(--kb-card-meta-color); + font-size: var(--font-size-xs, 0.75rem); + font-weight: 500; + font-variant-numeric: tabular-nums; + flex-shrink: 0; +} + +.kb-column__collapse-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.5rem; + height: 1.5rem; + border: none; + background: transparent; + color: var(--kb-card-meta-color); + cursor: pointer; + border-radius: 999px; + transition: background var(--kb-transition), transform 200ms var(--kb-ease-spring); + + &:hover { + background: var(--kb-quick-add-hover-bg); + } + + svg { + width: 0.75rem; + height: 0.75rem; + transition: transform 200ms var(--kb-ease-spring); + } +} + +.kb-column__body { + flex: 1; + overflow-x: hidden; + overflow-y: auto; + padding: 0.25rem var(--kb-column-padding) var(--kb-column-padding); + display: flex; + flex-direction: column; + gap: 0.875rem; + min-height: 2rem; + min-width: 0; + + &::-webkit-scrollbar { + width: 4px; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: var(--kb-card-meta-color); + border-radius: 4px; + + &:hover { + background: var(--kb-card-desc-color); + } + } + + scrollbar-width: thin; + scrollbar-color: var(--kb-card-meta-color) transparent; +} + +.kb-column__empty { + display: flex; + align-items: center; + justify-content: center; + min-height: 4rem; +} + +.kb-column__empty-text { + font-size: var(--font-size-sm, 0.875rem); + color: var(--kb-card-meta-color); +} + +.kb-column__drop-placeholder { + padding: 1rem; + border: 2px dashed var(--kb-drop-zone-border); + border-radius: var(--kb-card-radius); + background: var(--kb-drop-zone-bg); + color: var(--color-primary-500, #3b82f6); + font-size: var(--font-size-sm, 0.875rem); + text-align: center; + width: 100%; +} + +.kb-column__drop-indicator { + height: 2px; + background: var(--kb-drop-indicator-color); + border-radius: 1px; + margin: -0.25rem 0; +} diff --git a/src/components/kb-column/kb-column.component.ts b/src/components/kb-column/kb-column.component.ts new file mode 100644 index 0000000..d3c674a --- /dev/null +++ b/src/components/kb-column/kb-column.component.ts @@ -0,0 +1,147 @@ +import { + Component, ChangeDetectionStrategy, input, output, computed, inject, + ElementRef, viewChild, TemplateRef, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import type { KbCard, KbColumn, KbWipStatus } from '../../types/kanban.types'; +import type { + KbCardMoveEvent, KbCardClickEvent, KbCardDblClickEvent, + KbCardContextMenuEvent, KbCardAddEvent, KbColumnClickEvent, + KbColumnToggleEvent, +} from '../../types/event.types'; +import { KANBAN_CONFIG } from '../../providers/kanban-config.provider'; +import { KanbanDragService } from '../../services/kanban-drag.service'; +import { KanbanBoardService } from '../../services/kanban-board.service'; +import { KbCardComponent } from '../kb-card/kb-card.component'; +import { KbQuickAddComponent } from '../kb-quick-add/kb-quick-add.component'; +import { KbWipIndicatorComponent } from '../kb-wip-indicator/kb-wip-indicator.component'; +import type { KbCardDefContext } from '../kb-card-def/kb-card-def.directive'; +import type { KbColumnHeaderDefContext } from '../kb-column-header-def/kb-column-header-def.directive'; + +@Component({ + selector: 'kb-column', + standalone: true, + imports: [CommonModule, KbCardComponent, KbQuickAddComponent, KbWipIndicatorComponent], + templateUrl: './kb-column.component.html', + styleUrl: './kb-column.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class KbColumnComponent { + private config = inject(KANBAN_CONFIG); + private dragService = inject(KanbanDragService); + private boardService = inject(KanbanBoardService); + + readonly column = input.required(); + readonly cards = input([]); + readonly collapsed = input(false); + readonly allowReorder = input(false); + readonly allowQuickAdd = input(true); + readonly showWipLimits = input(true); + readonly showCardCount = input(true); + readonly swimlaneId = input(); + readonly cardTemplate = input | null>(null); + readonly headerTemplate = input | null>(null); + + readonly cardMove = output(); + readonly cardClick = output(); + readonly cardDblClick = output(); + readonly cardContextMenu = output(); + readonly cardAdd = output(); + readonly columnClick = output(); + readonly columnToggle = output(); + + private cardListRef = viewChild>('cardList'); + + readonly isOver = computed(() => this.dragService.isOverColumn(this.column().id)); + + readonly wipStatus = computed(() => + this.boardService.getWipStatus(this.cards().length, this.column().wipLimit) + ); + + readonly headerContext = computed(() => ({ + $implicit: this.column(), + column: this.column(), + cardCount: this.cards().length, + wipStatus: this.wipStatus(), + })); + + protected onHeaderClick(event: MouseEvent): void { + this.columnClick.emit({ column: this.column(), event }); + } + + protected onToggleCollapse(): void { + this.columnToggle.emit({ column: this.column(), collapsed: !this.collapsed() }); + } + + // --- Card DnD handlers --- + protected onDragOver(event: DragEvent): void { + event.preventDefault(); + if (!event.dataTransfer) return; + event.dataTransfer.dropEffect = 'move'; + + const listEl = this.cardListRef()?.nativeElement; + if (!listEl) return; + + const cardElements = Array.from(listEl.querySelectorAll('.kb-card')); + const dropIdx = this.dragService.calculateDropIndex(event, cardElements); + this.dragService.dragOverColumn(this.column().id, dropIdx); + } + + protected onDragEnter(event: DragEvent): void { + event.preventDefault(); + } + + protected onDragLeave(event: DragEvent): void { + // Only handle if actually leaving the column + const el = event.currentTarget as HTMLElement; + const related = event.relatedTarget as Node | null; + if (related && el.contains(related)) return; + + this.dragService.dragLeaveColumn(this.column().id); + } + + protected onDrop(event: DragEvent): void { + event.preventDefault(); + const dropIdx = this.dragService.dropIndex(); + const targetIdx = dropIdx >= 0 ? dropIdx : this.cards().length; + + const moveEvent = this.dragService.drop( + this.column().id, + targetIdx, + this.swimlaneId(), + ); + + if (moveEvent) { + this.cardMove.emit(moveEvent); + } + } + + // --- Column DnD (for reorder) --- + protected onColumnDragStart(event: DragEvent): void { + if (!this.allowReorder()) return; + if (!event.dataTransfer) return; + event.dataTransfer.effectAllowed = 'move'; + event.dataTransfer.setData('text/plain', `column:${this.column().id}`); + this.dragService.startColumnDrag(this.column().id); + } + + protected onColumnDragEnd(): void { + this.dragService.endDrag(); + } + + protected onCardClick(event: KbCardClickEvent): void { + this.cardClick.emit(event); + } + + protected onCardDblClick(event: KbCardDblClickEvent): void { + this.cardDblClick.emit(event); + } + + protected onCardContextMenu(event: KbCardContextMenuEvent): void { + this.cardContextMenu.emit(event); + } + + protected onCardAdd(event: KbCardAddEvent): void { + this.cardAdd.emit(event); + } +} diff --git a/src/components/kb-quick-add/index.ts b/src/components/kb-quick-add/index.ts new file mode 100644 index 0000000..010e9b0 --- /dev/null +++ b/src/components/kb-quick-add/index.ts @@ -0,0 +1 @@ +export * from './kb-quick-add.component'; diff --git a/src/components/kb-quick-add/kb-quick-add.component.html b/src/components/kb-quick-add/kb-quick-add.component.html new file mode 100644 index 0000000..67994f3 --- /dev/null +++ b/src/components/kb-quick-add/kb-quick-add.component.html @@ -0,0 +1,25 @@ +@if (isEditing()) { +
+ +
+} @else { + +} diff --git a/src/components/kb-quick-add/kb-quick-add.component.scss b/src/components/kb-quick-add/kb-quick-add.component.scss new file mode 100644 index 0000000..90f01c1 --- /dev/null +++ b/src/components/kb-quick-add/kb-quick-add.component.scss @@ -0,0 +1,58 @@ +:host { + display: block; +} + +.kb-quick-add__trigger { + display: flex; + align-items: center; + gap: 0.5rem; + width: 100%; + padding: 0.75rem var(--kb-column-padding, 1rem); + border: none; + background: var(--kb-quick-add-bg); + color: var(--kb-quick-add-text); + font-size: var(--font-size-sm, 0.875rem); + cursor: pointer; + border-radius: 0 0 var(--kb-column-radius, 0.75rem) var(--kb-column-radius, 0.75rem); + transition: background 200ms var(--kb-ease-smooth); + + &:hover { + background: var(--kb-quick-add-hover-bg); + } +} + +.kb-quick-add__icon { + display: inline-flex; + align-items: center; + justify-content: center; + + svg { + width: 1rem; + height: 1rem; + } +} + +.kb-quick-add__form { + padding: 0.625rem var(--kb-column-padding, 1rem) 0.75rem; +} + +.kb-quick-add__input { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid var(--kb-toolbar-border); + border-radius: 0.5rem; + background: var(--kb-card-bg); + color: var(--kb-card-title-color); + font-size: var(--font-size-sm, 0.875rem); + outline: none; + transition: border-color 200ms var(--kb-ease-smooth), box-shadow 200ms var(--kb-ease-smooth); + + &:focus { + border-color: var(--kb-drop-zone-border); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); + } + + &::placeholder { + color: var(--kb-quick-add-text); + } +} diff --git a/src/components/kb-quick-add/kb-quick-add.component.ts b/src/components/kb-quick-add/kb-quick-add.component.ts new file mode 100644 index 0000000..32ad36d --- /dev/null +++ b/src/components/kb-quick-add/kb-quick-add.component.ts @@ -0,0 +1,61 @@ +import { + Component, ChangeDetectionStrategy, input, output, signal, + ElementRef, viewChild, +} from '@angular/core'; +import type { KbCardAddEvent } from '../../types/event.types'; + +@Component({ + selector: 'kb-quick-add', + standalone: true, + templateUrl: './kb-quick-add.component.html', + styleUrl: './kb-quick-add.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class KbQuickAddComponent { + readonly columnId = input.required(); + readonly swimlaneId = input(); + + readonly cardAdd = output(); + + readonly isEditing = signal(false); + readonly inputValue = signal(''); + + private inputRef = viewChild>('addInput'); + + protected startEditing(): void { + this.isEditing.set(true); + this.inputValue.set(''); + // Focus after view update + setTimeout(() => this.inputRef()?.nativeElement.focus()); + } + + protected onKeydown(event: KeyboardEvent): void { + if (event.key === 'Enter') { + event.preventDefault(); + this.submit(); + } else if (event.key === 'Escape') { + this.cancel(); + } + } + + protected onInput(event: Event): void { + this.inputValue.set((event.target as HTMLInputElement).value); + } + + protected submit(): void { + const title = this.inputValue().trim(); + if (title) { + this.cardAdd.emit({ + title, + columnId: this.columnId(), + swimlaneId: this.swimlaneId(), + }); + } + this.cancel(); + } + + protected cancel(): void { + this.isEditing.set(false); + this.inputValue.set(''); + } +} diff --git a/src/components/kb-swimlane/index.ts b/src/components/kb-swimlane/index.ts new file mode 100644 index 0000000..55f6b1e --- /dev/null +++ b/src/components/kb-swimlane/index.ts @@ -0,0 +1 @@ +export * from './kb-swimlane.component'; diff --git a/src/components/kb-swimlane/kb-swimlane.component.html b/src/components/kb-swimlane/kb-swimlane.component.html new file mode 100644 index 0000000..f33ad66 --- /dev/null +++ b/src/components/kb-swimlane/kb-swimlane.component.html @@ -0,0 +1,27 @@ +
+ + + @if (!collapsed()) { +
+ +
+ } +
diff --git a/src/components/kb-swimlane/kb-swimlane.component.scss b/src/components/kb-swimlane/kb-swimlane.component.scss new file mode 100644 index 0000000..5c1ffb7 --- /dev/null +++ b/src/components/kb-swimlane/kb-swimlane.component.scss @@ -0,0 +1,109 @@ +:host { + display: block; +} + +.kb-swimlane { + border-bottom: 1px solid var(--kb-swimlane-border); + + &:last-child { + border-bottom: none; + } +} + +.kb-swimlane__header { + display: flex; + align-items: center; + gap: 0.625rem; + width: 100%; + padding: var(--kb-swimlane-header-padding); + border: none; + background: var(--kb-glass-bg); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + cursor: pointer; + text-align: left; + border-radius: 0.5rem; + transition: background 200ms var(--kb-ease-smooth); + + &:hover { + background: var(--kb-quick-add-hover-bg); + } + + &:disabled { + cursor: default; + &:hover { + background: var(--kb-glass-bg); + } + } +} + +.kb-swimlane__toggle { + display: inline-flex; + align-items: center; + color: var(--kb-card-meta-color); + transition: transform 200ms var(--kb-ease-spring); + + svg { + width: 0.75rem; + height: 0.75rem; + } +} + +.kb-swimlane--collapsed .kb-swimlane__toggle { + transform: rotate(-90deg); +} + +.kb-swimlane__color-bar { + width: 3px; + height: 1rem; + border-radius: 2px; + flex-shrink: 0; +} + +.kb-swimlane__title { + font-size: var(--kb-swimlane-header-font-size); + font-weight: var(--kb-swimlane-header-font-weight); + color: var(--kb-swimlane-header-color); +} + +.kb-swimlane__count { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 1.25rem; + height: 1.25rem; + padding: 0 0.375rem; + border-radius: 999px; + background: var(--kb-bg); + color: var(--kb-card-meta-color); + font-size: var(--font-size-xs, 0.75rem); + font-weight: 500; + font-variant-numeric: tabular-nums; +} + +.kb-swimlane__body { + display: flex; + gap: var(--kb-column-gap); + overflow-x: auto; + padding: 0.75rem 0.25rem 1rem; + + &::-webkit-scrollbar { + height: 4px; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: var(--kb-card-meta-color); + border-radius: 4px; + + &:hover { + background: var(--kb-card-desc-color); + } + } + + scrollbar-width: thin; + scrollbar-color: var(--kb-card-meta-color) transparent; +} diff --git a/src/components/kb-swimlane/kb-swimlane.component.ts b/src/components/kb-swimlane/kb-swimlane.component.ts new file mode 100644 index 0000000..01158f5 --- /dev/null +++ b/src/components/kb-swimlane/kb-swimlane.component.ts @@ -0,0 +1,24 @@ +import { Component, ChangeDetectionStrategy, input, output } from '@angular/core'; +import type { KbSwimlane } from '../../types/kanban.types'; +import type { KbSwimlaneToggleEvent } from '../../types/event.types'; + +@Component({ + selector: 'kb-swimlane', + standalone: true, + templateUrl: './kb-swimlane.component.html', + styleUrl: './kb-swimlane.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class KbSwimlaneComponent { + readonly swimlane = input.required(); + readonly collapsed = input(false); + readonly cardCount = input(0); + + readonly swimlaneToggle = output(); + + protected toggle(): void { + const sl = this.swimlane(); + if (sl.collapsible === false) return; + this.swimlaneToggle.emit({ swimlane: sl, collapsed: !this.collapsed() }); + } +} diff --git a/src/components/kb-toolbar/index.ts b/src/components/kb-toolbar/index.ts new file mode 100644 index 0000000..518e0af --- /dev/null +++ b/src/components/kb-toolbar/index.ts @@ -0,0 +1 @@ +export * from './kb-toolbar.component'; diff --git a/src/components/kb-toolbar/kb-toolbar.component.html b/src/components/kb-toolbar/kb-toolbar.component.html new file mode 100644 index 0000000..44005a1 --- /dev/null +++ b/src/components/kb-toolbar/kb-toolbar.component.html @@ -0,0 +1,87 @@ +
+ @if (searchable()) { + + } + +
+ +
+ + @if (showSortMenu()) { +
+ @for (opt of sortOptions; track opt.field) { + + } +
+ } +
+ + @if (filterCount() > 0) { + {{ filterCount() }} + } + + @if (showViewToggle()) { + + + } +
+
diff --git a/src/components/kb-toolbar/kb-toolbar.component.scss b/src/components/kb-toolbar/kb-toolbar.component.scss new file mode 100644 index 0000000..8ba84d9 --- /dev/null +++ b/src/components/kb-toolbar/kb-toolbar.component.scss @@ -0,0 +1,175 @@ +:host { + display: block; +} + +.kb-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: var(--kb-toolbar-padding); + background: var(--kb-glass-bg); + backdrop-filter: blur(var(--kb-glass-blur)); + -webkit-backdrop-filter: blur(var(--kb-glass-blur)); + border: 1px solid var(--kb-glass-border); + border-bottom: 1px solid var(--kb-toolbar-border); + border-radius: var(--kb-column-radius) var(--kb-column-radius) 0 0; +} + +.kb-toolbar__search { + display: flex; + align-items: center; + gap: 0.5rem; + flex: 1; + max-width: 320px; + padding: 0.375rem 0.75rem; + background: var(--kb-bg); + border: 1px solid var(--kb-toolbar-border); + border-radius: 999px; + transition: border-color 200ms var(--kb-ease-smooth), box-shadow 200ms var(--kb-ease-smooth); + + &:focus-within { + border-color: var(--kb-drop-zone-border); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); + } +} + +.kb-toolbar__search-icon { + display: inline-flex; + color: var(--kb-card-meta-color); + flex-shrink: 0; + + svg { + width: 0.875rem; + height: 0.875rem; + } +} + +.kb-toolbar__search-input { + flex: 1; + border: none; + background: transparent; + color: var(--kb-card-title-color); + font-size: var(--font-size-sm, 0.875rem); + outline: none; + + &::placeholder { + color: var(--kb-quick-add-text); + } +} + +.kb-toolbar__actions { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.kb-toolbar__btn { + padding: 0.375rem 0.875rem; + border: 1px solid var(--kb-toolbar-border); + border-radius: 999px; + background: transparent; + color: var(--kb-card-desc-color); + font-size: var(--font-size-sm, 0.875rem); + cursor: pointer; + transition: all 200ms var(--kb-ease-smooth); + + &:hover { + background: var(--kb-quick-add-hover-bg); + color: var(--kb-card-title-color); + } + + &--active { + background: var(--color-primary-50, #eff6ff); + color: var(--color-primary-500, #3b82f6); + border-color: var(--color-primary-500, #3b82f6); + } +} + +.kb-toolbar__btn-icon { + display: inline-flex; + align-items: center; + gap: 0.375rem; + + svg { + width: 0.875rem; + height: 0.875rem; + } +} + +.kb-toolbar__sort { + position: relative; +} + +.kb-toolbar__sort-menu { + position: absolute; + top: calc(100% + 0.375rem); + right: 0; + z-index: 10; + min-width: 180px; + background: var(--kb-card-bg); + border: 1px solid var(--kb-card-border); + border-radius: var(--kb-card-radius); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.06); + overflow: hidden; + animation: kb-slide-down 150ms var(--kb-ease-spring); +} + +@keyframes kb-slide-down { + from { + opacity: 0; + transform: translateY(-4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.kb-toolbar__sort-option { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 0.5rem 0.75rem; + border: none; + background: transparent; + color: var(--kb-card-title-color); + font-size: var(--font-size-sm, 0.875rem); + cursor: pointer; + text-align: left; + transition: background var(--kb-transition); + + &:hover { + background: var(--kb-quick-add-hover-bg); + } + + &--active { + color: var(--color-primary-500, #3b82f6); + font-weight: 500; + } +} + +.kb-toolbar__sort-dir { + display: inline-flex; + + svg { + width: 0.625rem; + height: 0.625rem; + } +} + +.kb-toolbar__filter-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 1.25rem; + height: 1.25rem; + padding: 0 0.25rem; + border-radius: 999px; + background: var(--color-primary-500, #3b82f6); + color: #fff; + font-size: 0.625rem; + font-weight: 600; + font-variant-numeric: tabular-nums; +} diff --git a/src/components/kb-toolbar/kb-toolbar.component.ts b/src/components/kb-toolbar/kb-toolbar.component.ts new file mode 100644 index 0000000..6964c1d --- /dev/null +++ b/src/components/kb-toolbar/kb-toolbar.component.ts @@ -0,0 +1,60 @@ +import { Component, ChangeDetectionStrategy, input, output, signal } from '@angular/core'; +import type { KbViewMode, KbCardSort, KbCardSortField } from '../../types/kanban.types'; +import type { KbViewChangeEvent, KbSortChangeEvent } from '../../types/event.types'; + +@Component({ + selector: 'kb-toolbar', + standalone: true, + templateUrl: './kb-toolbar.component.html', + styleUrl: './kb-toolbar.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class KbToolbarComponent { + readonly viewMode = input('board'); + readonly searchable = input(true); + readonly showViewToggle = input(true); + readonly filterCount = input(0); + readonly currentSort = input(null); + + readonly searchChange = output(); + readonly viewChange = output(); + readonly sortChange = output(); + + readonly searchTerm = signal(''); + readonly showSortMenu = signal(false); + + readonly sortOptions: { field: KbCardSortField; label: string }[] = [ + { field: 'order', label: 'Manual order' }, + { field: 'priority', label: 'Priority' }, + { field: 'dueDate', label: 'Due date' }, + { field: 'title', label: 'Title' }, + { field: 'createdAt', label: 'Created date' }, + ]; + + protected onSearchInput(event: Event): void { + const value = (event.target as HTMLInputElement).value; + this.searchTerm.set(value); + this.searchChange.emit(value); + } + + protected toggleView(): void { + const next: KbViewMode = this.viewMode() === 'board' ? 'pipeline' : 'board'; + this.viewChange.emit({ view: next }); + } + + protected onSort(field: KbCardSortField): void { + const current = this.currentSort(); + let direction: 'asc' | 'desc' = 'asc'; + + if (current?.field === field) { + direction = current.direction === 'asc' ? 'desc' : 'asc'; + } + + this.sortChange.emit({ sort: { field, direction } }); + this.showSortMenu.set(false); + } + + protected toggleSortMenu(): void { + this.showSortMenu.update(v => !v); + } +} diff --git a/src/components/kb-wip-indicator/index.ts b/src/components/kb-wip-indicator/index.ts new file mode 100644 index 0000000..27cca72 --- /dev/null +++ b/src/components/kb-wip-indicator/index.ts @@ -0,0 +1 @@ +export * from './kb-wip-indicator.component'; diff --git a/src/components/kb-wip-indicator/kb-wip-indicator.component.ts b/src/components/kb-wip-indicator/kb-wip-indicator.component.ts new file mode 100644 index 0000000..d0df8b8 --- /dev/null +++ b/src/components/kb-wip-indicator/kb-wip-indicator.component.ts @@ -0,0 +1,71 @@ +import { Component, ChangeDetectionStrategy, input, computed } from '@angular/core'; +import type { KbWipStatus } from '../../types/kanban.types'; + +@Component({ + selector: 'kb-wip-indicator', + standalone: true, + template: ` + + {{ current() }} / {{ limit() }} + + `, + styles: [` + :host { display: inline-flex; } + + .kb-wip { + display: inline-flex; + align-items: center; + padding: 0.1875rem 0.625rem; + border-radius: 999px; + font-size: var(--font-size-xs, 0.75rem); + font-weight: 500; + font-variant-numeric: tabular-nums; + line-height: 1; + white-space: nowrap; + transition: box-shadow 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94); + } + + .kb-wip--normal { + color: var(--kb-wip-normal-color); + background: var(--kb-wip-normal-bg); + } + + .kb-wip--warning { + color: var(--kb-wip-warning-color); + background: var(--kb-wip-warning-bg); + box-shadow: 0 0 0 1px rgba(217, 119, 6, 0.2), 0 0 6px rgba(217, 119, 6, 0.15); + } + + .kb-wip--exceeded { + color: var(--kb-wip-exceeded-color); + background: var(--kb-wip-exceeded-bg); + font-weight: 600; + box-shadow: 0 0 0 1px rgba(239, 68, 68, 0.3), 0 0 8px rgba(239, 68, 68, 0.2); + animation: kb-wip-pulse 2s ease-in-out infinite; + } + + @keyframes kb-wip-pulse { + 0%, 100% { box-shadow: 0 0 0 1px rgba(239, 68, 68, 0.3), 0 0 8px rgba(239, 68, 68, 0.2); } + 50% { box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.4), 0 0 12px rgba(239, 68, 68, 0.3); } + } + `], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class KbWipIndicatorComponent { + readonly current = input.required(); + readonly limit = input.required(); + readonly status = input('normal'); + + readonly tooltipText = computed(() => { + const s = this.status(); + if (s === 'exceeded') return `WIP limit exceeded (${this.current()}/${this.limit()})`; + if (s === 'warning') return `Approaching WIP limit (${this.current()}/${this.limit()})`; + return `${this.current()} of ${this.limit()} WIP limit`; + }); +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..0eae092 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,48 @@ +/** + * Kanban Elements UI + * Main library entry point + * + * Angular components for kanban boards with drag-and-drop, swimlanes, + * WIP limits, pipeline views, and custom card templates powered by @sda/base-ui + * + * @example + * ```typescript + * import { Component, signal } from '@angular/core'; + * import { KbBoardComponent, KbCardDefDirective, type KbCard, type KbColumn } from '@sda/kanban-elements-ui'; + * + * @Component({ + * standalone: true, + * imports: [KbBoardComponent, KbCardDefDirective], + * template: ` + * + * + *
{{ card.title }}
+ *
+ *
+ * ` + * }) + * export class AppComponent { + * cards = signal([...]); + * columns: KbColumn[] = [ + * { id: 'todo', title: 'To Do', order: 0 }, + * { id: 'doing', title: 'In Progress', order: 1, wipLimit: 3 }, + * { id: 'done', title: 'Done', order: 2 }, + * ]; + * } + * ``` + */ + +// Types +export * from './types'; + +// Utils +export * from './utils'; + +// Providers +export * from './providers'; + +// Services +export * from './services'; + +// Components +export * from './components'; diff --git a/src/providers/index.ts b/src/providers/index.ts new file mode 100644 index 0000000..51e061b --- /dev/null +++ b/src/providers/index.ts @@ -0,0 +1 @@ +export * from './kanban-config.provider'; diff --git a/src/providers/kanban-config.provider.ts b/src/providers/kanban-config.provider.ts new file mode 100644 index 0000000..5679652 --- /dev/null +++ b/src/providers/kanban-config.provider.ts @@ -0,0 +1,35 @@ +import { InjectionToken, makeEnvironmentProviders, EnvironmentProviders } from '@angular/core'; +import type { KbConfig } from '../types/config.types'; +import type { KbPriorityMeta } from '../types/kanban.types'; + +const DEFAULT_PRIORITIES: KbPriorityMeta[] = [ + { level: 'critical', label: 'Critical', icon: 'alert-circle', color: '#dc2626' }, + { level: 'high', label: 'High', icon: 'arrow-up', color: '#f97316' }, + { level: 'medium', label: 'Medium', icon: 'minus', color: '#eab308' }, + { level: 'low', label: 'Low', icon: 'arrow-down', color: '#22c55e' }, + { level: 'none', label: 'None', icon: 'minus', color: '#9ca3af' }, +]; + +export const DEFAULT_KANBAN_CONFIG: KbConfig = { + defaultView: 'board', + collapsibleColumns: true, + showCardCount: true, + showWipLimits: true, + wipWarningThreshold: 0.8, + allowColumnReorder: true, + allowQuickAdd: true, + cardDescriptionLines: 2, + priorities: DEFAULT_PRIORITIES, + locale: 'en-US', +}; + +export const KANBAN_CONFIG = new InjectionToken('KANBAN_CONFIG', { + providedIn: 'root', + factory: () => DEFAULT_KANBAN_CONFIG, +}); + +export function provideKanbanConfig(config: Partial = {}): EnvironmentProviders { + return makeEnvironmentProviders([ + { provide: KANBAN_CONFIG, useValue: { ...DEFAULT_KANBAN_CONFIG, ...config } }, + ]); +} diff --git a/src/services/index.ts b/src/services/index.ts new file mode 100644 index 0000000..5c80e30 --- /dev/null +++ b/src/services/index.ts @@ -0,0 +1,3 @@ +export * from './kanban-board.service'; +export * from './kanban-drag.service'; +export * from './kanban-filter.service'; diff --git a/src/services/kanban-board.service.ts b/src/services/kanban-board.service.ts new file mode 100644 index 0000000..1d87520 --- /dev/null +++ b/src/services/kanban-board.service.ts @@ -0,0 +1,88 @@ +import { Injectable, inject, signal, computed } from '@angular/core'; +import type { KbCard, KbCardSort, KbColumn, KbWipStatus } from '../types/kanban.types'; +import { KANBAN_CONFIG } from '../providers/kanban-config.provider'; +import { getCardsByColumn, sortCards } from '../utils/card.utils'; +import { getWipStatus } from '../utils/wip.utils'; + +@Injectable() +export class KanbanBoardService { + private config = inject(KANBAN_CONFIG); + + readonly collapsedColumns = signal>(new Set()); + readonly collapsedSwimlanes = signal>(new Set()); + readonly currentSort = signal(null); + + /** Toggle a column's collapsed state */ + toggleColumn(columnId: string): boolean { + const set = new Set(this.collapsedColumns()); + const collapsed = !set.has(columnId); + if (collapsed) { + set.add(columnId); + } else { + set.delete(columnId); + } + this.collapsedColumns.set(set); + return collapsed; + } + + /** Toggle a swimlane's collapsed state */ + toggleSwimlane(swimlaneId: string): boolean { + const set = new Set(this.collapsedSwimlanes()); + const collapsed = !set.has(swimlaneId); + if (collapsed) { + set.add(swimlaneId); + } else { + set.delete(swimlaneId); + } + this.collapsedSwimlanes.set(set); + return collapsed; + } + + /** Set current sort */ + setSort(sort: KbCardSort | null): void { + this.currentSort.set(sort); + } + + /** Apply the current sort to cards within each column */ + applySortWithinColumns(cards: KbCard[], columns: KbColumn[]): KbCard[] { + const sort = this.currentSort(); + if (!sort) return cards; + + const result: KbCard[] = []; + for (const col of columns) { + const columnCards = getCardsByColumn(cards, col.id); + const sorted = sortCards(columnCards, sort); + result.push(...sorted); + } + + // Add any remaining cards not in known columns + const knownIds = new Set(columns.map(c => c.id)); + for (const card of cards) { + if (!knownIds.has(card.columnId)) { + result.push(card); + } + } + + return result; + } + + /** Get cards for a specific column */ + getCardsForColumn(cards: KbCard[], columnId: string, swimlaneId?: string): KbCard[] { + return getCardsByColumn(cards, columnId, swimlaneId).sort((a, b) => a.order - b.order); + } + + /** Get WIP status for a column */ + getWipStatus(cardCount: number, wipLimit?: number): KbWipStatus { + return getWipStatus(cardCount, wipLimit, this.config.wipWarningThreshold); + } + + /** Check if a column is collapsed */ + isColumnCollapsed(columnId: string): boolean { + return this.collapsedColumns().has(columnId); + } + + /** Check if a swimlane is collapsed */ + isSwimlaneCollapsed(swimlaneId: string): boolean { + return this.collapsedSwimlanes().has(swimlaneId); + } +} diff --git a/src/services/kanban-drag.service.ts b/src/services/kanban-drag.service.ts new file mode 100644 index 0000000..b837154 --- /dev/null +++ b/src/services/kanban-drag.service.ts @@ -0,0 +1,128 @@ +import { Injectable, signal } from '@angular/core'; +import type { KbCard, KbColumn } from '../types/kanban.types'; +import type { KbCardMoveEvent, KbColumnReorderEvent } from '../types/event.types'; + +@Injectable() +export class KanbanDragService { + // Card drag state + readonly isDragging = signal(false); + readonly draggedCard = signal(null); + readonly sourceColumnId = signal(null); + readonly sourceSwimlaneId = signal(null); + readonly sourceIndex = signal(-1); + readonly overColumnId = signal(null); + readonly dropIndex = signal(-1); + + // Column drag state + readonly isDraggingColumn = signal(false); + readonly draggedColumnId = signal(null); + + /** Start dragging a card */ + startDrag(card: KbCard, columnId: string, index: number, swimlaneId?: string): void { + this.isDragging.set(true); + this.draggedCard.set(card); + this.sourceColumnId.set(columnId); + this.sourceSwimlaneId.set(swimlaneId ?? null); + this.sourceIndex.set(index); + } + + /** Update when dragging over a column */ + dragOverColumn(columnId: string, dropIdx: number): void { + this.overColumnId.set(columnId); + this.dropIndex.set(dropIdx); + } + + /** Clear the over-column state when leaving */ + dragLeaveColumn(columnId: string): void { + if (this.overColumnId() === columnId) { + this.overColumnId.set(null); + this.dropIndex.set(-1); + } + } + + /** Complete a card drop and return the move event */ + drop(targetColumnId: string, targetIndex: number, targetSwimlaneId?: string): KbCardMoveEvent | null { + const card = this.draggedCard(); + const fromCol = this.sourceColumnId(); + const fromIdx = this.sourceIndex(); + + if (!card || !fromCol) return null; + + const event: KbCardMoveEvent = { + card, + fromColumnId: fromCol, + toColumnId: targetColumnId, + fromSwimlaneId: this.sourceSwimlaneId() ?? undefined, + toSwimlaneId: targetSwimlaneId, + fromIndex: fromIdx, + toIndex: targetIndex, + }; + + this.endDrag(); + return event; + } + + /** Clean up drag state */ + endDrag(): void { + this.isDragging.set(false); + this.draggedCard.set(null); + this.sourceColumnId.set(null); + this.sourceSwimlaneId.set(null); + this.sourceIndex.set(-1); + this.overColumnId.set(null); + this.dropIndex.set(-1); + this.isDraggingColumn.set(false); + this.draggedColumnId.set(null); + } + + /** Start dragging a column for reorder */ + startColumnDrag(columnId: string): void { + this.isDraggingColumn.set(true); + this.draggedColumnId.set(columnId); + } + + /** Complete a column drop and return the reorder event */ + dropColumn( + columns: KbColumn[], + targetColumnId: string, + ): KbColumnReorderEvent | null { + const draggedId = this.draggedColumnId(); + if (!draggedId || draggedId === targetColumnId) { + this.endDrag(); + return null; + } + + const sorted = [...columns].sort((a, b) => a.order - b.order); + const fromIndex = sorted.findIndex(c => c.id === draggedId); + const toIndex = sorted.findIndex(c => c.id === targetColumnId); + const column = sorted[fromIndex]; + + if (fromIndex === -1 || toIndex === -1 || !column) { + this.endDrag(); + return null; + } + + const event: KbColumnReorderEvent = { column, fromIndex, toIndex }; + this.endDrag(); + return event; + } + + /** Check if currently over a specific column */ + isOverColumn(columnId: string): boolean { + return this.isDragging() && this.overColumnId() === columnId; + } + + /** Calculate the drop index based on mouse Y position within a card list */ + calculateDropIndex(event: DragEvent, cardElements: HTMLElement[]): number { + if (cardElements.length === 0) return 0; + + const mouseY = event.clientY; + for (let i = 0; i < cardElements.length; i++) { + const rect = cardElements[i].getBoundingClientRect(); + const midY = rect.top + rect.height / 2; + if (mouseY < midY) return i; + } + + return cardElements.length; + } +} diff --git a/src/services/kanban-filter.service.ts b/src/services/kanban-filter.service.ts new file mode 100644 index 0000000..0ec8966 --- /dev/null +++ b/src/services/kanban-filter.service.ts @@ -0,0 +1,66 @@ +import { Injectable, signal, computed } from '@angular/core'; +import type { KbCard, KbCardFilter, KbPriority } from '../types/kanban.types'; +import { applyCardFilters, countActiveFilters } from '../utils/filter.utils'; + +@Injectable() +export class KanbanFilterService { + readonly searchTerm = signal(''); + readonly priorityFilter = signal([]); + readonly labelFilter = signal([]); + readonly assigneeFilter = signal([]); + readonly blockedFilter = signal(undefined); + + readonly activeFilterCount = computed(() => + countActiveFilters(this.currentFilter()) + ); + + readonly hasFilters = computed(() => this.activeFilterCount() > 0); + + readonly currentFilter = computed(() => ({ + searchTerm: this.searchTerm() || undefined, + priorities: this.priorityFilter().length > 0 ? this.priorityFilter() : undefined, + labelIds: this.labelFilter().length > 0 ? this.labelFilter() : undefined, + assigneeIds: this.assigneeFilter().length > 0 ? this.assigneeFilter() : undefined, + blocked: this.blockedFilter(), + })); + + /** Set search term */ + setSearch(term: string): void { + this.searchTerm.set(term); + } + + /** Set priority filter */ + setPriorities(priorities: KbPriority[]): void { + this.priorityFilter.set(priorities); + } + + /** Set label filter */ + setLabels(labelIds: string[]): void { + this.labelFilter.set(labelIds); + } + + /** Set assignee filter */ + setAssignees(assigneeIds: string[]): void { + this.assigneeFilter.set(assigneeIds); + } + + /** Set blocked filter */ + setBlocked(blocked: boolean | undefined): void { + this.blockedFilter.set(blocked); + } + + /** Apply all current filters to a list of cards */ + applyFilters(cards: KbCard[]): KbCard[] { + if (!this.hasFilters()) return cards; + return applyCardFilters(cards, this.currentFilter()); + } + + /** Clear all filters */ + clearAll(): void { + this.searchTerm.set(''); + this.priorityFilter.set([]); + this.labelFilter.set([]); + this.assigneeFilter.set([]); + this.blockedFilter.set(undefined); + } +} diff --git a/src/styles/_index.scss b/src/styles/_index.scss new file mode 100644 index 0000000..ec2276a --- /dev/null +++ b/src/styles/_index.scss @@ -0,0 +1,2 @@ +@forward 'tokens'; +@forward 'mixins'; diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss new file mode 100644 index 0000000..92acd71 --- /dev/null +++ b/src/styles/_mixins.scss @@ -0,0 +1,141 @@ +@mixin kb-container { + display: block; + position: relative; + width: 100%; + font-family: var(--kb-font-family, inherit); +} + +@mixin kb-flex-center { + display: flex; + align-items: center; +} + +@mixin kb-flex-between { + display: flex; + align-items: center; + justify-content: space-between; +} + +@mixin kb-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +@mixin kb-line-clamp($lines: 2) { + display: -webkit-box; + -webkit-line-clamp: $lines; + -webkit-box-orient: vertical; + overflow: hidden; +} + +@mixin kb-card-base { + background: var(--kb-card-bg); + border: 1px solid var(--kb-card-border); + border-radius: var(--kb-card-radius); + padding: var(--kb-card-padding); + box-shadow: var(--kb-card-shadow); + transition: + box-shadow 200ms var(--kb-ease-smooth), + transform 200ms var(--kb-ease-spring), + opacity 200ms var(--kb-ease-smooth), + border-color var(--kb-transition); + cursor: grab; + + &:hover { + box-shadow: var(--kb-card-shadow-hover); + } +} + +@mixin kb-column-base { + background: var(--kb-column-bg); + border: 1px solid var(--kb-column-border); + border-radius: var(--kb-column-radius); + width: var(--kb-column-width); + min-width: var(--kb-column-min-width); + max-width: var(--kb-column-max-width); + display: flex; + flex-direction: column; + flex-shrink: 0; +} + +@mixin kb-drop-zone { + border: 2px dashed var(--kb-drop-zone-border); + background: var(--kb-drop-zone-bg); + border-radius: var(--kb-card-radius); +} + +@mixin kb-wip-state($state) { + @if $state == 'normal' { + color: var(--kb-wip-normal-color); + background: var(--kb-wip-normal-bg); + } @else if $state == 'warning' { + color: var(--kb-wip-warning-color); + background: var(--kb-wip-warning-bg); + } @else if $state == 'exceeded' { + color: var(--kb-wip-exceeded-color); + background: var(--kb-wip-exceeded-bg); + } +} + +// Premium mixins + +@mixin kb-glass { + background: var(--kb-glass-bg); + backdrop-filter: blur(var(--kb-glass-blur)); + -webkit-backdrop-filter: blur(var(--kb-glass-blur)); + border-color: var(--kb-glass-border); +} + +@mixin kb-hover-lift { + transition: + box-shadow 200ms var(--kb-ease-smooth), + transform 250ms var(--kb-ease-spring); + + &:hover { + transform: translateY(-1px); + box-shadow: var(--kb-card-shadow-hover); + } +} + +@mixin kb-glow($color: var(--kb-glow-color)) { + box-shadow: + 0 0 0 1px $color, + 0 0 var(--kb-glow-spread) rgba(59, 130, 246, var(--kb-glow-opacity)); +} + +@mixin kb-drag-state { + transform: scale(var(--kb-card-drag-scale)) rotate(var(--kb-card-drag-rotate)); + box-shadow: var(--kb-card-shadow-dragging); + opacity: var(--kb-card-dragging-opacity); + cursor: grabbing; + z-index: 100; +} + +@mixin kb-scrollbar($width: 4px) { + &::-webkit-scrollbar { + width: $width; + height: $width; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: var(--kb-card-meta-color); + border-radius: $width; + opacity: 0.4; + + &:hover { + background: var(--kb-card-desc-color); + } + } + + scrollbar-width: thin; + scrollbar-color: var(--kb-card-meta-color) transparent; +} + +@mixin kb-accent-bar($color: var(--kb-drop-indicator-color)) { + border-top: var(--kb-column-accent-height) solid $color; +} diff --git a/src/styles/_tokens.scss b/src/styles/_tokens.scss new file mode 100644 index 0000000..f4b3f3f --- /dev/null +++ b/src/styles/_tokens.scss @@ -0,0 +1,167 @@ +:root { + // Board + --kb-bg: var(--color-bg-primary, #f5f5f7); + --kb-border: var(--color-border-primary, #e5e7eb); + + // Column + --kb-column-bg: var(--color-bg-secondary, #ffffff); + --kb-column-border: var(--color-border-primary, #e5e7eb); + --kb-column-radius: 0.875rem; + --kb-column-width: 380px; + --kb-column-min-width: 340px; + --kb-column-max-width: 480px; + --kb-column-gap: 16px; + --kb-column-padding: 1rem; + --kb-column-header-bg: transparent; + --kb-column-header-padding: 1rem 1rem 0.625rem; + --kb-column-header-font-size: var(--font-size-sm, 0.875rem); + --kb-column-header-font-weight: 600; + --kb-column-header-color: var(--color-text-primary, #111827); + --kb-column-collapsed-width: 48px; + --kb-column-accent-height: 3px; + + // Card + --kb-card-bg: var(--color-bg-secondary, #ffffff); + --kb-card-border: var(--color-border-primary, #e5e7eb); + --kb-card-radius: 0.75rem; + --kb-card-padding: 1rem; + --kb-card-gap: 0.625rem; + --kb-card-shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 4px rgba(0, 0, 0, 0.03); + --kb-card-shadow-hover: 0 4px 12px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.04); + --kb-card-shadow-dragging: 0 16px 48px rgba(0, 0, 0, 0.12), 0 8px 16px rgba(0, 0, 0, 0.08); + --kb-card-title-font-size: var(--font-size-sm, 0.875rem); + --kb-card-title-font-weight: 500; + --kb-card-title-color: var(--color-text-primary, #111827); + --kb-card-desc-font-size: var(--font-size-xs, 0.75rem); + --kb-card-desc-color: var(--color-text-secondary, #6b7280); + --kb-card-meta-font-size: var(--font-size-xs, 0.75rem); + --kb-card-meta-color: var(--color-text-muted, #9ca3af); + --kb-card-blocked-border: var(--color-error-500, #ef4444); + --kb-card-overdue-color: var(--color-error-500, #ef4444); + --kb-card-dragging-opacity: 0.85; + --kb-card-drag-scale: 1.03; + --kb-card-drag-rotate: 1.5deg; + + // Label + --kb-label-radius: 999px; + --kb-label-padding: 0.125rem 0.625rem; + --kb-label-font-size: 0.625rem; + + // Swimlane + --kb-swimlane-bg: transparent; + --kb-swimlane-border: var(--color-border-primary, #e5e7eb); + --kb-swimlane-header-padding: 0.75rem 1rem; + --kb-swimlane-header-font-size: var(--font-size-sm, 0.875rem); + --kb-swimlane-header-font-weight: 600; + --kb-swimlane-header-color: var(--color-text-primary, #111827); + --kb-swimlane-gap: 0.75rem; + + // WIP + --kb-wip-normal-color: var(--color-text-muted, #9ca3af); + --kb-wip-normal-bg: transparent; + --kb-wip-warning-color: #d97706; + --kb-wip-warning-bg: rgba(217, 119, 6, 0.1); + --kb-wip-exceeded-color: var(--color-error-500, #ef4444); + --kb-wip-exceeded-bg: rgba(239, 68, 68, 0.1); + + // Drag + --kb-drop-indicator-color: var(--color-primary-500, #3b82f6); + --kb-drop-zone-bg: rgba(59, 130, 246, 0.05); + --kb-drop-zone-border: var(--color-primary-500, #3b82f6); + + // Toolbar + --kb-toolbar-bg: var(--color-bg-secondary, #ffffff); + --kb-toolbar-border: var(--color-border-primary, #e5e7eb); + --kb-toolbar-padding: 0.875rem 1.25rem; + + // Quick-add + --kb-quick-add-bg: transparent; + --kb-quick-add-hover-bg: var(--color-bg-hover, #f3f4f6); + --kb-quick-add-text: var(--color-text-muted, #9ca3af); + + // Pipeline + --kb-pipeline-connector-color: var(--color-border-primary, #e5e7eb); + --kb-pipeline-connector-width: 2px; + + // Transitions + --kb-transition: 150ms ease-in-out; + --kb-transition-slow: 300ms ease-in-out; + + // Spring & smooth easing + --kb-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); + --kb-ease-smooth: cubic-bezier(0.25, 0.46, 0.45, 0.94); + + // Glass + --kb-glass-bg: rgba(255, 255, 255, 0.72); + --kb-glass-blur: 12px; + --kb-glass-border: rgba(255, 255, 255, 0.2); + + // Glow + --kb-glow-color: var(--color-primary-500, #3b82f6); + --kb-glow-spread: 8px; + --kb-glow-opacity: 0.25; +} + +@media (prefers-color-scheme: dark) { + :root { + --kb-bg: var(--color-bg-primary, #0c0f17); + + --kb-column-bg: var(--color-bg-secondary, #161b26); + --kb-column-border: var(--color-border-primary, #1e2536); + --kb-column-header-color: var(--color-text-primary, #f9fafb); + + --kb-card-bg: var(--color-bg-secondary, #161b26); + --kb-card-border: var(--color-border-primary, #1e2536); + --kb-card-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 1px 4px rgba(0, 0, 0, 0.2); + --kb-card-shadow-hover: 0 4px 12px rgba(0, 0, 0, 0.4), 0 2px 4px rgba(0, 0, 0, 0.3); + --kb-card-shadow-dragging: 0 16px 48px rgba(0, 0, 0, 0.5), 0 8px 16px rgba(0, 0, 0, 0.35); + --kb-card-title-color: var(--color-text-primary, #f9fafb); + --kb-card-desc-color: var(--color-text-secondary, #9ca3af); + --kb-card-meta-color: var(--color-text-muted, #6b7280); + + --kb-swimlane-border: var(--color-border-primary, #1e2536); + --kb-swimlane-header-color: var(--color-text-primary, #f9fafb); + + --kb-toolbar-bg: var(--color-bg-secondary, #161b26); + --kb-toolbar-border: var(--color-border-primary, #1e2536); + + --kb-quick-add-hover-bg: var(--color-bg-hover, #1e2536); + --kb-quick-add-text: var(--color-text-muted, #6b7280); + + --kb-pipeline-connector-color: var(--color-border-primary, #1e2536); + + --kb-glass-bg: rgba(12, 15, 23, 0.75); + --kb-glass-border: rgba(255, 255, 255, 0.06); + } +} + +[data-mode="dark"] { + --kb-bg: var(--color-bg-primary, #0c0f17); + + --kb-column-bg: var(--color-bg-secondary, #161b26); + --kb-column-border: var(--color-border-primary, #1e2536); + --kb-column-header-color: var(--color-text-primary, #f9fafb); + + --kb-card-bg: var(--color-bg-secondary, #161b26); + --kb-card-border: var(--color-border-primary, #1e2536); + --kb-card-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 1px 4px rgba(0, 0, 0, 0.2); + --kb-card-shadow-hover: 0 4px 12px rgba(0, 0, 0, 0.4), 0 2px 4px rgba(0, 0, 0, 0.3); + --kb-card-shadow-dragging: 0 16px 48px rgba(0, 0, 0, 0.5), 0 8px 16px rgba(0, 0, 0, 0.35); + --kb-card-title-color: var(--color-text-primary, #f9fafb); + --kb-card-desc-color: var(--color-text-secondary, #9ca3af); + --kb-card-meta-color: var(--color-text-muted, #6b7280); + + --kb-swimlane-border: var(--color-border-primary, #1e2536); + --kb-swimlane-header-color: var(--color-text-primary, #f9fafb); + + --kb-toolbar-bg: var(--color-bg-secondary, #161b26); + --kb-toolbar-border: var(--color-border-primary, #1e2536); + + --kb-quick-add-hover-bg: var(--color-bg-hover, #1e2536); + --kb-quick-add-text: var(--color-text-muted, #6b7280); + + --kb-pipeline-connector-color: var(--color-border-primary, #1e2536); + + --kb-glass-bg: rgba(12, 15, 23, 0.75); + --kb-glass-border: rgba(255, 255, 255, 0.06); +} diff --git a/src/types/config.types.ts b/src/types/config.types.ts new file mode 100644 index 0000000..27fa02b --- /dev/null +++ b/src/types/config.types.ts @@ -0,0 +1,25 @@ +import type { KbPriorityMeta, KbViewMode } from './kanban.types'; + +/** Global kanban board configuration */ +export interface KbConfig { + /** Default view mode */ + defaultView: KbViewMode; + /** Whether columns can be collapsed */ + collapsibleColumns: boolean; + /** Show card count badge on column headers */ + showCardCount: boolean; + /** Show WIP limit indicators on columns */ + showWipLimits: boolean; + /** Threshold (0-1) at which WIP warning state triggers */ + wipWarningThreshold: number; + /** Whether columns can be reordered via drag */ + allowColumnReorder: boolean; + /** Whether quick-add is enabled on columns */ + allowQuickAdd: boolean; + /** Max lines for card description before clamping */ + cardDescriptionLines: number; + /** Priority metadata for display */ + priorities: KbPriorityMeta[]; + /** Locale for date formatting */ + locale: string; +} diff --git a/src/types/event.types.ts b/src/types/event.types.ts new file mode 100644 index 0000000..301f0df --- /dev/null +++ b/src/types/event.types.ts @@ -0,0 +1,105 @@ +import type { KbCard, KbColumn, KbSwimlane, KbViewMode, KbCardSort, KbCardFilter } from './kanban.types'; + +/** Emitted when a card is moved between columns or reordered */ +export interface KbCardMoveEvent { + card: KbCard; + fromColumnId: string; + toColumnId: string; + fromSwimlaneId?: string; + toSwimlaneId?: string; + fromIndex: number; + toIndex: number; +} + +/** Emitted when a card is clicked */ +export interface KbCardClickEvent { + card: KbCard; + columnId: string; + event: MouseEvent; +} + +/** Emitted when a card is double-clicked */ +export interface KbCardDblClickEvent { + card: KbCard; + columnId: string; + event: MouseEvent; +} + +/** Emitted when a card is right-clicked */ +export interface KbCardContextMenuEvent { + card: KbCard; + columnId: string; + event: MouseEvent; +} + +/** Emitted when a new card is added via quick-add */ +export interface KbCardAddEvent { + title: string; + columnId: string; + swimlaneId?: string; +} + +/** Emitted when a card is deleted */ +export interface KbCardDeleteEvent { + card: KbCard; +} + +/** Emitted when a column header is clicked */ +export interface KbColumnClickEvent { + column: KbColumn; + event: MouseEvent; +} + +/** Emitted when a column is collapsed or expanded */ +export interface KbColumnToggleEvent { + column: KbColumn; + collapsed: boolean; +} + +/** Emitted when a column is reordered */ +export interface KbColumnReorderEvent { + column: KbColumn; + fromIndex: number; + toIndex: number; +} + +/** Emitted when a swimlane is collapsed or expanded */ +export interface KbSwimlaneToggleEvent { + swimlane: KbSwimlane; + collapsed: boolean; +} + +/** Emitted when the view mode changes */ +export interface KbViewChangeEvent { + view: KbViewMode; +} + +/** Emitted when the sort configuration changes */ +export interface KbSortChangeEvent { + sort: KbCardSort; +} + +/** Emitted when the filter configuration changes */ +export interface KbFilterChangeEvent { + filter: KbCardFilter; +} + +/** Emitted when a column's WIP limit is exceeded */ +export interface KbWipExceededEvent { + column: KbColumn; + cardCount: number; + wipLimit: number; +} + +/** Emitted when a card drag starts */ +export interface KbDragStartEvent { + card: KbCard; + columnId: string; + swimlaneId?: string; +} + +/** Emitted when a card drag ends */ +export interface KbDragEndEvent { + card: KbCard; + dropped: boolean; +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..bef7778 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,3 @@ +export * from './kanban.types'; +export * from './config.types'; +export * from './event.types'; diff --git a/src/types/kanban.types.ts b/src/types/kanban.types.ts new file mode 100644 index 0000000..3560bfa --- /dev/null +++ b/src/types/kanban.types.ts @@ -0,0 +1,97 @@ +/** Priority levels for kanban cards */ +export type KbPriority = 'critical' | 'high' | 'medium' | 'low' | 'none'; + +/** Column type classification */ +export type KbColumnType = 'backlog' | 'active' | 'done' | 'archive' | 'custom'; + +/** Board display mode */ +export type KbViewMode = 'board' | 'pipeline'; + +/** WIP limit status */ +export type KbWipStatus = 'normal' | 'warning' | 'exceeded'; + +/** Available sort fields for cards */ +export type KbCardSortField = 'order' | 'priority' | 'dueDate' | 'title' | 'createdAt'; + +/** Label attached to a card */ +export interface KbLabel { + id: string; + text: string; + color: string; +} + +/** User assigned to a card */ +export interface KbAssignee { + id: string; + name: string; + avatar?: string; + email?: string; +} + +/** Priority metadata for display */ +export interface KbPriorityMeta { + level: KbPriority; + label: string; + icon: string; + color: string; +} + +/** Kanban card data */ +export interface KbCard { + id: string; + title: string; + description?: string; + columnId: string; + swimlaneId?: string; + order: number; + priority?: KbPriority; + labels?: KbLabel[]; + assignees?: KbAssignee[]; + dueDate?: Date | string; + storyPoints?: number; + subtasks?: { completed: number; total: number }; + coverImage?: string; + blocked?: boolean; + blockedReason?: string; + createdAt?: Date | string; + updatedAt?: Date | string; + metadata?: Record; +} + +/** Kanban column definition */ +export interface KbColumn { + id: string; + title: string; + color?: string; + wipLimit?: number; + collapsed?: boolean; + order: number; + allowAdd?: boolean; + columnType?: KbColumnType; +} + +/** Swimlane definition for grouping cards */ +export interface KbSwimlane { + id: string; + title: string; + order: number; + color?: string; + collapsible?: boolean; + collapsed?: boolean; + isDefault?: boolean; +} + +/** Card sort configuration */ +export interface KbCardSort { + field: KbCardSortField; + direction: 'asc' | 'desc'; +} + +/** Card filter configuration */ +export interface KbCardFilter { + searchTerm?: string; + priorities?: KbPriority[]; + labelIds?: string[]; + assigneeIds?: string[]; + blocked?: boolean; +} diff --git a/src/utils/card.utils.ts b/src/utils/card.utils.ts new file mode 100644 index 0000000..d9b6651 --- /dev/null +++ b/src/utils/card.utils.ts @@ -0,0 +1,111 @@ +import type { KbCard, KbCardSort, KbPriority } from '../types/kanban.types'; + +const PRIORITY_ORDER: Record = { + critical: 0, + high: 1, + medium: 2, + low: 3, + none: 4, +}; + +/** Get cards belonging to a specific column, optionally filtered by swimlane */ +export function getCardsByColumn( + cards: KbCard[], + columnId: string, + swimlaneId?: string, +): KbCard[] { + return cards.filter(c => { + if (c.columnId !== columnId) return false; + if (swimlaneId !== undefined && c.swimlaneId !== swimlaneId) return false; + return true; + }); +} + +/** Reorder cards after a move: returns new array with updated order values */ +export function reorderCards( + cards: KbCard[], + movedCardId: string, + targetColumnId: string, + targetIndex: number, + targetSwimlaneId?: string, +): KbCard[] { + const moved = cards.find(c => c.id === movedCardId); + if (!moved) return cards; + + // Remove from current position + const without = cards.filter(c => c.id !== movedCardId); + + // Get target column cards sorted by order + const targetCards = without + .filter(c => c.columnId === targetColumnId && (targetSwimlaneId === undefined || c.swimlaneId === targetSwimlaneId)) + .sort((a, b) => a.order - b.order); + + // Clamp target index + const clampedIndex = Math.max(0, Math.min(targetIndex, targetCards.length)); + + // Insert and recalculate orders + const updatedMoved: KbCard = { + ...moved, + columnId: targetColumnId, + swimlaneId: targetSwimlaneId ?? moved.swimlaneId, + order: clampedIndex, + }; + + // Build final array + const result: KbCard[] = []; + for (const card of without) { + if (card.columnId === targetColumnId && (targetSwimlaneId === undefined || card.swimlaneId === targetSwimlaneId)) { + continue; // Will be re-added with updated order + } + result.push(card); + } + + // Re-insert target column cards with correct orders + targetCards.splice(clampedIndex, 0, updatedMoved); + for (let i = 0; i < targetCards.length; i++) { + result.push({ ...targetCards[i], order: i }); + } + + return result; +} + +/** Sort cards by the given sort configuration */ +export function sortCards(cards: KbCard[], sort: KbCardSort): KbCard[] { + const sorted = [...cards]; + const dir = sort.direction === 'asc' ? 1 : -1; + + sorted.sort((a, b) => { + switch (sort.field) { + case 'order': + return (a.order - b.order) * dir; + case 'priority': { + const pa = PRIORITY_ORDER[a.priority ?? 'none']; + const pb = PRIORITY_ORDER[b.priority ?? 'none']; + return (pa - pb) * dir; + } + case 'dueDate': { + const da = a.dueDate ? new Date(a.dueDate).getTime() : Infinity; + const db = b.dueDate ? new Date(b.dueDate).getTime() : Infinity; + return (da - db) * dir; + } + case 'title': + return a.title.localeCompare(b.title) * dir; + case 'createdAt': { + const ca = a.createdAt ? new Date(a.createdAt).getTime() : 0; + const cb = b.createdAt ? new Date(b.createdAt).getTime() : 0; + return (ca - cb) * dir; + } + default: + return 0; + } + }); + + return sorted; +} + +/** Compute the next order value for a new card in a column */ +export function computeCardOrder(cards: KbCard[], columnId: string): number { + const columnCards = cards.filter(c => c.columnId === columnId); + if (columnCards.length === 0) return 0; + return Math.max(...columnCards.map(c => c.order)) + 1; +} diff --git a/src/utils/date.utils.ts b/src/utils/date.utils.ts new file mode 100644 index 0000000..2213ed1 --- /dev/null +++ b/src/utils/date.utils.ts @@ -0,0 +1,27 @@ +/** Format a date for display on a card */ +export function formatCardDate(date: Date | string | undefined, locale: string = 'en-US'): string { + if (!date) return ''; + const d = typeof date === 'string' ? new Date(date) : date; + if (isNaN(d.getTime())) return ''; + + const now = new Date(); + const diffMs = d.getTime() - now.getTime(); + const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24)); + + if (diffDays === 0) return 'Today'; + if (diffDays === 1) return 'Tomorrow'; + if (diffDays === -1) return 'Yesterday'; + + return d.toLocaleDateString(locale, { month: 'short', day: 'numeric' }); +} + +/** Check if a date is overdue (before today) */ +export function isOverdue(date: Date | string | undefined): boolean { + if (!date) return false; + const d = typeof date === 'string' ? new Date(date) : date; + if (isNaN(d.getTime())) return false; + + const now = new Date(); + now.setHours(0, 0, 0, 0); + return d.getTime() < now.getTime(); +} diff --git a/src/utils/filter.utils.ts b/src/utils/filter.utils.ts new file mode 100644 index 0000000..fea8523 --- /dev/null +++ b/src/utils/filter.utils.ts @@ -0,0 +1,47 @@ +import type { KbCard, KbCardFilter } from '../types/kanban.types'; + +/** Apply filters to a list of cards */ +export function applyCardFilters(cards: KbCard[], filter: KbCardFilter): KbCard[] { + let result = cards; + + if (filter.searchTerm) { + const term = filter.searchTerm.toLowerCase(); + result = result.filter(c => + c.title.toLowerCase().includes(term) || + (c.description && c.description.toLowerCase().includes(term)) + ); + } + + if (filter.priorities && filter.priorities.length > 0) { + result = result.filter(c => filter.priorities!.includes(c.priority ?? 'none')); + } + + if (filter.labelIds && filter.labelIds.length > 0) { + result = result.filter(c => + c.labels?.some(l => filter.labelIds!.includes(l.id)) + ); + } + + if (filter.assigneeIds && filter.assigneeIds.length > 0) { + result = result.filter(c => + c.assignees?.some(a => filter.assigneeIds!.includes(a.id)) + ); + } + + if (filter.blocked !== undefined) { + result = result.filter(c => (c.blocked ?? false) === filter.blocked); + } + + return result; +} + +/** Count the number of active filters */ +export function countActiveFilters(filter: KbCardFilter): number { + let count = 0; + if (filter.searchTerm) count++; + if (filter.priorities && filter.priorities.length > 0) count++; + if (filter.labelIds && filter.labelIds.length > 0) count++; + if (filter.assigneeIds && filter.assigneeIds.length > 0) count++; + if (filter.blocked !== undefined) count++; + return count; +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..b995e7b --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,4 @@ +export * from './card.utils'; +export * from './filter.utils'; +export * from './wip.utils'; +export * from './date.utils'; diff --git a/src/utils/wip.utils.ts b/src/utils/wip.utils.ts new file mode 100644 index 0000000..6da51c1 --- /dev/null +++ b/src/utils/wip.utils.ts @@ -0,0 +1,19 @@ +import type { KbWipStatus } from '../types/kanban.types'; + +/** Get the WIP status for a column based on card count and limit */ +export function getWipStatus( + cardCount: number, + wipLimit: number | undefined, + warningThreshold: number = 0.8, +): KbWipStatus { + if (!wipLimit || wipLimit <= 0) return 'normal'; + if (cardCount > wipLimit) return 'exceeded'; + if (cardCount >= wipLimit * warningThreshold) return 'warning'; + return 'normal'; +} + +/** Check if a WIP limit is exceeded */ +export function isWipExceeded(cardCount: number, wipLimit: number | undefined): boolean { + if (!wipLimit || wipLimit <= 0) return false; + return cardCount > wipLimit; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..dddab58 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "esModuleInterop": true, + "sourceMap": true, + "declaration": true, + "experimentalDecorators": true, + "moduleResolution": "bundler", + "importHelpers": true, + "target": "ES2022", + "module": "ES2022", + "lib": ["ES2022", "dom"], + "useDefineForClassFields": false + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +}