commit a07edd485b67ccb9929eeaf3b24a038ee644c0b8 Author: Giuliano Silvestro Date: Fri Feb 13 21:58:25 2026 +1000 Initial commit: timeline-elements-ui library Angular 19 component library for timelines and event tracking: timeline, Gantt chart, event cards, date markers, connectors, and custom event templates. Includes signal-based services, SCSS design tokens with dark mode, and 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/README.md b/README.md new file mode 100644 index 0000000..5adc373 --- /dev/null +++ b/README.md @@ -0,0 +1,194 @@ +# Timeline Elements UI + +Angular 19 components for timeline, activity feed, audit trail, changelog, and Gantt chart displays powered by @sda/base-ui. + +## Features + +- **Timeline** — Vertical timeline with alternating layouts, grouping, and sorting +- **Horizontal Timeline** — Scrollable horizontal timeline with nav arrows +- **Activity Feed** — Social-style activity stream with avatars and load-more pagination +- **Audit Trail** — Comprehensive audit log with search, filter, and expandable change diffs +- **Changelog** — Version-based changelog with categorized entries and breaking change badges +- **Gantt Chart** — Interactive Gantt chart with zoom levels, progress bars, and today line +- **Signal API** — Built with Angular 19 signals (`input()`, `output()`, `computed()`, `signal()`) +- **Standalone** — All components are standalone with OnPush change detection +- **Themeable** — CSS custom properties with base-ui token fallbacks +- **Dark Mode** — Full dark mode support via `prefers-color-scheme` +- **Accessible** — ARIA labels, keyboard navigation, focus management + +## Installation + +```bash +npm install @sda/timeline-elements-ui @sda/base-ui +``` + +## Quick Start + +```typescript +import { Component } from '@angular/core'; +import { TlTimelineComponent, type TlTimelineItem } from '@sda/timeline-elements-ui'; + +@Component({ + standalone: true, + imports: [TlTimelineComponent], + template: ` + + ` +}) +export class AppComponent { + events: TlTimelineItem[] = [ + { + id: '1', + title: 'Tenancy started', + description: 'John Doe moved in', + date: new Date('2024-01-01'), + icon: 'home', + status: 'completed' + } + ]; + + onItemClick(event: any) { + console.log('Clicked:', event.item); + } +} +``` + +## Components + +### `tl-timeline` +Vertical timeline with markers and connectors. + +**Inputs:** +- `items` — Timeline items (required) +- `layout` — 'left' | 'right' | 'alternating' +- `markerSize` — 'sm' | 'md' | 'lg' +- `connectorStyle` — 'solid' | 'dashed' | 'dotted' +- `sortDirection` — 'asc' | 'desc' +- `groupByDate` — Group items by date labels +- `animate` — Animate item entry +- `loading` — Show skeleton loaders +- `emptyMessage` — Message when no items + +**Outputs:** +- `itemClick` — Emits when item is clicked +- `itemHover` — Emits when item is hovered + +### `tl-horizontal-timeline` +Horizontal scrollable timeline. + +**Inputs:** +- `items` — Timeline items (required) +- `markerSize`, `connectorStyle`, `sortDirection` — Same as `tl-timeline` +- `scrollable`, `showNavArrows`, `loading`, `emptyMessage` + +**Outputs:** +- `itemClick` + +### `tl-activity-feed` +Activity stream with avatars and pagination. + +**Inputs:** +- `items` — Activity items (required) +- `pageSize` — Items per page +- `showLoadMore`, `showUnreadIndicator`, `groupByDate`, `loading`, `emptyMessage` + +**Outputs:** +- `activityClick`, `actorClick`, `loadMore` + +### `tl-audit-trail` +Audit log with search, filter, and expandable diffs. + +**Inputs:** +- `entries` — Audit entries (required) +- `pageSize`, `searchable`, `filterable`, `expandable`, `showSeverityIcons`, `loading`, `emptyMessage`, `serverSide` + +**Outputs:** +- `entryClick`, `filterChange`, `pageChange` + +### `tl-changelog` +Version-based changelog. + +**Inputs:** +- `versions` — Changelog versions (required) +- `collapsible`, `expandLatest`, `showCategoryBadges`, `showBreakingBadge`, `loading`, `emptyMessage` + +**Outputs:** +- `versionClick`, `entryClick` + +### `tl-gantt-chart` +Interactive Gantt chart. + +**Inputs:** +- `tasks` — Gantt tasks (required) +- `dependencies`, `zoomLevel`, `rowHeight`, `showDependencies`, `showProgress`, `showTodayLine`, `loading`, `emptyMessage` + +**Outputs:** +- `taskClick`, `taskResize`, `taskMove`, `zoomChange`, `scroll` + +## Configuration + +```typescript +import { provideTimelineConfig } from '@sda/timeline-elements-ui'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideTimelineConfig({ + locale: 'en-US', + relativeTime: true, + animate: true, + timeline: { + layout: 'left', + markerSize: 'md', + connectorStyle: 'solid', + sortDirection: 'desc', + groupByDate: false, + }, + activity: { + pageSize: 20, + showUnreadIndicator: true, + groupByDate: true, + }, + audit: { + pageSize: 25, + searchable: true, + filterable: true, + expandable: true, + showSeverityIcons: true, + }, + gantt: { + zoomLevel: 'week', + rowHeight: 40, + showDependencies: true, + showProgress: true, + showTodayLine: true, + }, + }), + ], +}; +``` + +## Styling + +All components use CSS custom properties with base-ui token fallbacks: + +```scss +:root { + --tl-bg: var(--color-bg-primary, #ffffff); + --tl-text: var(--color-text-primary, #111827); + --tl-border: var(--color-border-primary, #e5e7eb); + --tl-marker-bg: var(--color-primary-500, #3b82f6); + --tl-status-completed: #10b981; + --tl-status-active: #3b82f6; + --tl-status-error: #ef4444; + // ... more tokens +} +``` + +## License + +MIT diff --git a/build-for-dev.sh b/build-for-dev.sh new file mode 100755 index 0000000..f98ab4d --- /dev/null +++ b/build-for-dev.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +echo "Building @sda/timeline-elements-ui for local development..." +npm run build +cd dist +npm pack +echo "Done! Package is ready in dist/" 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..f605ad3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3785 @@ +{ + "name": "@sda/timeline-elements-ui", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@sda/timeline-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.1.0", + "@sda/base-ui": "file:../../sda-frontend/libs/base-ui/dist", + "ng-packagr": "^19.1.0", + "typescript": "~5.7.2" + }, + "peerDependencies": { + "@angular/common": "^19.0.0", + "@angular/core": "^19.0.0", + "@sda/base-ui": "*" + } + }, + "../../sda-frontend/libs/base-ui/dist": { + "name": "base-ui", + "version": "0.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^19.0.0", + "@angular/common": "^19.0.0", + "@angular/core": "^19.0.0", + "@angular/forms": "^19.0.0", + "@angular/platform-browser": "^19.0.0", + "@angular/router": "^19.0.0", + "rxjs": "^7.8.0" + } + }, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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", + "peer": true, + "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/@angular/platform-browser": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-19.2.18.tgz", + "integrity": "sha512-eahtsHPyXTYLARs9YOlXhnXGgzw0wcyOcDkBvNWK/3lA0NHIgIHmQgXAmBo+cJ+g9skiEQTD2OmSrrwbFKWJkw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/animations": "19.2.18", + "@angular/common": "19.2.18", + "@angular/core": "19.2.18" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "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", + "peer": true, + "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/@sda/base-ui": { + "resolved": "../../sda-frontend/libs/base-ui/dist", + "link": true + }, + "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", + "peer": true, + "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/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", + "peer": true, + "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", + "peer": true + }, + "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", + "peer": true, + "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" + } + }, + "node_modules/zone.js": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz", + "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==", + "dev": true, + "license": "MIT", + "peer": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0a1d804 --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "name": "@sda/timeline-elements-ui", + "version": "0.1.0", + "description": "Angular components for timeline, activity feed, audit trail, changelog, and Gantt chart displays powered by @sda/base-ui", + "keywords": [ + "angular", + "timeline", + "activity", + "audit", + "changelog", + "gantt", + "components", + "ui" + ], + "repository": { + "type": "git", + "url": "https://git.sky-ai.com/ui-core-design/timeline-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.1.0", + "@sda/base-ui": "file:../../sda-frontend/libs/base-ui/dist", + "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..2c7219a --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,15 @@ +// Leaf components +export * from './tl-timeline-toolbar'; +export * from './tl-timeline-item'; +export * from './tl-activity-item'; +export * from './tl-audit-entry'; +export * from './tl-changelog-version'; +export * from './tl-gantt-row'; + +// Container components +export * from './tl-timeline'; +export * from './tl-horizontal-timeline'; +export * from './tl-activity-feed'; +export * from './tl-audit-trail'; +export * from './tl-changelog'; +export * from './tl-gantt-chart'; diff --git a/src/components/tl-activity-feed/index.ts b/src/components/tl-activity-feed/index.ts new file mode 100644 index 0000000..c0dcfc2 --- /dev/null +++ b/src/components/tl-activity-feed/index.ts @@ -0,0 +1 @@ +export * from './tl-activity-feed.component'; diff --git a/src/components/tl-activity-feed/tl-activity-feed.component.html b/src/components/tl-activity-feed/tl-activity-feed.component.html new file mode 100644 index 0000000..bd5224e --- /dev/null +++ b/src/components/tl-activity-feed/tl-activity-feed.component.html @@ -0,0 +1,59 @@ +
+ @if (loading()) { +
+ @for (i of getLoadingItems(); track i) { +
+ +
+ + +
+
+ } +
+ } @else if (items().length === 0) { +
+

{{ emptyMessage() }}

+
+ } @else { + @if (groupByDate() && groupedItems()) { + @for (group of groupedItems(); track group.label) { +
+

{{ group.label }}

+
+ @for (item of group.items; track item.id) { + + } +
+
+ } + } @else { +
+ @for (item of displayedItems(); track item.id) { + + } +
+ } + + @if (showLoadMore() && hasMore()) { +
+ + Load More + +
+ } + } +
diff --git a/src/components/tl-activity-feed/tl-activity-feed.component.scss b/src/components/tl-activity-feed/tl-activity-feed.component.scss new file mode 100644 index 0000000..9c5faaf --- /dev/null +++ b/src/components/tl-activity-feed/tl-activity-feed.component.scss @@ -0,0 +1,70 @@ +.tl-activity-feed { + background: var(--color-bg-primary, #ffffff); + border: 1px solid var(--color-border-primary, #e5e7eb); + border-radius: var(--radius-lg, 0.5rem); + padding: var(--spacing-md, 1rem); + transition: all var(--transition-normal, 200ms ease); + padding: 0; + overflow: hidden; + + &__loading { + display: flex; + flex-direction: column; + } + + &__skeleton { + display: grid; + grid-template-columns: auto 1fr; + gap: var(--tl-activity-gap); + padding: var(--spacing-md, 1rem); + border-bottom: 1px solid var(--color-border-primary, #e5e7eb); + } + + &__skeleton-content { + display: flex; + flex-direction: column; + gap: var(--spacing-xs, 0.5rem); + } + + &__empty { + padding: var(--spacing-xl, 2rem); + text-align: center; + color: var(--color-text-muted, #9ca3af); + + p { + margin: 0; + font-size: var(--font-size-base, 1rem); + } + } + + &__group { + &:not(:last-child) { + border-bottom: 2px solid var(--color-border-primary, #e5e7eb); + } + } + + &__group-label { + margin: 0; + padding: var(--spacing-md, 1rem) var(--spacing-md, 1rem) var(--spacing-sm, 0.75rem); + font-size: var(--font-size-sm, 0.875rem); + font-weight: 600; + color: var(--color-text-secondary, #6b7280); + text-transform: uppercase; + letter-spacing: 0.05em; + background: var(--color-bg-secondary, #f9fafb); + border-bottom: 1px solid var(--color-border-primary, #e5e7eb); + } + + &__items { + display: flex; + flex-direction: column; + } + + &__load-more { + display: flex; + align-items: center; + justify-content: center; + padding: var(--spacing-md, 1rem); + border-top: 1px solid var(--color-border-primary, #e5e7eb); + } +} diff --git a/src/components/tl-activity-feed/tl-activity-feed.component.ts b/src/components/tl-activity-feed/tl-activity-feed.component.ts new file mode 100644 index 0000000..aaff5ff --- /dev/null +++ b/src/components/tl-activity-feed/tl-activity-feed.component.ts @@ -0,0 +1,66 @@ +import { Component, ChangeDetectionStrategy, input, output, computed, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ButtonComponent, SkeletonComponent } from '@sda/base-ui'; +import { TlActivityItemComponent } from '../tl-activity-item'; +import type { TlActivityItem } from '../../types/activity.types'; +import type { TlActivityClickEvent, TlActorClickEvent, TlActivityLoadMoreEvent } from '../../types/event.types'; +import { groupByDate } from '../../utils/date.utils'; + +@Component({ + selector: 'tl-activity-feed', + standalone: true, + imports: [CommonModule, ButtonComponent, SkeletonComponent, TlActivityItemComponent], + templateUrl: './tl-activity-feed.component.html', + styleUrl: './tl-activity-feed.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TlActivityFeedComponent { + readonly items = input.required(); + readonly pageSize = input(20); + readonly showLoadMore = input(true); + readonly showUnreadIndicator = input(true); + readonly loading = input(false); + readonly emptyMessage = input('No activity'); + readonly groupByDate = input(true); + + readonly activityClick = output(); + readonly actorClick = output(); + readonly loadMore = output(); + + protected readonly displayCount = signal(20); + + protected readonly displayedItems = computed(() => { + const count = this.displayCount(); + const pageSize = this.pageSize(); + const actualCount = Math.max(count, pageSize); + return this.items().slice(0, actualCount); + }); + + protected readonly hasMore = computed(() => { + return this.displayedItems().length < this.items().length; + }); + + protected readonly groupedItems = computed(() => { + if (!this.groupByDate()) return null; + return groupByDate(this.displayedItems()); + }); + + protected onLoadMore(): void { + const currentCount = this.displayedItems().length; + const pageSize = this.pageSize(); + this.displayCount.update(c => c + pageSize); + this.loadMore.emit({ currentCount, pageSize }); + } + + protected onActivityClick(event: TlActivityClickEvent): void { + this.activityClick.emit(event); + } + + protected onActorClick(event: TlActorClickEvent): void { + this.actorClick.emit(event); + } + + protected getLoadingItems(): number[] { + return Array.from({ length: 5 }, (_, i) => i); + } +} diff --git a/src/components/tl-activity-item/index.ts b/src/components/tl-activity-item/index.ts new file mode 100644 index 0000000..2452782 --- /dev/null +++ b/src/components/tl-activity-item/index.ts @@ -0,0 +1 @@ +export * from './tl-activity-item.component'; diff --git a/src/components/tl-activity-item/tl-activity-item.component.html b/src/components/tl-activity-item/tl-activity-item.component.html new file mode 100644 index 0000000..95a5cf9 --- /dev/null +++ b/src/components/tl-activity-item/tl-activity-item.component.html @@ -0,0 +1,38 @@ +
+ @if (showUnreadIndicator() && isUnread()) { +
+ } + +
+ @if (item().actor.avatar) { + + } @else { + {{ item().actor.initials || item().actor.name.substring(0, 2) }} + } +
+ +
+
+ + {{ item().actor.name }} + + {{ item().action }} + @if (item().target) { + {{ item().target }} + } +
+ + @if (item().description) { +

{{ item().description }}

+ } + + +
+ +
+ +
+
diff --git a/src/components/tl-activity-item/tl-activity-item.component.scss b/src/components/tl-activity-item/tl-activity-item.component.scss new file mode 100644 index 0000000..f0268a1 --- /dev/null +++ b/src/components/tl-activity-item/tl-activity-item.component.scss @@ -0,0 +1,108 @@ +.tl-activity-item { + position: relative; + display: grid; + grid-template-columns: auto 1fr auto; + align-items: start; + gap: var(--spacing-sm, 0.75rem); + padding: var(--spacing-md, 1rem); + border-bottom: 1px solid var(--color-border-primary, #e5e7eb); + cursor: pointer; + transition: background var(--transition-normal, 200ms ease); + + &:hover { + background: var(--color-bg-hover, #f3f4f6); + } + + &--unread { + background: var(--color-bg-selected, #eff6ff); + } + + &__unread-dot { + position: absolute; + left: var(--spacing-xs, 0.5rem); + top: 50%; + transform: translateY(-50%); + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--tl-activity-unread-dot); + } + + &__avatar { + width: var(--tl-activity-avatar-size, 36px); + height: var(--tl-activity-avatar-size, 36px); + border-radius: 50%; + overflow: hidden; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + background: var(--color-bg-tertiary, #e5e7eb); + color: var(--color-text-secondary, #6b7280); + font-size: var(--font-size-xs, 0.75rem); + font-weight: 600; + cursor: pointer; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + + &:hover { + opacity: 0.8; + } + } + + &__content { + min-width: 0; + } + + &__header { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + font-size: var(--font-size-sm, 0.875rem); + line-height: 1.5; + } + + &__actor { + font-weight: 600; + color: var(--color-text-primary, #111827); + cursor: pointer; + + &:hover { + text-decoration: underline; + } + } + + &__action { + color: var(--color-text-secondary, #6b7280); + } + + &__target { + font-weight: 500; + color: var(--color-text-primary, #111827); + } + + &__description { + margin: 0.25rem 0 0.25rem; + font-size: var(--font-size-xs, 0.75rem); + color: var(--color-text-secondary, #6b7280); + line-height: 1.5; + } + + &__time { + font-size: var(--font-size-xs, 0.75rem); + color: var(--color-text-muted, #9ca3af); + } + + &__icon { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + color: var(--color-text-muted, #9ca3af); + } +} diff --git a/src/components/tl-activity-item/tl-activity-item.component.ts b/src/components/tl-activity-item/tl-activity-item.component.ts new file mode 100644 index 0000000..dbe4903 --- /dev/null +++ b/src/components/tl-activity-item/tl-activity-item.component.ts @@ -0,0 +1,54 @@ +import { Component, ChangeDetectionStrategy, input, output, computed } from '@angular/core'; +import { IconComponent, TooltipDirective } from '@sda/base-ui'; +import type { TlActivityItem } from '../../types/activity.types'; +import type { TlActivityClickEvent, TlActorClickEvent } from '../../types/event.types'; +import { formatSmartTime } from '../../utils/date.utils'; + +@Component({ + selector: 'tl-activity-item', + standalone: true, + imports: [IconComponent, TooltipDirective], + templateUrl: './tl-activity-item.component.html', + styleUrl: './tl-activity-item.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + '[class.tl-activity-item--unread]': 'isUnread()', + }, +}) +export class TlActivityItemComponent { + readonly item = input.required(); + readonly showUnreadIndicator = input(true); + + readonly activityClick = output(); + readonly actorClick = output(); + + protected readonly formattedTime = computed(() => formatSmartTime(this.item().date)); + + protected readonly actionIcon = computed(() => { + const item = this.item(); + if (item.icon) return item.icon; + switch (item.type) { + case 'create': return 'plus'; + case 'update': return 'edit'; + case 'delete': return 'trash'; + case 'comment': return 'message'; + case 'assign': return 'user'; + case 'status': return 'activity'; + case 'upload': return 'upload'; + case 'share': return 'share'; + case 'system': return 'settings'; + default: return 'activity'; + } + }); + + protected readonly isUnread = computed(() => this.item().unread ?? false); + + protected onActivityClick(): void { + this.activityClick.emit({ item: this.item() }); + } + + protected onActorClick(event: Event): void { + event.stopPropagation(); + this.actorClick.emit({ actor: this.item().actor }); + } +} diff --git a/src/components/tl-audit-entry/index.ts b/src/components/tl-audit-entry/index.ts new file mode 100644 index 0000000..b5bc4ba --- /dev/null +++ b/src/components/tl-audit-entry/index.ts @@ -0,0 +1 @@ +export * from './tl-audit-entry.component'; diff --git a/src/components/tl-audit-entry/tl-audit-entry.component.html b/src/components/tl-audit-entry/tl-audit-entry.component.html new file mode 100644 index 0000000..804dc7f --- /dev/null +++ b/src/components/tl-audit-entry/tl-audit-entry.component.html @@ -0,0 +1,71 @@ +
+
+ @if (showSeverityIcon()) { +
+ +
+ } + +
+
+ {{ entry().action }} + {{ entry().category }} + + {{ entry().severity }} + +
+ + + + @if (entry().description) { +

{{ entry().description }}

+ } +
+ + @if (expandable() && hasChanges()) { +
+ +
+ } +
+ + @if (isExpanded() && hasChanges()) { +
+
Changes
+ + + + + + + + + + @for (change of entry().changes; track $index) { + + + + + + } + +
FieldOld ValueNew Value
{{ change.field }} + {{ change.oldValue ?? '(empty)' }} + + {{ change.newValue ?? '(empty)' }} +
+
+ } +
diff --git a/src/components/tl-audit-entry/tl-audit-entry.component.scss b/src/components/tl-audit-entry/tl-audit-entry.component.scss new file mode 100644 index 0000000..5b1d2ef --- /dev/null +++ b/src/components/tl-audit-entry/tl-audit-entry.component.scss @@ -0,0 +1,130 @@ +.tl-audit-entry { + border-bottom: 1px solid var(--color-border-primary, #e5e7eb); + transition: background var(--transition-normal, 200ms ease); + + &--expandable { + cursor: pointer; + + &:hover { + background: var(--color-bg-hover, #f3f4f6); + } + } + + &__main { + display: grid; + grid-template-columns: auto 1fr auto; + gap: var(--spacing-md, 1rem); + padding: var(--spacing-md, 1rem); + } + + &__icon { + display: flex; + align-items: flex-start; + padding-top: 2px; + } + + &__content { + min-width: 0; + } + + &__header { + display: flex; + flex-wrap: wrap; + gap: var(--spacing-xs, 0.5rem); + align-items: center; + margin-bottom: var(--spacing-xs, 0.5rem); + } + + &__action { + font-size: var(--font-size-base, 1rem); + font-weight: 600; + color: var(--color-text-primary, #111827); + } + + &__meta { + display: flex; + flex-wrap: wrap; + gap: var(--spacing-xs, 0.5rem); + font-size: var(--font-size-xs, 0.75rem); + color: var(--color-text-muted, #9ca3af); + } + + &__user { + font-weight: 500; + color: var(--color-text-secondary, #6b7280); + } + + &__separator { + color: var(--color-text-muted, #9ca3af); + } + + &__description { + margin: var(--spacing-sm, 0.75rem) 0 0; + font-size: var(--font-size-sm, 0.875rem); + color: var(--color-text-secondary, #6b7280); + line-height: 1.5; + } + + &__expand { + display: flex; + align-items: center; + color: var(--color-text-muted, #9ca3af); + transition: transform var(--transition-normal, 200ms ease); + } + + &--expanded &__expand { + transform: rotate(180deg); + } + + &__changes { + padding: 0 var(--spacing-md, 1rem) var(--spacing-md, 1rem); + padding-left: calc(var(--spacing-md, 1rem) * 3 + 20px); + } + + &__changes-title { + margin: 0 0 var(--spacing-sm, 0.75rem); + font-size: var(--font-size-sm, 0.875rem); + font-weight: 600; + color: var(--color-text-primary, #111827); + } + + &__changes-table { + width: 100%; + border-collapse: collapse; + font-size: var(--font-size-xs, 0.75rem); + + th { + padding: var(--spacing-xs, 0.5rem) var(--spacing-sm, 0.75rem); + text-align: left; + font-weight: 600; + color: var(--color-text-secondary, #6b7280); + background: var(--color-bg-secondary, #f9fafb); + border-bottom: 1px solid var(--color-border-primary, #e5e7eb); + } + + td { + padding: var(--spacing-xs, 0.5rem) var(--spacing-sm, 0.75rem); + border-bottom: 1px solid var(--color-border-primary, #e5e7eb); + color: var(--color-text-secondary, #6b7280); + } + + tr:last-child td { + border-bottom: none; + } + } + + &__field { + font-weight: 500; + color: var(--color-text-primary, #111827); + } + + &__old-value { + color: var(--tl-status-error); + text-decoration: line-through; + } + + &__new-value { + color: var(--tl-change-added); + font-weight: 500; + } +} diff --git a/src/components/tl-audit-entry/tl-audit-entry.component.ts b/src/components/tl-audit-entry/tl-audit-entry.component.ts new file mode 100644 index 0000000..376dc72 --- /dev/null +++ b/src/components/tl-audit-entry/tl-audit-entry.component.ts @@ -0,0 +1,62 @@ +import { Component, ChangeDetectionStrategy, input, output, computed, signal } from '@angular/core'; +import { IconComponent, BadgeComponent, TooltipDirective } from '@sda/base-ui'; +import type { TlAuditEntry } from '../../types/audit.types'; +import type { TlAuditEntryClickEvent } from '../../types/event.types'; +import { formatSmartTime } from '../../utils/date.utils'; + +@Component({ + selector: 'tl-audit-entry', + standalone: true, + imports: [IconComponent, BadgeComponent, TooltipDirective], + templateUrl: './tl-audit-entry.component.html', + styleUrl: './tl-audit-entry.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + '[class.tl-audit-entry--expandable]': 'expandable()', + '[class.tl-audit-entry--expanded]': 'isExpanded()', + }, +}) +export class TlAuditEntryComponent { + readonly entry = input.required(); + readonly expandable = input(true); + readonly expanded = input(false); + readonly showSeverityIcon = input(true); + + readonly entryClick = output(); + readonly expandToggle = output(); + + protected readonly isExpanded = signal(false); + + protected readonly formattedTime = computed(() => formatSmartTime(this.entry().date)); + + protected readonly severityIcon = computed(() => { + switch (this.entry().severity) { + case 'critical': return 'alert-circle'; + case 'error': return 'x-circle'; + case 'warning': return 'alert-triangle'; + case 'info': return 'info'; + case 'debug': return 'bug'; + default: return 'info'; + } + }); + + protected readonly severityColor = computed(() => { + return `var(--tl-severity-${this.entry().severity})`; + }); + + protected readonly hasChanges = computed(() => { + return (this.entry().changes?.length ?? 0) > 0; + }); + + protected onEntryClick(): void { + this.entryClick.emit({ entry: this.entry() }); + if (this.expandable() && this.hasChanges()) { + this.toggleExpand(); + } + } + + protected toggleExpand(): void { + this.isExpanded.update(v => !v); + this.expandToggle.emit(this.isExpanded()); + } +} diff --git a/src/components/tl-audit-trail/index.ts b/src/components/tl-audit-trail/index.ts new file mode 100644 index 0000000..b6a3f50 --- /dev/null +++ b/src/components/tl-audit-trail/index.ts @@ -0,0 +1 @@ +export * from './tl-audit-trail.component'; diff --git a/src/components/tl-audit-trail/tl-audit-trail.component.html b/src/components/tl-audit-trail/tl-audit-trail.component.html new file mode 100644 index 0000000..2d309ca --- /dev/null +++ b/src/components/tl-audit-trail/tl-audit-trail.component.html @@ -0,0 +1,50 @@ +
+ @if (searchable() || filterable()) { + + } + +
+ @if (loading()) { +
+ @for (i of getLoadingItems(); track i) { +
+ + +
+ } +
+ } @else if (pagedEntries().length === 0) { +
+ +

{{ emptyMessage() }}

+
+ } @else { +
+ @for (entry of pagedEntries(); track entry.id) { + + } +
+ + @if (totalPages() > 1) { +
+ + Page {{ currentPage() }} of {{ totalPages() }} + ({{ filteredEntries().length }} entries) + +
+ } + } +
+
diff --git a/src/components/tl-audit-trail/tl-audit-trail.component.scss b/src/components/tl-audit-trail/tl-audit-trail.component.scss new file mode 100644 index 0000000..d44b15f --- /dev/null +++ b/src/components/tl-audit-trail/tl-audit-trail.component.scss @@ -0,0 +1,59 @@ +.tl-audit-trail { + &__content { + background: var(--color-bg-primary, #ffffff); + border: 1px solid var(--color-border-primary, #e5e7eb); + border-radius: var(--radius-lg, 0.5rem); + padding: var(--spacing-md, 1rem); + transition: all var(--transition-normal, 200ms ease); + padding: 0; + overflow: hidden; + } + + &__loading { + display: flex; + flex-direction: column; + } + + &__skeleton { + display: grid; + grid-template-columns: auto 1fr; + gap: var(--spacing-md, 1rem); + padding: var(--spacing-md, 1rem); + border-bottom: 1px solid var(--color-border-primary, #e5e7eb); + } + + &__empty { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + gap: var(--spacing-md, 1rem); + padding: var(--spacing-xl, 2rem); + color: var(--color-text-muted, #9ca3af); + text-align: center; + + p { + margin: 0; + font-size: var(--font-size-base, 1rem); + } + } + + &__entries { + display: flex; + flex-direction: column; + } + + &__pagination { + display: flex; + align-items: center; + justify-content: center; + padding: var(--spacing-md, 1rem); + border-top: 2px solid var(--color-border-primary, #e5e7eb); + background: var(--color-bg-secondary, #f9fafb); + } + + &__pagination-info { + font-size: var(--font-size-sm, 0.875rem); + color: var(--color-text-secondary, #6b7280); + } +} diff --git a/src/components/tl-audit-trail/tl-audit-trail.component.ts b/src/components/tl-audit-trail/tl-audit-trail.component.ts new file mode 100644 index 0000000..d5c93a6 --- /dev/null +++ b/src/components/tl-audit-trail/tl-audit-trail.component.ts @@ -0,0 +1,98 @@ +import { Component, ChangeDetectionStrategy, input, output, computed } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ButtonComponent, IconComponent, BadgeComponent, SkeletonComponent } from '@sda/base-ui'; +import { TlAuditEntryComponent } from '../tl-audit-entry'; +import { TlTimelineToolbarComponent } from '../tl-timeline-toolbar'; +import { TimelineService } from '../../services/timeline.service'; +import type { TlAuditEntry } from '../../types/audit.types'; +import type { TlAuditEntryClickEvent, TlAuditFilterChangeEvent, TlAuditPageChangeEvent } from '../../types/event.types'; +import { filterAuditEntries } from '../../utils/filter.utils'; + +@Component({ + selector: 'tl-audit-trail', + standalone: true, + imports: [ + CommonModule, + ButtonComponent, + IconComponent, + BadgeComponent, + SkeletonComponent, + TlAuditEntryComponent, + TlTimelineToolbarComponent, + ], + providers: [TimelineService], + templateUrl: './tl-audit-trail.component.html', + styleUrl: './tl-audit-trail.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TlAuditTrailComponent { + constructor(private readonly service: TimelineService) {} + + readonly entries = input.required(); + readonly pageSize = input(25); + readonly searchable = input(true); + readonly filterable = input(true); + readonly expandable = input(true); + readonly showSeverityIcons = input(true); + readonly loading = input(false); + readonly emptyMessage = input('No audit entries'); + readonly serverSide = input(false); + + readonly entryClick = output(); + readonly filterChange = output(); + readonly pageChange = output(); + + protected readonly currentPage = computed(() => 1); // Simplified for now + + protected readonly filteredEntries = computed(() => { + if (this.serverSide()) return this.entries(); + + return filterAuditEntries(this.entries(), { + searchTerm: this.service.searchTerm(), + categories: this.service.categoryFilter(), + severities: this.service.severityFilter(), + dateRange: this.service.dateRangeFilter(), + }); + }); + + protected readonly pagedEntries = computed(() => { + const filtered = this.filteredEntries(); + const pageSize = this.pageSize(); + const page = this.currentPage(); + const start = (page - 1) * pageSize; + return filtered.slice(start, start + pageSize); + }); + + protected readonly totalPages = computed(() => { + return Math.ceil(this.filteredEntries().length / this.pageSize()); + }); + + protected readonly activeFilterCount = computed(() => this.service.activeFilterCount()); + + protected onSearchChange(term: string): void { + this.service.setSearch(term); + this.emitFilterChange(); + } + + protected onClearFilters(): void { + this.service.clearAll(); + this.emitFilterChange(); + } + + protected onEntryClick(event: TlAuditEntryClickEvent): void { + this.entryClick.emit(event); + } + + private emitFilterChange(): void { + this.filterChange.emit({ + searchTerm: this.service.searchTerm(), + categories: this.service.categoryFilter(), + severities: this.service.severityFilter(), + dateRange: this.service.dateRangeFilter(), + }); + } + + protected getLoadingItems(): number[] { + return Array.from({ length: 5 }, (_, i) => i); + } +} diff --git a/src/components/tl-changelog-version/index.ts b/src/components/tl-changelog-version/index.ts new file mode 100644 index 0000000..479ba2c --- /dev/null +++ b/src/components/tl-changelog-version/index.ts @@ -0,0 +1 @@ +export * from './tl-changelog-version.component'; diff --git a/src/components/tl-changelog-version/tl-changelog-version.component.html b/src/components/tl-changelog-version/tl-changelog-version.component.html new file mode 100644 index 0000000..bff6e04 --- /dev/null +++ b/src/components/tl-changelog-version/tl-changelog-version.component.html @@ -0,0 +1,73 @@ +
+
+
+

{{ version().version }}

+ @if (version().title) { + {{ version().title }} + } +
+ + @if (collapsible()) { + + + + } +
+ + @if (version().description) { +

{{ version().description }}

+ } + + @if (isExpanded()) { +
+ @for (group of entriesByCategory(); track group.category) { +
+
+ +

+ {{ group.category | titlecase }} +

+ @if (showCategoryBadges()) { + + {{ group.entries.length }} + + } +
+ +
    + @for (entry of group.entries; track $index) { +
  • + + {{ entry.description }} + + @if (showBreakingBadge() && entry.breaking) { + BREAKING + } + @if (entry.reference) { + + + + } +
  • + } +
+
+ } +
+ } +
diff --git a/src/components/tl-changelog-version/tl-changelog-version.component.scss b/src/components/tl-changelog-version/tl-changelog-version.component.scss new file mode 100644 index 0000000..2d5e7b0 --- /dev/null +++ b/src/components/tl-changelog-version/tl-changelog-version.component.scss @@ -0,0 +1,128 @@ +.tl-changelog-version { + background: var(--color-bg-primary, #ffffff); + border: 1px solid var(--color-border-primary, #e5e7eb); + border-radius: var(--radius-lg, 0.5rem); + padding: var(--spacing-md, 1rem); + transition: all var(--transition-normal, 200ms ease); + margin-bottom: var(--spacing-lg, 1.5rem); + + &__header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--spacing-md, 1rem); + cursor: pointer; + + &:hover .tl-changelog-version__version { + color: var(--color-border-focus, #3b82f6); + } + } + + &__title-group { + display: flex; + align-items: baseline; + gap: var(--spacing-sm, 0.75rem); + flex: 1; + } + + &__version { + margin: 0; + font-size: 1.5rem; + font-weight: 700; + color: var(--color-text-primary, #111827); + transition: color var(--transition-normal, 200ms ease); + } + + &__title { + font-size: var(--font-size-base, 1rem); + color: var(--color-text-secondary, #6b7280); + } + + &__date { + font-size: var(--font-size-sm, 0.875rem); + color: var(--color-text-muted, #9ca3af); + } + + &__description { + margin: 0 0 var(--spacing-md, 1rem); + font-size: var(--font-size-base, 1rem); + color: var(--color-text-secondary, #6b7280); + line-height: 1.6; + } + + &__entries { + display: flex; + flex-direction: column; + gap: var(--spacing-lg, 1.5rem); + } + + &__category { + &:not(:last-child) { + padding-bottom: var(--spacing-md, 1rem); + border-bottom: 1px solid var(--color-border-primary, #e5e7eb); + } + } + + &__category-header { + display: flex; + align-items: center; + gap: var(--spacing-sm, 0.75rem); + margin-bottom: var(--spacing-sm, 0.75rem); + } + + &__category-title { + margin: 0; + font-size: var(--font-size-base, 1rem); + font-weight: 600; + color: var(--color-text-primary, #111827); + } + + &__list { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: var(--spacing-xs, 0.5rem); + } + + &__entry { + display: flex; + align-items: flex-start; + gap: var(--spacing-sm, 0.75rem); + padding: var(--spacing-xs, 0.5rem) 0; + font-size: var(--font-size-sm, 0.875rem); + color: var(--color-text-secondary, #6b7280); + line-height: 1.5; + + &::before { + content: '•'; + color: var(--color-text-muted, #9ca3af); + font-weight: bold; + margin-right: var(--spacing-xs, 0.5rem); + } + + &:hover { + color: var(--color-text-primary, #111827); + } + } + + &__entry-text { + flex: 1; + } + + &__reference { + display: flex; + align-items: center; + color: var(--color-border-focus, #3b82f6); + text-decoration: none; + + &:hover { + opacity: 0.8; + } + } + + &--collapsible &__header { + cursor: pointer; + } +} diff --git a/src/components/tl-changelog-version/tl-changelog-version.component.ts b/src/components/tl-changelog-version/tl-changelog-version.component.ts new file mode 100644 index 0000000..deb077e --- /dev/null +++ b/src/components/tl-changelog-version/tl-changelog-version.component.ts @@ -0,0 +1,82 @@ +import { Component, ChangeDetectionStrategy, input, output, computed, signal } from '@angular/core'; +import { TitleCasePipe } from '@angular/common'; +import { BadgeComponent, IconComponent, ButtonComponent } from '@sda/base-ui'; +import type { TlChangelogVersion, TlChangeCategory } from '../../types/changelog.types'; +import type { TlVersionClickEvent, TlChangelogEntryClickEvent } from '../../types/event.types'; +import { formatDate } from '../../utils/date.utils'; + +@Component({ + selector: 'tl-changelog-version', + standalone: true, + imports: [BadgeComponent, IconComponent, ButtonComponent, TitleCasePipe], + templateUrl: './tl-changelog-version.component.html', + styleUrl: './tl-changelog-version.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + '[class.tl-changelog-version--collapsible]': 'collapsible()', + '[class.tl-changelog-version--expanded]': 'isExpanded()', + }, +}) +export class TlChangelogVersionComponent { + readonly version = input.required(); + readonly collapsible = input(false); + readonly expanded = input(true); + readonly showCategoryBadges = input(true); + readonly showBreakingBadge = input(true); + + readonly versionClick = output(); + readonly entryClick = output(); + readonly expandToggle = output(); + + protected readonly isExpanded = signal(true); + + protected readonly formattedDate = computed(() => formatDate(this.version().date)); + + protected readonly entriesByCategory = computed(() => { + const entries = this.version().entries; + const categories: TlChangeCategory[] = ['added', 'changed', 'fixed', 'removed', 'deprecated', 'security']; + + return categories + .map(category => ({ + category, + entries: entries.filter(e => e.category === category), + })) + .filter(group => group.entries.length > 0); + }); + + protected readonly categoryColor = computed(() => { + return (category: TlChangeCategory): string => { + return `var(--tl-change-${category})`; + }; + }); + + protected readonly categoryIcon = computed(() => { + return (category: TlChangeCategory): string => { + switch (category) { + case 'added': return 'plus'; + case 'changed': return 'edit'; + case 'fixed': return 'check'; + case 'removed': return 'minus'; + case 'deprecated': return 'alert-triangle'; + case 'security': return 'shield'; + default: return 'circle'; + } + }; + }); + + protected onVersionClick(): void { + this.versionClick.emit({ version: this.version() }); + if (this.collapsible()) { + this.toggleExpand(); + } + } + + protected toggleExpand(): void { + this.isExpanded.update(v => !v); + this.expandToggle.emit(this.isExpanded()); + } + + protected onEntryClick(entry: any): void { + this.entryClick.emit({ entry, version: this.version() }); + } +} diff --git a/src/components/tl-changelog/index.ts b/src/components/tl-changelog/index.ts new file mode 100644 index 0000000..1a90ed2 --- /dev/null +++ b/src/components/tl-changelog/index.ts @@ -0,0 +1 @@ +export * from './tl-changelog.component'; diff --git a/src/components/tl-changelog/tl-changelog.component.html b/src/components/tl-changelog/tl-changelog.component.html new file mode 100644 index 0000000..f9dac28 --- /dev/null +++ b/src/components/tl-changelog/tl-changelog.component.html @@ -0,0 +1,31 @@ +
+ @if (loading()) { +
+ @for (i of getLoadingItems(); track i) { +
+ + +
+ } +
+ } @else if (versions().length === 0) { +
+

{{ emptyMessage() }}

+
+ } @else { +
+ @for (version of versions(); track version.version) { + + } +
+ } +
diff --git a/src/components/tl-changelog/tl-changelog.component.scss b/src/components/tl-changelog/tl-changelog.component.scss new file mode 100644 index 0000000..7ac66e8 --- /dev/null +++ b/src/components/tl-changelog/tl-changelog.component.scss @@ -0,0 +1,39 @@ +.tl-changelog { + &__loading { + display: flex; + flex-direction: column; + gap: var(--spacing-lg, 1.5rem); + } + + &__skeleton { + background: var(--color-bg-primary, #ffffff); + border: 1px solid var(--color-border-primary, #e5e7eb); + border-radius: var(--radius-lg, 0.5rem); + padding: var(--spacing-md, 1rem); + transition: all var(--transition-normal, 200ms ease); + display: flex; + flex-direction: column; + gap: var(--spacing-md, 1rem); + } + + &__empty { + background: var(--color-bg-primary, #ffffff); + border: 1px solid var(--color-border-primary, #e5e7eb); + border-radius: var(--radius-lg, 0.5rem); + padding: var(--spacing-md, 1rem); + transition: all var(--transition-normal, 200ms ease); + padding: var(--spacing-xl, 2rem); + text-align: center; + color: var(--color-text-muted, #9ca3af); + + p { + margin: 0; + font-size: var(--font-size-base, 1rem); + } + } + + &__versions { + display: flex; + flex-direction: column; + } +} diff --git a/src/components/tl-changelog/tl-changelog.component.ts b/src/components/tl-changelog/tl-changelog.component.ts new file mode 100644 index 0000000..8dc3930 --- /dev/null +++ b/src/components/tl-changelog/tl-changelog.component.ts @@ -0,0 +1,59 @@ +import { Component, ChangeDetectionStrategy, input, output, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SkeletonComponent } from '@sda/base-ui'; +import { TlChangelogVersionComponent } from '../tl-changelog-version'; +import type { TlChangelogVersion } from '../../types/changelog.types'; +import type { TlVersionClickEvent, TlChangelogEntryClickEvent } from '../../types/event.types'; + +@Component({ + selector: 'tl-changelog', + standalone: true, + imports: [CommonModule, SkeletonComponent, TlChangelogVersionComponent], + templateUrl: './tl-changelog.component.html', + styleUrl: './tl-changelog.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TlChangelogComponent { + readonly versions = input.required(); + readonly collapsible = input(false); + readonly expandLatest = input(true); + readonly showCategoryBadges = input(true); + readonly showBreakingBadge = input(true); + readonly loading = input(false); + readonly emptyMessage = input('No changelog entries'); + + readonly versionClick = output(); + readonly entryClick = output(); + + protected readonly expandedVersions = signal>(new Set()); + + protected isExpanded(version: string): boolean { + if (!this.collapsible()) return true; + if (this.expandLatest() && this.versions()[0]?.version === version) return true; + return this.expandedVersions().has(version); + } + + protected onVersionClick(event: TlVersionClickEvent): void { + this.versionClick.emit(event); + } + + protected onEntryClick(event: TlChangelogEntryClickEvent): void { + this.entryClick.emit(event); + } + + protected onExpandToggle(version: string, expanded: boolean): void { + this.expandedVersions.update(set => { + const newSet = new Set(set); + if (expanded) { + newSet.add(version); + } else { + newSet.delete(version); + } + return newSet; + }); + } + + protected getLoadingItems(): number[] { + return Array.from({ length: 3 }, (_, i) => i); + } +} diff --git a/src/components/tl-gantt-chart/index.ts b/src/components/tl-gantt-chart/index.ts new file mode 100644 index 0000000..02c0664 --- /dev/null +++ b/src/components/tl-gantt-chart/index.ts @@ -0,0 +1 @@ +export * from './tl-gantt-chart.component'; diff --git a/src/components/tl-gantt-chart/tl-gantt-chart.component.html b/src/components/tl-gantt-chart/tl-gantt-chart.component.html new file mode 100644 index 0000000..adb867e --- /dev/null +++ b/src/components/tl-gantt-chart/tl-gantt-chart.component.html @@ -0,0 +1,73 @@ +
+ + + @if (loading()) { +
+ @for (i of getLoadingItems(); track i) { + + } +
+ } @else if (tasks().length === 0) { +
+ +

{{ emptyMessage() }}

+
+ } @else { +
+ +
+
+ Task Name +
+ @for (task of sortedTasks(); track task.id) { +
+ {{ task.name }} +
+ } +
+ + +
+ +
+ @for (tick of dateAxis(); track tick.position) { +
+ {{ tick.label }} +
+ } +
+ + +
+ @for (task of sortedTasks(); track task.id; let idx = $index) { + + } + + + @if (showTodayLine() && todayPosition() !== null) { +
+ } +
+
+
+ } +
diff --git a/src/components/tl-gantt-chart/tl-gantt-chart.component.scss b/src/components/tl-gantt-chart/tl-gantt-chart.component.scss new file mode 100644 index 0000000..68498b1 --- /dev/null +++ b/src/components/tl-gantt-chart/tl-gantt-chart.component.scss @@ -0,0 +1,165 @@ +.tl-gantt-chart { + &__loading { + display: flex; + flex-direction: column; + gap: 1px; + } + + &__empty { + background: var(--color-bg-primary, #ffffff); + border: 1px solid var(--color-border-primary, #e5e7eb); + border-radius: var(--radius-lg, 0.5rem); + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + gap: var(--spacing-md, 1rem); + padding: var(--spacing-2xl, 3rem); + color: var(--color-text-muted, #9ca3af); + text-align: center; + + p { + margin: 0; + font-size: var(--font-size-sm, 0.875rem); + } + } + + &__container { + background: var(--color-bg-primary, #ffffff); + border: 1px solid var(--color-border-primary, #e5e7eb); + border-radius: var(--radius-lg, 0.5rem); + overflow: hidden; + display: grid; + grid-template-columns: var(--tl-gantt-sidebar-width) 1fr; + } + + // ── Sidebar ── + &__sidebar { + border-right: 1px solid var(--color-border-primary, #e5e7eb); + background: var(--color-bg-primary, #ffffff); + } + + &__sidebar-header { + display: flex; + align-items: center; + padding: 0 var(--spacing-md, 1rem); + font-weight: 600; + font-size: var(--font-size-xs, 0.75rem); + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--color-text-muted, #9ca3af); + background: var(--color-bg-secondary, #f9fafb); + border-bottom: 1px solid var(--color-border-primary, #e5e7eb); + } + + &__sidebar-row { + display: flex; + align-items: center; + padding: 0 var(--spacing-md, 1rem); + font-size: var(--font-size-sm, 0.875rem); + font-weight: 500; + color: var(--color-text-primary, #111827); + border-bottom: 1px solid var(--color-border-primary, #e5e7eb); + background: var(--color-bg-primary, #ffffff); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + transition: background var(--transition-fast, 150ms ease); + + &:nth-child(even) { + background: var(--color-bg-secondary, #f9fafb); + } + + &:hover { + background: var(--color-bg-hover, #f3f4f6); + } + } + + // ── Chart area ── + &__chart { + overflow-x: auto; + position: relative; + + &::-webkit-scrollbar { + height: 6px; + } + + &::-webkit-scrollbar-track { + background: var(--color-bg-secondary, #f9fafb); + } + + &::-webkit-scrollbar-thumb { + background: var(--color-border-primary, #e5e7eb); + border-radius: 3px; + + &:hover { + background: var(--color-text-muted, #9ca3af); + } + } + } + + // ── Date axis ── + &__axis { + position: relative; + height: var(--tl-gantt-axis-height); + background: var(--color-bg-secondary, #f9fafb); + border-bottom: 1px solid var(--color-border-primary, #e5e7eb); + } + + &__tick { + position: absolute; + top: 0; + height: 100%; + display: flex; + align-items: flex-end; + padding: 0 var(--spacing-xs, 0.5rem) 0.5rem; + font-size: 0.6875rem; + color: var(--color-text-muted, #9ca3af); + border-left: 1px solid var(--color-border-primary, #e5e7eb); + + &--major { + font-weight: 600; + font-size: var(--font-size-xs, 0.75rem); + color: var(--color-text-secondary, #6b7280); + border-left: 1px solid var(--color-text-muted, #9ca3af); + } + + &--weekend { + background: var(--tl-gantt-weekend-bg); + } + } + + // ── Rows ── + &__rows { + position: relative; + min-height: 200px; + } + + // ── Today line ── + &__today-line { + position: absolute; + top: 0; + bottom: 0; + width: 2px; + background: var(--tl-gantt-today-line); + z-index: 10; + pointer-events: none; + + &::before { + content: 'Today'; + position: absolute; + top: -20px; + left: 50%; + transform: translateX(-50%); + font-size: 0.625rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--color-bg-primary, #ffffff); + background: var(--tl-gantt-today-line); + padding: 1px 6px; + border-radius: var(--radius-sm, 0.25rem); + white-space: nowrap; + } + } +} diff --git a/src/components/tl-gantt-chart/tl-gantt-chart.component.ts b/src/components/tl-gantt-chart/tl-gantt-chart.component.ts new file mode 100644 index 0000000..2ae963b --- /dev/null +++ b/src/components/tl-gantt-chart/tl-gantt-chart.component.ts @@ -0,0 +1,102 @@ +import { Component, ChangeDetectionStrategy, input, output, computed } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ButtonComponent, IconComponent, TooltipDirective, SkeletonComponent } from '@sda/base-ui'; +import { TlGanttRowComponent } from '../tl-gantt-row'; +import { TlTimelineToolbarComponent } from '../tl-timeline-toolbar'; +import { GanttService } from '../../services/gantt.service'; +import type { TlGanttTask, TlGanttDependency, TlDateTick } from '../../types/gantt.types'; +import type { TlGanttTaskClickEvent, TlGanttTaskResizeEvent, TlGanttTaskMoveEvent, TlGanttZoomChangeEvent, TlGanttScrollEvent } from '../../types/event.types'; +import { calculateDateRange, sortTasksHierarchically, calculateChartWidth } from '../../utils/gantt.utils'; + +@Component({ + selector: 'tl-gantt-chart', + standalone: true, + imports: [ + CommonModule, + ButtonComponent, + IconComponent, + TooltipDirective, + SkeletonComponent, + TlGanttRowComponent, + TlTimelineToolbarComponent, + ], + providers: [GanttService], + templateUrl: './tl-gantt-chart.component.html', + styleUrl: './tl-gantt-chart.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TlGanttChartComponent { + constructor(protected readonly ganttService: GanttService) {} + + readonly tasks = input.required(); + readonly dependencies = input([]); + readonly zoomLevel = input<'day' | 'week' | 'month' | 'quarter' | 'year'>('week'); + readonly rowHeight = input(40); + readonly showDependencies = input(true); + readonly showProgress = input(true); + readonly showTodayLine = input(true); + readonly loading = input(false); + readonly emptyMessage = input('No tasks'); + + readonly taskClick = output(); + readonly taskResize = output(); + readonly taskMove = output(); + readonly zoomChange = output(); + readonly scroll = output(); + + protected readonly dateRange = computed(() => { + if (this.tasks().length === 0) { + return { start: new Date(), end: new Date() }; + } + return calculateDateRange(this.tasks()); + }); + + protected readonly dateAxis = computed(() => { + const range = this.dateRange(); + return this.ganttService.calculateDateAxis(range.start, range.end); + }); + + protected readonly sortedTasks = computed(() => { + return sortTasksHierarchically(this.tasks()); + }); + + protected readonly taskPositions = computed(() => { + const range = this.dateRange(); + return this.sortedTasks().map(task => + this.ganttService.calculateTaskPosition(task, range.start) + ); + }); + + protected readonly todayPosition = computed(() => { + if (!this.showTodayLine()) return null; + const range = this.dateRange(); + return this.ganttService.calculateTodayPosition(range.start); + }); + + protected readonly totalWidth = computed(() => { + const range = this.dateRange(); + return calculateChartWidth(range.start, range.end, this.ganttService.pixelsPerDay()); + }); + + protected onZoomIn(): void { + this.ganttService.zoomIn(); + this.zoomChange.emit({ level: this.ganttService.zoomLevel() }); + } + + protected onZoomOut(): void { + this.ganttService.zoomOut(); + this.zoomChange.emit({ level: this.ganttService.zoomLevel() }); + } + + protected onTaskClick(event: TlGanttTaskClickEvent): void { + this.taskClick.emit(event); + } + + protected onTaskResize(event: TlGanttTaskResizeEvent): void { + this.taskResize.emit(event); + } + + protected getLoadingItems(): number[] { + return Array.from({ length: 5 }, (_, i) => i); + } +} diff --git a/src/components/tl-gantt-row/index.ts b/src/components/tl-gantt-row/index.ts new file mode 100644 index 0000000..2a7a5fd --- /dev/null +++ b/src/components/tl-gantt-row/index.ts @@ -0,0 +1 @@ +export * from './tl-gantt-row.component'; diff --git a/src/components/tl-gantt-row/tl-gantt-row.component.html b/src/components/tl-gantt-row/tl-gantt-row.component.html new file mode 100644 index 0000000..24c893f --- /dev/null +++ b/src/components/tl-gantt-row/tl-gantt-row.component.html @@ -0,0 +1,20 @@ +
+
+ @if (showProgress() && task().progress !== undefined) { +
+ } + {{ task().name }} +
+
diff --git a/src/components/tl-gantt-row/tl-gantt-row.component.scss b/src/components/tl-gantt-row/tl-gantt-row.component.scss new file mode 100644 index 0000000..524210e --- /dev/null +++ b/src/components/tl-gantt-row/tl-gantt-row.component.scss @@ -0,0 +1,79 @@ +.tl-gantt-row { + position: relative; + border-bottom: 1px solid var(--color-border-primary, #e5e7eb); + background: var(--color-bg-primary, #ffffff); + + &:nth-child(even) { + background: var(--color-bg-secondary, #f9fafb); + } + + &__bar { + position: absolute; + top: 50%; + transform: translateY(-50%); + height: var(--tl-gantt-bar-height); + border-radius: var(--radius-base, 4px); + cursor: pointer; + overflow: hidden; + transition: box-shadow var(--transition-fast, 150ms ease), + transform var(--transition-fast, 150ms ease); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); + + &:hover { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + transform: translateY(-50%) scaleY(1.08); + z-index: 2; + } + } + + &__progress { + position: absolute; + inset: 0; + background: rgba(255, 255, 255, 0.25); + border-right: 2px solid rgba(255, 255, 255, 0.4); + transition: width var(--transition-normal, 200ms ease); + } + + &__label { + position: relative; + display: block; + padding: 0 var(--spacing-sm, 0.75rem); + line-height: var(--tl-gantt-bar-height); + font-size: 0.6875rem; + font-weight: 600; + color: var(--color-text-inverse, #ffffff); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + z-index: 1; + } + + // Status modifiers + &--completed &__bar { + opacity: 0.65; + } + + &--on-hold &__bar { + opacity: 0.5; + background-image: repeating-linear-gradient( + -45deg, + transparent, + transparent 4px, + rgba(255, 255, 255, 0.1) 4px, + rgba(255, 255, 255, 0.1) 8px + ); + } + + &--delayed &__bar { + animation: tl-pulse 2s ease-in-out infinite; + } +} + +@keyframes tl-pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.7; + } +} diff --git a/src/components/tl-gantt-row/tl-gantt-row.component.ts b/src/components/tl-gantt-row/tl-gantt-row.component.ts new file mode 100644 index 0000000..0e2fce6 --- /dev/null +++ b/src/components/tl-gantt-row/tl-gantt-row.component.ts @@ -0,0 +1,72 @@ +import { Component, ChangeDetectionStrategy, input, output, computed } from '@angular/core'; +import { NgStyle } from '@angular/common'; +import { TooltipDirective, IconComponent } from '@sda/base-ui'; +import type { TlGanttTask, TlGanttTaskPosition } from '../../types/gantt.types'; +import type { TlGanttTaskClickEvent, TlGanttTaskResizeEvent } from '../../types/event.types'; +import { formatDate, diffInDays } from '../../utils/date.utils'; + +@Component({ + selector: 'tl-gantt-row', + standalone: true, + imports: [NgStyle, TooltipDirective, IconComponent], + templateUrl: './tl-gantt-row.component.html', + styleUrl: './tl-gantt-row.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + '[class.tl-gantt-row--clickable]': 'true', + }, +}) +export class TlGanttRowComponent { + readonly task = input.required(); + readonly position = input.required(); + readonly showProgress = input(true); + readonly rowHeight = input(40); + + readonly taskClick = output(); + readonly taskResize = output(); + + protected readonly statusClass = computed(() => { + const status = this.task().status; + return status ? `tl-gantt-row--${status}` : ''; + }); + + protected readonly progressWidth = computed(() => { + const progress = this.task().progress ?? 0; + return `${Math.min(100, Math.max(0, progress))}%`; + }); + + protected readonly barColor = computed(() => { + const task = this.task(); + if (task.color) return task.color; + if (task.status === 'completed') return 'var(--tl-status-completed)'; + if (task.status === 'delayed') return 'var(--tl-status-error)'; + if (task.status === 'on-hold') return 'var(--tl-status-warning)'; + if (task.status === 'in-progress') return 'var(--tl-status-active)'; + return 'var(--tl-status-pending)'; + }); + + protected readonly barStyle = computed(() => { + const pos = this.position(); + return { + left: `${pos.left}px`, + width: `${pos.width}px`, + 'background-color': this.barColor(), + }; + }); + + protected readonly tooltipContent = computed(() => { + const task = this.task(); + const duration = diffInDays(task.startDate, task.endDate); + return ` + ${task.name} + ${formatDate(task.startDate)} - ${formatDate(task.endDate)} + Duration: ${duration} days + ${task.progress !== undefined ? `Progress: ${task.progress}%` : ''} + ${task.assignee ? `Assignee: ${task.assignee}` : ''} + `.trim(); + }); + + protected onTaskClick(): void { + this.taskClick.emit({ task: this.task() }); + } +} diff --git a/src/components/tl-horizontal-timeline/index.ts b/src/components/tl-horizontal-timeline/index.ts new file mode 100644 index 0000000..4c25abf --- /dev/null +++ b/src/components/tl-horizontal-timeline/index.ts @@ -0,0 +1 @@ +export * from './tl-horizontal-timeline.component'; diff --git a/src/components/tl-horizontal-timeline/tl-horizontal-timeline.component.html b/src/components/tl-horizontal-timeline/tl-horizontal-timeline.component.html new file mode 100644 index 0000000..86dce55 --- /dev/null +++ b/src/components/tl-horizontal-timeline/tl-horizontal-timeline.component.html @@ -0,0 +1,75 @@ +
+ @if (showNavArrows && canScrollLeft) { + + + + } + +
+ @if (loading) { +
+ @for (i of getLoadingItems(); track i) { +
+ + +
+ } +
+ } @else if (items.length === 0) { +
+ +

{{ emptyMessage }}

+
+ } @else { +
+ @for (item of sortedItems; track item.id; let idx = $index) { +
+
+ @if (item.icon) { + + } +
+ +
+ +

{{ item.title }}

+ @if (item.description) { +

{{ item.description }}

+ } +
+ + @if (!$last) { +
+ } +
+ } +
+ } +
+ + @if (showNavArrows && canScrollRight) { + + + + } +
diff --git a/src/components/tl-horizontal-timeline/tl-horizontal-timeline.component.scss b/src/components/tl-horizontal-timeline/tl-horizontal-timeline.component.scss new file mode 100644 index 0000000..83f68f0 --- /dev/null +++ b/src/components/tl-horizontal-timeline/tl-horizontal-timeline.component.scss @@ -0,0 +1,136 @@ +@use '../../styles/mixins' as *; + +.tl-horizontal-timeline { + position: relative; + display: flex; + align-items: center; + gap: var(--spacing-md, 1rem); + + &__nav { + position: absolute; + z-index: 2; + box-shadow: var(--shadow-md, 0 4px 6px rgba(0, 0, 0, 0.1)); + + &--left { + left: var(--spacing-md, 1rem); + } + + &--right { + right: var(--spacing-md, 1rem); + } + } + + &__container { + @include tl-scrollable; + flex: 1; + overflow-x: auto; + padding: var(--spacing-xl, 2rem) var(--spacing-lg, 1.5rem); + } + + &__loading, + &__track { + display: flex; + gap: var(--spacing-2xl, 2.5rem); + align-items: flex-start; + min-width: min-content; + } + + &__skeleton { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--spacing-md, 1rem); + } + + &__empty { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + gap: var(--spacing-lg, 1.5rem); + width: 100%; + padding: var(--spacing-2xl, 3rem); + color: var(--color-text-muted, #9ca3af); + text-align: center; + + p { + margin: 0; + font-size: var(--font-size-base, 1rem); + line-height: var(--line-height-relaxed, 1.625); + } + } + + &__item { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + gap: var(--spacing-md, 1rem); + min-width: 160px; + cursor: pointer; + transition: transform var(--transition-normal, 200ms ease); + + &:hover { + transform: translateY(-2px); + + .tl-horizontal-timeline__content { + background: var(--color-bg-hover, #f3f4f6); + } + } + } + + &__marker { + @include tl-marker(md); + } + + &__connector { + position: absolute; + top: 16px; + left: 100%; + width: var(--spacing-2xl, 2.5rem); + height: var(--tl-line-width); + background: var(--tl-line-color); + } + + &__content { + background: var(--color-bg-primary, #ffffff); + border: 1px solid var(--color-border-primary, #e5e7eb); + border-radius: var(--radius-lg, 0.5rem); + padding: var(--spacing-md, 1rem) var(--spacing-lg, 1.5rem); + text-align: center; + min-width: 120px; + max-width: 220px; + transition: all var(--transition-normal, 200ms ease); + } + + &__time { + display: block; + font-size: var(--font-size-xs, 0.75rem); + color: var(--color-text-muted, #9ca3af); + margin-bottom: var(--spacing-xs, 0.5rem); + font-weight: var(--font-weight-medium, 500); + } + + &__title { + margin: 0 0 var(--spacing-xs, 0.5rem); + font-size: var(--font-size-sm, 0.875rem); + font-weight: var(--font-weight-semibold, 600); + color: var(--color-text-primary, #111827); + line-height: var(--line-height-snug, 1.375); + letter-spacing: var(--letter-spacing-tight, -0.025em); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &__description { + margin: 0; + font-size: var(--font-size-xs, 0.75rem); + color: var(--color-text-secondary, #6b7280); + line-height: var(--line-height-normal, 1.5); + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + } +} diff --git a/src/components/tl-horizontal-timeline/tl-horizontal-timeline.component.ts b/src/components/tl-horizontal-timeline/tl-horizontal-timeline.component.ts new file mode 100644 index 0000000..0fbb376 --- /dev/null +++ b/src/components/tl-horizontal-timeline/tl-horizontal-timeline.component.ts @@ -0,0 +1,92 @@ +import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, ViewChild, ElementRef, AfterViewInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ButtonComponent, IconComponent, SkeletonComponent, TooltipDirective } from '@sda/base-ui'; +import type { TlTimelineItem } from '../../types/timeline.types'; +import type { TlMarkerSize, TlConnectorStyle, TlSortDirection } from '../../types/config.types'; +import type { TlTimelineItemClickEvent } from '../../types/event.types'; +import { formatSmartTime } from '../../utils/date.utils'; + +@Component({ + selector: 'tl-horizontal-timeline', + standalone: true, + imports: [CommonModule, ButtonComponent, IconComponent, SkeletonComponent, TooltipDirective], + templateUrl: './tl-horizontal-timeline.component.html', + styleUrl: './tl-horizontal-timeline.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TlHorizontalTimelineComponent implements AfterViewInit { + @ViewChild('scrollContainer') scrollContainer?: ElementRef; + + @Input({ required: true }) items!: TlTimelineItem[]; + @Input() markerSize: TlMarkerSize = 'md'; + @Input() connectorStyle: TlConnectorStyle = 'solid'; + @Input() sortDirection: TlSortDirection = 'desc'; + @Input() scrollable = true; + @Input() showNavArrows = true; + @Input() loading = false; + @Input() emptyMessage = 'No timeline items'; + + @Output() itemClick = new EventEmitter(); + + protected canScrollLeft = false; + protected canScrollRight = false; + + protected get sortedItems(): TlTimelineItem[] { + const items = [...this.items]; + const direction = this.sortDirection; + + items.sort((a, b) => { + const aTime = new Date(a.date).getTime(); + const bTime = new Date(b.date).getTime(); + return direction === 'asc' ? aTime - bTime : bTime - aTime; + }); + + return items; + } + + ngAfterViewInit(): void { + this.updateScrollState(); + } + + protected formatTime(item: TlTimelineItem): string { + return formatSmartTime(item.date); + } + + protected getMarkerColor(item: TlTimelineItem): string { + if (item.iconColor) return item.iconColor; + if (item.status) return `var(--tl-status-${item.status})`; + return 'var(--tl-marker-bg)'; + } + + protected onItemClick(item: TlTimelineItem, index: number): void { + this.itemClick.emit({ item, index }); + } + + protected scrollLeft(): void { + const container = this.scrollContainer?.nativeElement; + if (container) { + container.scrollBy({ left: -200, behavior: 'smooth' }); + setTimeout(() => this.updateScrollState(), 300); + } + } + + protected scrollRight(): void { + const container = this.scrollContainer?.nativeElement; + if (container) { + container.scrollBy({ left: 200, behavior: 'smooth' }); + setTimeout(() => this.updateScrollState(), 300); + } + } + + protected updateScrollState(): void { + const container = this.scrollContainer?.nativeElement; + if (container) { + this.canScrollLeft = container.scrollLeft > 0; + this.canScrollRight = container.scrollLeft < container.scrollWidth - container.clientWidth - 1; + } + } + + protected getLoadingItems(): number[] { + return Array.from({ length: 5 }, (_, i) => i); + } +} diff --git a/src/components/tl-timeline-item/index.ts b/src/components/tl-timeline-item/index.ts new file mode 100644 index 0000000..136c426 --- /dev/null +++ b/src/components/tl-timeline-item/index.ts @@ -0,0 +1 @@ +export * from './tl-timeline-item.component'; diff --git a/src/components/tl-timeline-item/tl-timeline-item.component.html b/src/components/tl-timeline-item/tl-timeline-item.component.html new file mode 100644 index 0000000..c0d620e --- /dev/null +++ b/src/components/tl-timeline-item/tl-timeline-item.component.html @@ -0,0 +1,34 @@ +
+
+ @if (item.icon) { + + } +
+ +
+ +
+

{{ item.title }}

+ +
+ + @if (item.description) { +

{{ item.description }}

+ } + + @if (item.tags?.length) { +
+ @for (tag of item.tags; track tag) { + {{ tag }} + } +
+ } + + @if (item.category) { +
+ {{ item.category }} +
+ } +
+
+
diff --git a/src/components/tl-timeline-item/tl-timeline-item.component.scss b/src/components/tl-timeline-item/tl-timeline-item.component.scss new file mode 100644 index 0000000..4645a89 --- /dev/null +++ b/src/components/tl-timeline-item/tl-timeline-item.component.scss @@ -0,0 +1,124 @@ +@use '../../styles/mixins' as *; + +.tl-timeline-item { + position: relative; + display: grid; + grid-template-columns: auto 1fr; + gap: var(--spacing-lg, 1.5rem); + padding: var(--spacing-md, 1rem) 0; + + &::after { + @include tl-connector(solid); + left: calc(var(--tl-marker-size-md) / 2 - var(--tl-line-width) / 2); + top: calc(var(--tl-marker-size-md) + var(--spacing-md, 1rem)); + bottom: calc(-1 * var(--spacing-md, 1rem)); + } + + &:last-child::after { + display: none; + } + + &--animate { + animation: tl-slide-in 300ms ease-out; + } + + &__marker { + @include tl-marker(md); + } + + &__content { + min-width: 0; + } + + &__card { + background: var(--color-bg-primary, #ffffff); + border: 1px solid var(--color-border-primary, #e5e7eb); + border-radius: var(--radius-lg, 0.5rem); + padding: var(--spacing-lg, 1.5rem); + transition: all var(--transition-normal, 200ms ease); + } + + &__header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--spacing-sm, 0.75rem); + gap: var(--spacing-md, 1rem); + } + + &__title { + margin: 0; + font-size: var(--font-size-base, 1rem); + font-weight: var(--font-weight-semibold, 600); + color: var(--color-text-primary, #111827); + line-height: var(--line-height-snug, 1.375); + letter-spacing: var(--letter-spacing-tight, -0.025em); + } + + &__time { + font-size: var(--font-size-xs, 0.75rem); + color: var(--color-text-muted, #9ca3af); + white-space: nowrap; + font-weight: var(--font-weight-medium, 500); + } + + &__description { + margin: var(--spacing-xs, 0.5rem) 0 0; + font-size: var(--font-size-sm, 0.875rem); + color: var(--color-text-secondary, #6b7280); + line-height: var(--line-height-relaxed, 1.625); + } + + &__tags { + display: flex; + flex-wrap: wrap; + gap: var(--spacing-xs, 0.5rem); + margin-top: var(--spacing-md, 1rem); + } + + &__category { + margin-top: var(--spacing-md, 1rem); + } + + // Status colors + &--completed .tl-timeline-item__marker { + background: var(--tl-status-completed); + } + + &--active .tl-timeline-item__marker { + background: var(--tl-status-active); + } + + &--pending .tl-timeline-item__marker { + background: var(--tl-status-pending); + } + + &--error .tl-timeline-item__marker { + background: var(--tl-status-error); + } + + &--warning .tl-timeline-item__marker { + background: var(--tl-status-warning); + } + + // Alternating layout + .tl-timeline-item--alternating &:nth-child(even) { + grid-template-columns: 1fr auto; + direction: rtl; + + .tl-timeline-item__content { + direction: ltr; + } + } +} + +@keyframes tl-slide-in { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} diff --git a/src/components/tl-timeline-item/tl-timeline-item.component.ts b/src/components/tl-timeline-item/tl-timeline-item.component.ts new file mode 100644 index 0000000..185f090 --- /dev/null +++ b/src/components/tl-timeline-item/tl-timeline-item.component.ts @@ -0,0 +1,54 @@ +import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter } from '@angular/core'; +import { IconComponent, CardComponent, BadgeComponent, TooltipDirective } from '@sda/base-ui'; +import type { TlTimelineItem } from '../../types/timeline.types'; +import type { TlTimelineLayout, TlMarkerSize, TlConnectorStyle } from '../../types/config.types'; +import type { TlTimelineItemClickEvent, TlTimelineItemHoverEvent } from '../../types/event.types'; +import { formatSmartTime } from '../../utils/date.utils'; + +@Component({ + selector: 'tl-timeline-item', + standalone: true, + imports: [IconComponent, CardComponent, BadgeComponent, TooltipDirective], + templateUrl: './tl-timeline-item.component.html', + styleUrl: './tl-timeline-item.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + '[class.tl-timeline-item--left]': 'layout === "left"', + '[class.tl-timeline-item--right]': 'layout === "right"', + '[class.tl-timeline-item--alternating]': 'layout === "alternating"', + '[class.tl-timeline-item--animate]': 'animate', + }, +}) +export class TlTimelineItemComponent { + @Input({ required: true }) item!: TlTimelineItem; + @Input() layout: TlTimelineLayout = 'left'; + @Input() markerSize: TlMarkerSize = 'md'; + @Input() connectorStyle: TlConnectorStyle = 'solid'; + @Input() animate = false; + @Input() position: 'left' | 'right' = 'left'; + + @Output() itemClick = new EventEmitter(); + @Output() itemHover = new EventEmitter(); + + protected get formattedTime(): string { + return formatSmartTime(this.item.date); + } + + protected get markerColor(): string { + if (this.item.iconColor) return this.item.iconColor; + if (this.item.status) return `var(--tl-status-${this.item.status})`; + return 'var(--tl-marker-bg)'; + } + + protected get statusClass(): string { + return this.item.status ? `tl-timeline-item--${this.item.status}` : ''; + } + + protected onClick(index: number): void { + this.itemClick.emit({ item: this.item, index }); + } + + protected onHover(index: number): void { + this.itemHover.emit({ item: this.item, index }); + } +} diff --git a/src/components/tl-timeline-toolbar/index.ts b/src/components/tl-timeline-toolbar/index.ts new file mode 100644 index 0000000..ec93f6f --- /dev/null +++ b/src/components/tl-timeline-toolbar/index.ts @@ -0,0 +1 @@ +export * from './tl-timeline-toolbar.component'; diff --git a/src/components/tl-timeline-toolbar/tl-timeline-toolbar.component.html b/src/components/tl-timeline-toolbar/tl-timeline-toolbar.component.html new file mode 100644 index 0000000..31a7d19 --- /dev/null +++ b/src/components/tl-timeline-toolbar/tl-timeline-toolbar.component.html @@ -0,0 +1,56 @@ +
+ @if (searchable()) { + + } + +
+ @if (showFilter()) { + + + @if (filterCount() > 0) { + {{ filterCount() }} + } + + } + + @if (showZoom()) { + + + + + + + } + + @if (showSort()) { + + + + } +
+
diff --git a/src/components/tl-timeline-toolbar/tl-timeline-toolbar.component.scss b/src/components/tl-timeline-toolbar/tl-timeline-toolbar.component.scss new file mode 100644 index 0000000..a14819a --- /dev/null +++ b/src/components/tl-timeline-toolbar/tl-timeline-toolbar.component.scss @@ -0,0 +1,22 @@ +.tl-timeline-toolbar { + display: flex; + align-items: center; + gap: var(--spacing-xs, 0.5rem); + padding: var(--spacing-xs, 0.5rem) var(--spacing-md, 1rem); + background: var(--color-bg-secondary, #f9fafb); + border: 1px solid var(--color-border-primary, #e5e7eb); + border-radius: var(--radius-lg, 0.5rem); + margin-bottom: var(--spacing-sm, 0.75rem); + + &__search { + flex: 1; + min-width: 200px; + } + + &__actions { + display: flex; + gap: var(--spacing-xs, 0.5rem); + align-items: center; + margin-left: auto; + } +} diff --git a/src/components/tl-timeline-toolbar/tl-timeline-toolbar.component.ts b/src/components/tl-timeline-toolbar/tl-timeline-toolbar.component.ts new file mode 100644 index 0000000..6aae4ec --- /dev/null +++ b/src/components/tl-timeline-toolbar/tl-timeline-toolbar.component.ts @@ -0,0 +1,45 @@ +import { Component, ChangeDetectionStrategy, input, output } from '@angular/core'; +import { ButtonComponent, IconComponent, BadgeComponent, InputComponent } from '@sda/base-ui'; + +@Component({ + selector: 'tl-timeline-toolbar', + standalone: true, + imports: [ButtonComponent, IconComponent, BadgeComponent, InputComponent], + templateUrl: './tl-timeline-toolbar.component.html', + styleUrl: './tl-timeline-toolbar.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TlTimelineToolbarComponent { + readonly searchable = input(false); + readonly searchPlaceholder = input('Search...'); + readonly showFilter = input(false); + readonly showZoom = input(false); + readonly showSort = input(false); + readonly filterCount = input(0); + + readonly searchChange = output(); + readonly clearFilters = output(); + readonly zoomIn = output(); + readonly zoomOut = output(); + readonly sortToggle = output(); + + protected onSearchChange(value: string): void { + this.searchChange.emit(value); + } + + protected onClearFilters(): void { + this.clearFilters.emit(); + } + + protected onZoomIn(): void { + this.zoomIn.emit(); + } + + protected onZoomOut(): void { + this.zoomOut.emit(); + } + + protected onSortToggle(): void { + this.sortToggle.emit(); + } +} diff --git a/src/components/tl-timeline/index.ts b/src/components/tl-timeline/index.ts new file mode 100644 index 0000000..30185ff --- /dev/null +++ b/src/components/tl-timeline/index.ts @@ -0,0 +1 @@ +export * from './tl-timeline.component'; diff --git a/src/components/tl-timeline/tl-timeline.component.html b/src/components/tl-timeline/tl-timeline.component.html new file mode 100644 index 0000000..9ff7d20 --- /dev/null +++ b/src/components/tl-timeline/tl-timeline.component.html @@ -0,0 +1,54 @@ +
+ @if (loading) { +
+ @for (i of getLoadingItems(); track i) { +
+ + +
+ } +
+ } @else if (items.length === 0) { +
+ +

{{ emptyMessage }}

+
+ } @else { + @if (groupByDate) { + @for (group of groupedItems; track group.label) { +
+

{{ group.label }}

+
+ @for (item of group.items; track item.id) { + + } +
+
+ } + } @else { +
+ @for (item of sortedItems; track item.id) { + + } +
+ } + } +
diff --git a/src/components/tl-timeline/tl-timeline.component.scss b/src/components/tl-timeline/tl-timeline.component.scss new file mode 100644 index 0000000..ea8a08e --- /dev/null +++ b/src/components/tl-timeline/tl-timeline.component.scss @@ -0,0 +1,57 @@ +@use '../../styles/mixins' as *; + +.tl-timeline { + &__loading { + display: flex; + flex-direction: column; + gap: var(--spacing-xl, 2rem); + } + + &__skeleton { + display: grid; + grid-template-columns: auto 1fr; + gap: var(--spacing-lg, 1.5rem); + align-items: start; + } + + &__empty { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + gap: var(--spacing-lg, 1.5rem); + padding: var(--spacing-2xl, 3rem); + color: var(--color-text-muted, #9ca3af); + text-align: center; + + p { + margin: 0; + font-size: var(--font-size-base, 1rem); + line-height: var(--line-height-relaxed, 1.625); + } + } + + &__group { + margin-bottom: var(--spacing-2xl, 2.5rem); + + &:last-child { + margin-bottom: 0; + } + } + + &__group-label { + margin: 0 0 var(--spacing-xl, 2rem); + font-size: var(--font-size-lg, 1.125rem); + font-weight: var(--font-weight-semibold, 600); + color: var(--color-text-primary, #111827); + padding-bottom: var(--spacing-md, 1rem); + border-bottom: 1px solid var(--color-border-primary, #e5e7eb); + line-height: var(--line-height-tight, 1.25); + letter-spacing: var(--letter-spacing-tight, -0.025em); + } + + &__items { + display: flex; + flex-direction: column; + } +} diff --git a/src/components/tl-timeline/tl-timeline.component.ts b/src/components/tl-timeline/tl-timeline.component.ts new file mode 100644 index 0000000..bac6bab --- /dev/null +++ b/src/components/tl-timeline/tl-timeline.component.ts @@ -0,0 +1,73 @@ +import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SkeletonComponent, IconComponent } from '@sda/base-ui'; +import { TlTimelineItemComponent } from '../tl-timeline-item'; +import type { TlTimelineItem } from '../../types/timeline.types'; +import type { TlTimelineLayout, TlMarkerSize, TlConnectorStyle, TlSortDirection } from '../../types/config.types'; +import type { TlTimelineItemClickEvent, TlTimelineItemHoverEvent } from '../../types/event.types'; +import { groupByDate } from '../../utils/date.utils'; + +@Component({ + selector: 'tl-timeline', + standalone: true, + imports: [CommonModule, SkeletonComponent, IconComponent, TlTimelineItemComponent], + templateUrl: './tl-timeline.component.html', + styleUrl: './tl-timeline.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + '[class.tl-timeline--left]': 'layout === "left"', + '[class.tl-timeline--right]': 'layout === "right"', + '[class.tl-timeline--alternating]': 'layout === "alternating"', + }, +}) +export class TlTimelineComponent { + @Input({ required: true }) items!: TlTimelineItem[]; + @Input() layout: TlTimelineLayout = 'left'; + @Input() markerSize: TlMarkerSize = 'md'; + @Input() connectorStyle: TlConnectorStyle = 'solid'; + @Input() sortDirection: TlSortDirection = 'desc'; + @Input() groupByDate = false; + @Input() animate = false; + @Input() loading = false; + @Input() emptyMessage = 'No timeline items'; + + @Output() itemClick = new EventEmitter(); + @Output() itemHover = new EventEmitter(); + + protected get sortedItems(): TlTimelineItem[] { + const items = [...this.items]; + const direction = this.sortDirection; + + items.sort((a, b) => { + const aTime = new Date(a.date).getTime(); + const bTime = new Date(b.date).getTime(); + return direction === 'asc' ? aTime - bTime : bTime - aTime; + }); + + return items; + } + + protected get groupedItems() { + if (!this.groupByDate) return null; + return groupByDate(this.sortedItems); + } + + protected getLoadingItems(): number[] { + return Array.from({ length: 5 }, (_, i) => i); + } + + protected onItemClick(event: TlTimelineItemClickEvent): void { + this.itemClick.emit(event); + } + + protected onItemHover(event: TlTimelineItemHoverEvent): void { + this.itemHover.emit(event); + } + + protected getItemPosition(index: number): 'left' | 'right' { + if (this.layout === 'alternating') { + return index % 2 === 0 ? 'left' : 'right'; + } + return 'left'; + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..389119c --- /dev/null +++ b/src/index.ts @@ -0,0 +1,65 @@ +/** + * Timeline Elements UI + * Main library entry point + * + * Angular components for timeline, activity feed, audit trail, changelog, + * and Gantt chart displays powered by @sda/base-ui + * + * @example + * ```typescript + * import { Component } from '@angular/core'; + * import { TlTimelineComponent, type TlTimelineItem } from '@sda/timeline-elements-ui'; + * + * @Component({ + * standalone: true, + * imports: [TlTimelineComponent], + * template: ` + * + * ` + * }) + * export class AppComponent { + * events: TlTimelineItem[] = [ + * { + * id: '1', + * title: 'Tenancy started', + * description: 'John Doe moved in', + * date: new Date('2024-01-01'), + * icon: 'home', + * status: 'completed' + * }, + * { + * id: '2', + * title: 'Inspection scheduled', + * description: 'Quarterly property inspection', + * date: new Date('2024-02-15'), + * icon: 'clipboard', + * status: 'pending' + * } + * ]; + * + * onItemClick(event: any) { + * console.log('Clicked:', event.item); + * } + * } + * ``` + */ + +// 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..1e1a00c --- /dev/null +++ b/src/providers/index.ts @@ -0,0 +1 @@ +export * from './timeline-config.provider'; diff --git a/src/providers/timeline-config.provider.ts b/src/providers/timeline-config.provider.ts new file mode 100644 index 0000000..15ec2fd --- /dev/null +++ b/src/providers/timeline-config.provider.ts @@ -0,0 +1,56 @@ +import { InjectionToken, makeEnvironmentProviders, EnvironmentProviders } from '@angular/core'; +import type { TlConfig } from '../types/config.types'; + +export const DEFAULT_TIMELINE_CONFIG: TlConfig = { + locale: 'en-US', + dateFormat: { year: 'numeric', month: 'short', day: 'numeric' }, + timeFormat: { hour: '2-digit', minute: '2-digit' }, + relativeTime: true, + animate: true, + timeline: { + layout: 'left', + markerSize: 'md', + connectorStyle: 'solid', + sortDirection: 'desc', + groupByDate: false, + }, + activity: { + pageSize: 20, + showUnreadIndicator: true, + groupByDate: true, + }, + audit: { + pageSize: 25, + searchable: true, + filterable: true, + expandable: true, + showSeverityIcons: true, + }, + gantt: { + zoomLevel: 'week', + rowHeight: 40, + showDependencies: true, + showProgress: true, + showTodayLine: true, + }, +}; + +export const TIMELINE_CONFIG = new InjectionToken('TIMELINE_CONFIG', { + providedIn: 'root', + factory: () => DEFAULT_TIMELINE_CONFIG, +}); + +export function provideTimelineConfig(config: Partial = {}): EnvironmentProviders { + const merged: TlConfig = { + ...DEFAULT_TIMELINE_CONFIG, + ...config, + timeline: { ...DEFAULT_TIMELINE_CONFIG.timeline, ...config.timeline }, + activity: { ...DEFAULT_TIMELINE_CONFIG.activity, ...config.activity }, + audit: { ...DEFAULT_TIMELINE_CONFIG.audit, ...config.audit }, + gantt: { ...DEFAULT_TIMELINE_CONFIG.gantt, ...config.gantt }, + }; + + return makeEnvironmentProviders([ + { provide: TIMELINE_CONFIG, useValue: merged }, + ]); +} diff --git a/src/services/gantt.service.ts b/src/services/gantt.service.ts new file mode 100644 index 0000000..5e94f81 --- /dev/null +++ b/src/services/gantt.service.ts @@ -0,0 +1,68 @@ +import { Injectable, signal, computed } from '@angular/core'; +import type { TlGanttZoomLevel } from '../types/config.types'; +import type { TlGanttTask, TlGanttTaskPosition, TlDateTick } from '../types/gantt.types'; +import { getPixelsPerDay, generateTicks, calculateTaskPosition, calculateTodayOffset } from '../utils/gantt.utils'; +import { toDate } from '../utils/date.utils'; + +@Injectable() +export class GanttService { + private readonly _zoomLevel = signal('week'); + private readonly _scrollOffset = signal(0); + private readonly _selectedTaskId = signal(null); + private readonly _collapsedGroups = signal>(new Set()); + + readonly zoomLevel = this._zoomLevel.asReadonly(); + readonly scrollOffset = this._scrollOffset.asReadonly(); + readonly selectedTaskId = this._selectedTaskId.asReadonly(); + readonly collapsedGroups = this._collapsedGroups.asReadonly(); + + readonly pixelsPerDay = computed(() => getPixelsPerDay(this._zoomLevel())); + + private readonly zoomLevels: TlGanttZoomLevel[] = ['day', 'week', 'month', 'quarter', 'year']; + + zoomIn(): void { + const current = this.zoomLevels.indexOf(this._zoomLevel()); + if (current > 0) { + this._zoomLevel.set(this.zoomLevels[current - 1]); + } + } + + zoomOut(): void { + const current = this.zoomLevels.indexOf(this._zoomLevel()); + if (current < this.zoomLevels.length - 1) { + this._zoomLevel.set(this.zoomLevels[current + 1]); + } + } + + setZoom(level: TlGanttZoomLevel): void { + this._zoomLevel.set(level); + } + + selectTask(taskId: string | null): void { + this._selectedTaskId.set(taskId); + } + + toggleGroupCollapse(groupId: string): void { + this._collapsedGroups.update(groups => { + const newGroups = new Set(groups); + if (newGroups.has(groupId)) { + newGroups.delete(groupId); + } else { + newGroups.add(groupId); + } + return newGroups; + }); + } + + calculateDateAxis(start: Date, end: Date): TlDateTick[] { + return generateTicks(start, end, this._zoomLevel()); + } + + calculateTaskPosition(task: TlGanttTask, chartStart: Date): TlGanttTaskPosition { + return calculateTaskPosition(task, chartStart, this.pixelsPerDay()); + } + + calculateTodayPosition(chartStart: Date): number { + return calculateTodayOffset(chartStart, this.pixelsPerDay()); + } +} diff --git a/src/services/index.ts b/src/services/index.ts new file mode 100644 index 0000000..dd26eda --- /dev/null +++ b/src/services/index.ts @@ -0,0 +1,2 @@ +export * from './timeline.service'; +export * from './gantt.service'; diff --git a/src/services/timeline.service.ts b/src/services/timeline.service.ts new file mode 100644 index 0000000..719e9f9 --- /dev/null +++ b/src/services/timeline.service.ts @@ -0,0 +1,49 @@ +import { Injectable, signal, computed } from '@angular/core'; +import type { TlAuditSeverity } from '../types/audit.types'; + +@Injectable() +export class TimelineService { + private readonly _searchTerm = signal(''); + private readonly _categoryFilter = signal([]); + private readonly _severityFilter = signal([]); + private readonly _dateRangeFilter = signal<{ start?: Date; end?: Date } | null>(null); + + readonly searchTerm = this._searchTerm.asReadonly(); + readonly categoryFilter = this._categoryFilter.asReadonly(); + readonly severityFilter = this._severityFilter.asReadonly(); + readonly dateRangeFilter = this._dateRangeFilter.asReadonly(); + + readonly activeFilterCount = computed(() => { + let count = 0; + if (this._searchTerm().trim()) count++; + if (this._categoryFilter().length) count++; + if (this._severityFilter().length) count++; + if (this._dateRangeFilter()) count++; + return count; + }); + + readonly hasFilters = computed(() => this.activeFilterCount() > 0); + + setSearch(term: string): void { + this._searchTerm.set(term); + } + + setCategoryFilter(categories: string[]): void { + this._categoryFilter.set(categories); + } + + setSeverityFilter(severities: TlAuditSeverity[]): void { + this._severityFilter.set(severities); + } + + setDateRange(range: { start?: Date; end?: Date } | null): void { + this._dateRangeFilter.set(range); + } + + clearAll(): void { + this._searchTerm.set(''); + this._categoryFilter.set([]); + this._severityFilter.set([]); + this._dateRangeFilter.set(null); + } +} diff --git a/src/styles/_index.scss b/src/styles/_index.scss new file mode 100644 index 0000000..8b9dda0 --- /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..e2f00ad --- /dev/null +++ b/src/styles/_mixins.scss @@ -0,0 +1,88 @@ +// ============================================================================= +// Timeline Elements UI - Mixins +// Timeline-specific mixins only. General utilities come from @sda/base-ui. +// ============================================================================= + +// Timeline connector line +@mixin tl-connector($style: solid) { + content: ''; + position: absolute; + width: var(--tl-line-width); + background: var(--tl-line-color); + @if $style == dashed { + background: none; + border-left: var(--tl-line-width) dashed var(--tl-line-color); + } @else if $style == dotted { + background: none; + border-left: var(--tl-line-width) dotted var(--tl-line-color); + } +} + +// Timeline marker +@mixin tl-marker($size: md) { + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background: var(--tl-marker-bg); + color: var(--tl-marker-text); + flex-shrink: 0; + z-index: 1; + + @if $size == sm { + width: var(--tl-marker-size-sm); + height: var(--tl-marker-size-sm); + font-size: var(--font-size-xs, 0.75rem); + } @else if $size == md { + width: var(--tl-marker-size-md); + height: var(--tl-marker-size-md); + font-size: var(--font-size-xs, 0.75rem); + } @else if $size == lg { + width: var(--tl-marker-size-lg); + height: var(--tl-marker-size-lg); + font-size: var(--font-size-sm, 0.875rem); + } +} + +// Scrollable container with styled scrollbar +@mixin tl-scrollable { + overflow: auto; + -webkit-overflow-scrolling: touch; + + &::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + &::-webkit-scrollbar-track { + background: var(--color-bg-secondary, #f9fafb); + border-radius: var(--radius-base, 4px); + } + + &::-webkit-scrollbar-thumb { + background: var(--color-border-primary, #e5e7eb); + border-radius: var(--radius-base, 4px); + + &:hover { + background: var(--color-text-muted, #9ca3af); + } + } +} + +// Status color helper +@mixin tl-status-color($status) { + @if $status == completed { color: var(--tl-status-completed); } + @else if $status == active { color: var(--tl-status-active); } + @else if $status == pending { color: var(--tl-status-pending); } + @else if $status == error { color: var(--tl-status-error); } + @else if $status == warning { color: var(--tl-status-warning); } +} + +// Severity color helper +@mixin tl-severity-color($severity) { + @if $severity == info { color: var(--tl-severity-info); } + @else if $severity == warning { color: var(--tl-severity-warning); } + @else if $severity == error { color: var(--tl-severity-error); } + @else if $severity == critical { color: var(--tl-severity-critical); } + @else if $severity == debug { color: var(--tl-severity-debug); } +} diff --git a/src/styles/_tokens.scss b/src/styles/_tokens.scss new file mode 100644 index 0000000..083b6ec --- /dev/null +++ b/src/styles/_tokens.scss @@ -0,0 +1,57 @@ +// ============================================================================= +// Timeline Elements UI - Design Tokens +// Only timeline-specific tokens. Everything else comes from @sda/base-ui. +// ============================================================================= + +:root { + // Timeline-specific: Connector line + --tl-line-color: var(--color-border-primary, #e5e7eb); + --tl-line-width: 2px; + + // Timeline-specific: Markers + --tl-marker-size-sm: 24px; + --tl-marker-size-md: 32px; + --tl-marker-size-lg: 40px; + --tl-marker-bg: var(--color-primary-500, #3b82f6); + --tl-marker-text: var(--color-text-inverse, #ffffff); + + // Timeline-specific: Status colors + --tl-status-completed: var(--color-status-success, #10b981); + --tl-status-active: var(--color-primary-500, #3b82f6); + --tl-status-pending: var(--color-text-muted, #9ca3af); + --tl-status-error: var(--color-status-error, #ef4444); + --tl-status-warning: var(--color-status-warning, #f59e0b); + + // Timeline-specific: Severity colors + --tl-severity-info: var(--color-status-info, #3b82f6); + --tl-severity-warning: var(--color-status-warning, #f59e0b); + --tl-severity-error: var(--color-status-error, #ef4444); + --tl-severity-critical: #dc2626; + --tl-severity-debug: #8b5cf6; + + // Timeline-specific: Changelog + --tl-change-added: var(--color-status-success, #10b981); + --tl-change-changed: var(--color-primary-500, #3b82f6); + --tl-change-fixed: #8b5cf6; + --tl-change-removed: var(--color-status-error, #ef4444); + --tl-change-deprecated: var(--color-status-warning, #f59e0b); + --tl-change-security: #dc2626; + + // Timeline-specific: Gantt + --tl-gantt-bar-height: 24px; + --tl-gantt-bar-radius: var(--radius-base, 4px); + --tl-gantt-row-height: 40px; + --tl-gantt-row-border: var(--color-border-primary, #e5e7eb); + --tl-gantt-row-bg: var(--color-bg-primary, #ffffff); + --tl-gantt-row-bg-alt: var(--color-bg-secondary, #f9fafb); + --tl-gantt-weekend-bg: rgba(0, 0, 0, 0.02); + --tl-gantt-today-line: var(--color-status-error, #ef4444); + --tl-gantt-sidebar-width: 240px; + --tl-gantt-axis-height: 48px; + --tl-gantt-axis-bg: var(--color-bg-secondary, #f9fafb); + --tl-gantt-axis-border: var(--color-border-primary, #e5e7eb); + + // Timeline-specific: Activity + --tl-activity-unread-dot: var(--color-primary-500, #3b82f6); + --tl-activity-avatar-size: 36px; +} diff --git a/src/types/activity.types.ts b/src/types/activity.types.ts new file mode 100644 index 0000000..40d0edf --- /dev/null +++ b/src/types/activity.types.ts @@ -0,0 +1,30 @@ +export interface TlActivityItem { + id: string; + actor: TlActor; + action: string; + target?: string; + description?: string; + date: Date | string; + icon?: string; + type?: TlActivityType; + unread?: boolean; + metadata?: Record; +} + +export interface TlActor { + id: string; + name: string; + avatar?: string; + initials?: string; +} + +export type TlActivityType = + | 'create' + | 'update' + | 'delete' + | 'comment' + | 'assign' + | 'status' + | 'upload' + | 'share' + | 'system'; diff --git a/src/types/audit.types.ts b/src/types/audit.types.ts new file mode 100644 index 0000000..fa2d038 --- /dev/null +++ b/src/types/audit.types.ts @@ -0,0 +1,21 @@ +export interface TlAuditEntry { + id: string; + action: string; + category: string; + user: string; + date: Date | string; + severity: TlAuditSeverity; + description?: string; + resource?: string; + ipAddress?: string; + changes?: TlAuditChange[]; + metadata?: Record; +} + +export interface TlAuditChange { + field: string; + oldValue: string | null; + newValue: string | null; +} + +export type TlAuditSeverity = 'info' | 'warning' | 'error' | 'critical' | 'debug'; diff --git a/src/types/changelog.types.ts b/src/types/changelog.types.ts new file mode 100644 index 0000000..b407d3b --- /dev/null +++ b/src/types/changelog.types.ts @@ -0,0 +1,22 @@ +export interface TlChangelogVersion { + version: string; + date: Date | string; + title?: string; + description?: string; + entries: TlChangelogEntry[]; +} + +export interface TlChangelogEntry { + description: string; + category: TlChangeCategory; + breaking?: boolean; + reference?: string; +} + +export type TlChangeCategory = + | 'added' + | 'changed' + | 'fixed' + | 'removed' + | 'deprecated' + | 'security'; diff --git a/src/types/config.types.ts b/src/types/config.types.ts new file mode 100644 index 0000000..0a76236 --- /dev/null +++ b/src/types/config.types.ts @@ -0,0 +1,39 @@ +export interface TlConfig { + locale: string; + dateFormat: Intl.DateTimeFormatOptions; + timeFormat: Intl.DateTimeFormatOptions; + relativeTime: boolean; + animate: boolean; + timeline: { + layout: TlTimelineLayout; + markerSize: TlMarkerSize; + connectorStyle: TlConnectorStyle; + sortDirection: TlSortDirection; + groupByDate: boolean; + }; + activity: { + pageSize: number; + showUnreadIndicator: boolean; + groupByDate: boolean; + }; + audit: { + pageSize: number; + searchable: boolean; + filterable: boolean; + expandable: boolean; + showSeverityIcons: boolean; + }; + gantt: { + zoomLevel: TlGanttZoomLevel; + rowHeight: number; + showDependencies: boolean; + showProgress: boolean; + showTodayLine: boolean; + }; +} + +export type TlTimelineLayout = 'alternating' | 'left' | 'right'; +export type TlMarkerSize = 'sm' | 'md' | 'lg'; +export type TlConnectorStyle = 'solid' | 'dashed' | 'dotted'; +export type TlSortDirection = 'asc' | 'desc'; +export type TlGanttZoomLevel = 'day' | 'week' | 'month' | 'quarter' | 'year'; diff --git a/src/types/event.types.ts b/src/types/event.types.ts new file mode 100644 index 0000000..fe5bc29 --- /dev/null +++ b/src/types/event.types.ts @@ -0,0 +1,86 @@ +import type { TlTimelineItem } from './timeline.types'; +import type { TlActivityItem, TlActor } from './activity.types'; +import type { TlAuditEntry } from './audit.types'; +import type { TlChangelogVersion, TlChangelogEntry } from './changelog.types'; +import type { TlGanttTask } from './gantt.types'; + +export interface TlTimelineItemClickEvent { + item: TlTimelineItem; + index: number; +} + +export interface TlTimelineItemHoverEvent { + item: TlTimelineItem; + index: number; +} + +export interface TlActivityClickEvent { + item: TlActivityItem; +} + +export interface TlActorClickEvent { + actor: TlActor; +} + +export interface TlActivityLoadMoreEvent { + currentCount: number; + pageSize: number; +} + +export interface TlAuditEntryClickEvent { + entry: TlAuditEntry; +} + +export interface TlAuditFilterChangeEvent { + searchTerm: string; + categories: string[]; + severities: string[]; + dateRange: { start?: Date; end?: Date } | null; +} + +export interface TlAuditPageChangeEvent { + page: number; + pageSize: number; +} + +export interface TlVersionClickEvent { + version: TlChangelogVersion; +} + +export interface TlChangelogEntryClickEvent { + entry: TlChangelogEntry; + version: TlChangelogVersion; +} + +export interface TlGanttTaskClickEvent { + task: TlGanttTask; +} + +export interface TlGanttTaskResizeEvent { + task: TlGanttTask; + newStartDate?: Date; + newEndDate?: Date; +} + +export interface TlGanttTaskMoveEvent { + task: TlGanttTask; + newStartDate: Date; + newEndDate: Date; +} + +export interface TlGanttZoomChangeEvent { + level: string; +} + +export interface TlGanttScrollEvent { + scrollLeft: number; + scrollTop: number; +} + +export interface TlSearchChangeEvent { + term: string; +} + +export interface TlSortToggleEvent { + direction: 'asc' | 'desc'; +} diff --git a/src/types/gantt.types.ts b/src/types/gantt.types.ts new file mode 100644 index 0000000..e7dd442 --- /dev/null +++ b/src/types/gantt.types.ts @@ -0,0 +1,35 @@ +export interface TlGanttTask { + id: string; + name: string; + startDate: Date | string; + endDate: Date | string; + progress?: number; + status?: TlGanttTaskStatus; + color?: string; + group?: string; + parentId?: string; + assignee?: string; + metadata?: Record; +} + +export interface TlGanttDependency { + fromTaskId: string; + toTaskId: string; + type?: TlDependencyType; +} + +export type TlGanttTaskStatus = 'not-started' | 'in-progress' | 'completed' | 'delayed' | 'on-hold'; +export type TlDependencyType = 'finish-to-start' | 'start-to-start' | 'finish-to-finish' | 'start-to-finish'; + +export interface TlGanttTaskPosition { + left: number; + width: number; +} + +export interface TlDateTick { + date: Date; + label: string; + position: number; + isWeekend: boolean; + isMajor: boolean; +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..27a94e3 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,7 @@ +export * from './config.types'; +export * from './timeline.types'; +export * from './activity.types'; +export * from './audit.types'; +export * from './changelog.types'; +export * from './gantt.types'; +export * from './event.types'; diff --git a/src/types/timeline.types.ts b/src/types/timeline.types.ts new file mode 100644 index 0000000..c25b673 --- /dev/null +++ b/src/types/timeline.types.ts @@ -0,0 +1,19 @@ +export interface TlTimelineItem { + id: string; + title: string; + description?: string; + date: Date | string; + icon?: string; + iconColor?: string; + status?: TlTimelineStatus; + category?: string; + tags?: string[]; + metadata?: Record; +} + +export type TlTimelineStatus = 'completed' | 'active' | 'pending' | 'error' | 'warning'; + +export interface TlTimelineGroup { + label: string; + items: TlTimelineItem[]; +} diff --git a/src/utils/date.utils.ts b/src/utils/date.utils.ts new file mode 100644 index 0000000..b8c3fe0 --- /dev/null +++ b/src/utils/date.utils.ts @@ -0,0 +1,97 @@ +import type { TlTimelineGroup } from '../types/timeline.types'; + +export function toDate(value: Date | string): Date { + return value instanceof Date ? value : new Date(value); +} + +export function formatDate(value: Date | string, locale = 'en-US', options?: Intl.DateTimeFormatOptions): string { + const date = toDate(value); + return date.toLocaleDateString(locale, options ?? { year: 'numeric', month: 'short', day: 'numeric' }); +} + +export function formatTime(value: Date | string, locale = 'en-US', options?: Intl.DateTimeFormatOptions): string { + const date = toDate(value); + return date.toLocaleTimeString(locale, options ?? { hour: '2-digit', minute: '2-digit' }); +} + +export function formatRelativeTime(value: Date | string): string { + const date = toDate(value); + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffSecs = Math.floor(diffMs / 1000); + const diffMins = Math.floor(diffSecs / 60); + const diffHours = Math.floor(diffMins / 60); + const diffDays = Math.floor(diffHours / 24); + + if (diffSecs < 60) return 'just now'; + if (diffMins < 60) return `${diffMins}m ago`; + if (diffHours < 24) return `${diffHours}h ago`; + if (diffDays < 7) return `${diffDays}d ago`; + return formatDate(value); +} + +export function formatSmartTime(value: Date | string, locale = 'en-US'): string { + const date = toDate(value); + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffHours = diffMs / (1000 * 60 * 60); + + if (diffHours < 24) return formatRelativeTime(value); + if (diffHours < 48) return 'Yesterday ' + formatTime(value, locale); + return formatDate(value, locale) + ' ' + formatTime(value, locale); +} + +export function getDateLabel(value: Date | string): string { + const date = toDate(value); + const now = new Date(); + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + const target = new Date(date.getFullYear(), date.getMonth(), date.getDate()); + const diffDays = Math.floor((today.getTime() - target.getTime()) / (1000 * 60 * 60 * 24)); + + if (diffDays === 0) return 'Today'; + if (diffDays === 1) return 'Yesterday'; + if (diffDays < 7) return `${diffDays} days ago`; + return formatDate(value); +} + +export function groupByDate(items: T[]): { label: string; items: T[] }[] { + const groups = new Map(); + + for (const item of items) { + const label = getDateLabel(item.date); + const group = groups.get(label); + if (group) { + group.push(item); + } else { + groups.set(label, [item]); + } + } + + return Array.from(groups.entries()).map(([label, items]) => ({ label, items })); +} + +export function diffInDays(start: Date | string, end: Date | string): number { + const s = toDate(start); + const e = toDate(end); + return Math.ceil((e.getTime() - s.getTime()) / (1000 * 60 * 60 * 24)); +} + +export function isWeekend(date: Date | string): boolean { + const d = toDate(date); + const day = d.getDay(); + return day === 0 || day === 6; +} + +export function isToday(date: Date | string): boolean { + const d = toDate(date); + const now = new Date(); + return d.getFullYear() === now.getFullYear() && + d.getMonth() === now.getMonth() && + d.getDate() === now.getDate(); +} + +export function addDays(date: Date | string, days: number): Date { + const d = new Date(toDate(date)); + d.setDate(d.getDate() + days); + return d; +} diff --git a/src/utils/filter.utils.ts b/src/utils/filter.utils.ts new file mode 100644 index 0000000..3ed3da9 --- /dev/null +++ b/src/utils/filter.utils.ts @@ -0,0 +1,77 @@ +import type { TlAuditEntry, TlAuditSeverity } from '../types/audit.types'; +import { toDate } from './date.utils'; + +export function searchItems(items: T[], term: string, fields: (keyof T)[]): T[] { + if (!term.trim()) return items; + const lower = term.toLowerCase(); + + return items.filter(item => + fields.some(field => { + const value = item[field]; + if (typeof value === 'string') { + return value.toLowerCase().includes(lower); + } + return false; + }) + ); +} + +export function filterAuditEntries( + entries: TlAuditEntry[], + options: { + searchTerm?: string; + categories?: string[]; + severities?: TlAuditSeverity[]; + dateRange?: { start?: Date; end?: Date } | null; + } +): TlAuditEntry[] { + let result = entries; + + if (options.searchTerm?.trim()) { + result = searchItems(result, options.searchTerm, ['action', 'user', 'description', 'resource', 'category']); + } + + if (options.categories?.length) { + result = result.filter(e => options.categories!.includes(e.category)); + } + + if (options.severities?.length) { + result = result.filter(e => options.severities!.includes(e.severity)); + } + + if (options.dateRange) { + result = filterByDateRange(result, 'date', options.dateRange.start, options.dateRange.end); + } + + return result; +} + +export function filterByField(items: T[], field: keyof T, values: unknown[]): T[] { + if (!values.length) return items; + return items.filter(item => values.includes(item[field])); +} + +export function filterByDateRange>( + items: T[], + dateField: keyof T, + start?: Date, + end?: Date +): T[] { + return items.filter(item => { + const date = toDate(item[dateField] as Date | string); + if (start && date < start) return false; + if (end && date > end) return false; + return true; + }); +} + +export function extractUniqueValues(items: T[], field: keyof T): string[] { + const values = new Set(); + for (const item of items) { + const value = item[field]; + if (typeof value === 'string') { + values.add(value); + } + } + return Array.from(values).sort(); +} diff --git a/src/utils/gantt.utils.ts b/src/utils/gantt.utils.ts new file mode 100644 index 0000000..900f94b --- /dev/null +++ b/src/utils/gantt.utils.ts @@ -0,0 +1,131 @@ +import type { TlGanttTask, TlGanttDependency, TlGanttTaskPosition, TlDateTick } from '../types/gantt.types'; +import type { TlGanttZoomLevel } from '../types/config.types'; +import { toDate, diffInDays, isWeekend, addDays } from './date.utils'; + +export function calculateDateRange(tasks: TlGanttTask[]): { start: Date; end: Date } { + if (tasks.length === 0) { + const now = new Date(); + return { start: now, end: addDays(now, 30) }; + } + + let minDate = toDate(tasks[0].startDate); + let maxDate = toDate(tasks[0].endDate); + + for (const task of tasks) { + const start = toDate(task.startDate); + const end = toDate(task.endDate); + if (start < minDate) minDate = start; + if (end > maxDate) maxDate = end; + } + + return { + start: addDays(minDate, -2), + end: addDays(maxDate, 2), + }; +} + +export function getPixelsPerDay(zoomLevel: TlGanttZoomLevel): number { + switch (zoomLevel) { + case 'day': return 60; + case 'week': return 20; + case 'month': return 6; + case 'quarter': return 2; + case 'year': return 1; + } +} + +export function generateTicks(start: Date, end: Date, zoomLevel: TlGanttZoomLevel): TlDateTick[] { + const ticks: TlDateTick[] = []; + const ppd = getPixelsPerDay(zoomLevel); + const current = new Date(start); + let position = 0; + + while (current <= end) { + const isMajor = zoomLevel === 'day' + ? current.getDay() === 1 + : zoomLevel === 'week' + ? current.getDate() === 1 + : current.getMonth() % 3 === 0 && current.getDate() === 1; + + const label = zoomLevel === 'day' || zoomLevel === 'week' + ? current.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) + : current.toLocaleDateString('en-US', { month: 'short', year: '2-digit' }); + + ticks.push({ + date: new Date(current), + label, + position, + isWeekend: isWeekend(current), + isMajor, + }); + + current.setDate(current.getDate() + 1); + position += ppd; + } + + return ticks; +} + +export function calculateTaskPosition( + task: TlGanttTask, + chartStart: Date, + pixelsPerDay: number +): TlGanttTaskPosition { + const start = toDate(task.startDate); + const end = toDate(task.endDate); + const left = diffInDays(chartStart, start) * pixelsPerDay; + const width = Math.max(diffInDays(start, end) * pixelsPerDay, pixelsPerDay); + return { left, width }; +} + +export function calculateChartWidth(start: Date, end: Date, pixelsPerDay: number): number { + return diffInDays(start, end) * pixelsPerDay; +} + +export function calculateDependencyPath( + from: TlGanttTaskPosition, + fromRowIndex: number, + to: TlGanttTaskPosition, + toRowIndex: number, + rowHeight: number +): string { + const startX = from.left + from.width; + const startY = fromRowIndex * rowHeight + rowHeight / 2; + const endX = to.left; + const endY = toRowIndex * rowHeight + rowHeight / 2; + const midX = startX + (endX - startX) / 2; + + return `M ${startX} ${startY} C ${midX} ${startY}, ${midX} ${endY}, ${endX} ${endY}`; +} + +export function sortTasksHierarchically(tasks: TlGanttTask[]): TlGanttTask[] { + const roots = tasks.filter(t => !t.parentId); + const childMap = new Map(); + + for (const task of tasks) { + if (task.parentId) { + const children = childMap.get(task.parentId) || []; + children.push(task); + childMap.set(task.parentId, children); + } + } + + const result: TlGanttTask[] = []; + const addWithChildren = (task: TlGanttTask): void => { + result.push(task); + const children = childMap.get(task.id) || []; + for (const child of children) { + addWithChildren(child); + } + }; + + for (const root of roots) { + addWithChildren(root); + } + + return result; +} + +export function calculateTodayOffset(chartStart: Date, pixelsPerDay: number): number { + return diffInDays(chartStart, new Date()) * pixelsPerDay; +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..c135f12 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from './date.utils'; +export * from './gantt.utils'; +export * from './filter.utils'; 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 + } +} diff --git a/tsconfig.lib.json b/tsconfig.lib.json new file mode 100644 index 0000000..c97d65e --- /dev/null +++ b/tsconfig.lib.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./dist/out-tsc", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [] + }, + "exclude": [ + "**/*.spec.ts", + "**/*.test.ts", + "**/*.stories.ts" + ], + "angularCompilerOptions": { + "compilationMode": "partial" + } +}