{"_id":"@alittlemore.dev/design-system","_rev":"4-c33d5e0e7bc83625f208466b309695a2","name":"@alittlemore.dev/design-system","dist-tags":{"latest":"0.3.1"},"versions":{"0.2.0":{"name":"@alittlemore.dev/design-system","version":"0.2.0","author":{"name":"Dmitriy Lunev"},"license":"MIT","_id":"@alittlemore.dev/design-system@0.2.0","maintainers":[{"name":"alittlemoron","email":"dima.lunev14@gmail.com"}],"homepage":"https://github.com/alittlemore-dev/design-system#readme","bugs":{"url":"https://github.com/alittlemore-dev/design-system/issues"},"dist":{"shasum":"b78ade062b5bec2ccf93216daa693c157d449856","tarball":"https://registry.npmjs.org/@alittlemore.dev/design-system/-/design-system-0.2.0.tgz","fileCount":21,"integrity":"sha512-U/r2ve9tR7Xjoj6uXOGwlI2etrXTnkbUkorFJ+OPe7XwwyxMuVwg8UVrYhFgQN6+OYq2EdVlWdmYCU1HWjS4KA==","signatures":[{"sig":"MEYCIQDcOC0c207nNMcTcJolx0erX5crF2MBIIADTU7TAXulzQIhAPbQsvlBOhKsurOQ+j7D8cOeL1mvv6J8c1Gkv8Y8QTwB","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1319817},"type":"module","_from":"file:dist/releases/alittlemore.dev-design-system-0.2.0.tgz","module":"fesm2022/alittlemore.dev-design-system.mjs","exports":{".":{"types":"./types/alittlemore.dev-design-system.d.ts","default":"./fesm2022/alittlemore.dev-design-system.mjs"},"./testing":{"types":"./types/alittlemore.dev-design-system-testing.d.ts","default":"./fesm2022/alittlemore.dev-design-system-testing.mjs"},"./markdown":{"types":"./types/alittlemore.dev-design-system-markdown.d.ts","default":"./fesm2022/alittlemore.dev-design-system-markdown.mjs"},"./styles/ui":{"sass":"./styles/ui.scss"},"./package.json":{"default":"./package.json"},"./theme-preload":{"default":"./theme-preload.js"},"./markdown-editor":{"types":"./types/alittlemore.dev-design-system-markdown-editor.d.ts","default":"./fesm2022/alittlemore.dev-design-system-markdown-editor.mjs"},"./styles/markdown":{"sass":"./styles/markdown.scss"},"./styles/cdk-overlay":{"sass":"./styles/cdk-overlay.scss"},"./styles/theme-tokens":{"sass":"./styles/theme-tokens.scss"},"./styles/bootstrap-overrides":{"sass":"./styles/bootstrap-overrides.scss"}},"typings":"types/alittlemore.dev-design-system.d.ts","_npmUser":{"name":"alittlemoron","email":"dima.lunev14@gmail.com"},"_resolved":"/home/runner/work/design-system/design-system/dist/releases/alittlemore.dev-design-system-0.2.0.tgz","_integrity":"sha512-U/r2ve9tR7Xjoj6uXOGwlI2etrXTnkbUkorFJ+OPe7XwwyxMuVwg8UVrYhFgQN6+OYq2EdVlWdmYCU1HWjS4KA==","repository":{"url":"git+https://github.com/alittlemore-dev/design-system.git","type":"git"},"_npmVersion":"11.13.0","description":"Application-independent Angular UI and Markdown building blocks.","directories":{},"sideEffects":["./theme-preload.js"],"_nodeVersion":"24.16.0","dependencies":{"tslib":"^2.3.0","marked":"^18.0.9","prismjs":"^1.30.0","dompurify":"^3.4.13","@lezer/common":"^1.5.2","@codemirror/view":"^6.43.8","@lezer/highlight":"^1.2.3","@codemirror/state":"^6.7.1","@codemirror/search":"^6.7.1","@codemirror/commands":"^6.10.4","@codemirror/language":"^6.12.4","@codemirror/autocomplete":"^6.20.3","@codemirror/lang-markdown":"^6.5.2"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"peerDependencies":{"rxjs":">=7.8.2 <8.0.0","bootstrap":">=5.3.8 <6.0.0","@angular/cdk":">=22.1.0 <23.0.0","@angular/core":">=22.1.0 <23.0.0","@angular/forms":">=22.1.0 <23.0.0","@angular/common":">=22.1.0 <23.0.0"},"_npmOperationalInternal":{"tmp":"tmp/design-system_0.2.0_1788519387631_0.8143153787250534","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@alittlemore.dev/design-system","version":"0.2.1","author":{"name":"Dmitriy Lunev"},"license":"MIT","_id":"@alittlemore.dev/design-system@0.2.1","maintainers":[{"name":"alittlemoron","email":"dima.lunev14@gmail.com"}],"homepage":"https://github.com/alittlemore-dev/design-system#readme","bugs":{"url":"https://github.com/alittlemore-dev/design-system/issues"},"dist":{"shasum":"69ebe50f3b250d2d8a31320315822ff6ab84b968","tarball":"https://registry.npmjs.org/@alittlemore.dev/design-system/-/design-system-0.2.1.tgz","fileCount":21,"integrity":"sha512-ke3cvq4HncEaemxKjiCSV9knofcgaO60wYvhgzRmYTyVAjL59pBYnZ/DEapSAu19aAuHyYH0bQB6Zkxt+24ZwA==","signatures":[{"sig":"MEUCIQDS0RaSUJERCwZe+eQIPLC2oYuqi/vp/PNpzQfU+TICygIgUreRSpcL9MG5J2dA3Skj54O4mVBba07XuZKelHYtrrg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@alittlemore.dev%2fdesign-system@0.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1319817},"type":"module","_from":"file:dist/releases/alittlemore.dev-design-system-0.2.1.tgz","module":"fesm2022/alittlemore.dev-design-system.mjs","exports":{".":{"types":"./types/alittlemore.dev-design-system.d.ts","default":"./fesm2022/alittlemore.dev-design-system.mjs"},"./testing":{"types":"./types/alittlemore.dev-design-system-testing.d.ts","default":"./fesm2022/alittlemore.dev-design-system-testing.mjs"},"./markdown":{"types":"./types/alittlemore.dev-design-system-markdown.d.ts","default":"./fesm2022/alittlemore.dev-design-system-markdown.mjs"},"./styles/ui":{"sass":"./styles/ui.scss"},"./package.json":{"default":"./package.json"},"./theme-preload":{"default":"./theme-preload.js"},"./markdown-editor":{"types":"./types/alittlemore.dev-design-system-markdown-editor.d.ts","default":"./fesm2022/alittlemore.dev-design-system-markdown-editor.mjs"},"./styles/markdown":{"sass":"./styles/markdown.scss"},"./styles/cdk-overlay":{"sass":"./styles/cdk-overlay.scss"},"./styles/theme-tokens":{"sass":"./styles/theme-tokens.scss"},"./styles/bootstrap-overrides":{"sass":"./styles/bootstrap-overrides.scss"}},"typings":"types/alittlemore.dev-design-system.d.ts","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:a3eb9212-0cc7-49d3-8969-8a9e372c0f26"}},"_resolved":"/home/runner/work/design-system/design-system/dist/releases/alittlemore.dev-design-system-0.2.1.tgz","_integrity":"sha512-ke3cvq4HncEaemxKjiCSV9knofcgaO60wYvhgzRmYTyVAjL59pBYnZ/DEapSAu19aAuHyYH0bQB6Zkxt+24ZwA==","repository":{"url":"git+https://github.com/alittlemore-dev/design-system.git","type":"git"},"_npmVersion":"11.13.0","description":"Application-independent Angular UI and Markdown building blocks.","directories":{},"sideEffects":["./theme-preload.js"],"_nodeVersion":"24.16.0","dependencies":{"tslib":"^2.3.0","marked":"^18.0.9","prismjs":"^1.30.0","dompurify":"^3.4.13","@lezer/common":"^1.5.2","@codemirror/view":"^6.43.8","@lezer/highlight":"^1.2.3","@codemirror/state":"^6.7.1","@codemirror/search":"^6.7.1","@codemirror/commands":"^6.10.4","@codemirror/language":"^6.12.4","@codemirror/autocomplete":"^6.20.3","@codemirror/lang-markdown":"^6.5.2"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"peerDependencies":{"rxjs":">=7.8.2 <8.0.0","bootstrap":">=5.3.8 <6.0.0","@angular/cdk":">=22.1.0 <23.0.0","@angular/core":">=22.1.0 <23.0.0","@angular/forms":">=22.1.0 <23.0.0","@angular/common":">=22.1.0 <23.0.0"},"_npmOperationalInternal":{"tmp":"tmp/design-system_0.2.1_1788522154192_0.6682947815689533","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@alittlemore.dev/design-system","version":"0.3.0","author":{"name":"Dmitriy Lunev"},"license":"MIT","_id":"@alittlemore.dev/design-system@0.3.0","maintainers":[{"name":"alittlemoron","email":"dima.lunev14@gmail.com"}],"homepage":"https://github.com/alittlemore-dev/design-system#readme","bugs":{"url":"https://github.com/alittlemore-dev/design-system/issues"},"dist":{"shasum":"e060ef3113f9be02bbd59697c95ba7c88e939c9a","tarball":"https://registry.npmjs.org/@alittlemore.dev/design-system/-/design-system-0.3.0.tgz","fileCount":21,"integrity":"sha512-xS3cH/Ot+vP1g/gkzvVD68nUIEqYv+srzoGgI2umMTxozoGdNfMaVmpDOGTCSGTbqE9eRUMRLZ32A/kpYh2ZdQ==","signatures":[{"sig":"MEUCIGu8HbGsub1gTABkKojy+aQ4goIRsq7jIeV3fSEujnxiAiEAx/NHF/Ua3DB+4VI8Ki9XWWQojYvIRbcYyYF0YynSbLs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@alittlemore.dev%2fdesign-system@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1930964},"type":"module","_from":"file:dist/releases/alittlemore.dev-design-system-0.3.0.tgz","module":"fesm2022/alittlemore.dev-design-system.mjs","exports":{".":{"types":"./types/alittlemore.dev-design-system.d.ts","default":"./fesm2022/alittlemore.dev-design-system.mjs"},"./testing":{"types":"./types/alittlemore.dev-design-system-testing.d.ts","default":"./fesm2022/alittlemore.dev-design-system-testing.mjs"},"./markdown":{"types":"./types/alittlemore.dev-design-system-markdown.d.ts","default":"./fesm2022/alittlemore.dev-design-system-markdown.mjs"},"./styles/ui":{"sass":"./styles/ui.scss"},"./package.json":{"default":"./package.json"},"./theme-preload":{"default":"./theme-preload.js"},"./markdown-editor":{"types":"./types/alittlemore.dev-design-system-markdown-editor.d.ts","default":"./fesm2022/alittlemore.dev-design-system-markdown-editor.mjs"},"./styles/markdown":{"sass":"./styles/markdown.scss"},"./styles/cdk-overlay":{"sass":"./styles/cdk-overlay.scss"},"./styles/theme-tokens":{"sass":"./styles/theme-tokens.scss"},"./styles/bootstrap-overrides":{"sass":"./styles/bootstrap-overrides.scss"}},"typings":"types/alittlemore.dev-design-system.d.ts","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:a3eb9212-0cc7-49d3-8969-8a9e372c0f26"}},"_resolved":"/home/runner/work/design-system/design-system/dist/releases/alittlemore.dev-design-system-0.3.0.tgz","_integrity":"sha512-xS3cH/Ot+vP1g/gkzvVD68nUIEqYv+srzoGgI2umMTxozoGdNfMaVmpDOGTCSGTbqE9eRUMRLZ32A/kpYh2ZdQ==","repository":{"url":"git+https://github.com/alittlemore-dev/design-system.git","type":"git"},"_npmVersion":"11.13.0","description":"Application-independent Angular UI and Markdown building blocks.","directories":{},"sideEffects":["./theme-preload.js"],"_nodeVersion":"24.16.0","dependencies":{"tslib":"^2.3.0","marked":"^18.0.9","prismjs":"^1.30.0","dompurify":"^3.4.13","@lezer/common":"^1.5.2","@codemirror/view":"^6.43.8","@lezer/highlight":"^1.2.3","@codemirror/state":"^6.7.1","@codemirror/search":"^6.7.1","@codemirror/commands":"^6.10.4","@codemirror/language":"^6.12.4","@codemirror/autocomplete":"^6.20.3","@codemirror/lang-markdown":"^6.5.2"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"peerDependencies":{"rxjs":">=7.8.2 <8.0.0","bootstrap":">=5.3.8 <6.0.0","@angular/cdk":">=22.1.0 <23.0.0","@angular/core":">=22.1.0 <23.0.0","@angular/forms":">=22.1.0 <23.0.0","@angular/common":">=22.1.0 <23.0.0"},"_npmOperationalInternal":{"tmp":"tmp/design-system_0.3.0_1789248069239_0.08789485665284857","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@alittlemore.dev/design-system","version":"0.3.1","description":"Application-independent Angular UI and Markdown building blocks.","author":{"name":"Dmitriy Lunev"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/alittlemore-dev/design-system.git"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"exports":{"./theme-preload":{"default":"./theme-preload.js"},"./styles/theme-tokens":{"sass":"./styles/theme-tokens.scss"},"./styles/bootstrap-overrides":{"sass":"./styles/bootstrap-overrides.scss"},"./styles/cdk-overlay":{"sass":"./styles/cdk-overlay.scss"},"./styles/ui":{"sass":"./styles/ui.scss"},"./styles/markdown":{"sass":"./styles/markdown.scss"},"./package.json":{"default":"./package.json"},".":{"types":"./types/alittlemore.dev-design-system.d.ts","default":"./fesm2022/alittlemore.dev-design-system.mjs"},"./markdown":{"types":"./types/alittlemore.dev-design-system-markdown.d.ts","default":"./fesm2022/alittlemore.dev-design-system-markdown.mjs"},"./markdown-editor":{"types":"./types/alittlemore.dev-design-system-markdown-editor.d.ts","default":"./fesm2022/alittlemore.dev-design-system-markdown-editor.mjs"},"./testing":{"types":"./types/alittlemore.dev-design-system-testing.d.ts","default":"./fesm2022/alittlemore.dev-design-system-testing.mjs"}},"peerDependencies":{"@angular/cdk":">=22.1.0 <23.0.0","@angular/common":">=22.1.0 <23.0.0","@angular/core":">=22.1.0 <23.0.0","@angular/forms":">=22.1.0 <23.0.0","bootstrap":">=5.3.8 <6.0.0","rxjs":">=7.8.2 <8.0.0"},"dependencies":{"@codemirror/autocomplete":"^6.20.3","@codemirror/commands":"^6.10.4","@codemirror/lang-markdown":"^6.5.2","@codemirror/language":"^6.12.4","@codemirror/search":"^6.7.1","@codemirror/state":"^6.7.1","@codemirror/view":"^6.43.8","@lezer/common":"^1.5.2","@lezer/highlight":"^1.2.3","dompurify":"^3.4.13","marked":"^18.0.9","prismjs":"^1.30.0","tslib":"^2.3.0"},"sideEffects":["./theme-preload.js"],"module":"fesm2022/alittlemore.dev-design-system.mjs","typings":"types/alittlemore.dev-design-system.d.ts","type":"module","_id":"@alittlemore.dev/design-system@0.3.1","bugs":{"url":"https://github.com/alittlemore-dev/design-system/issues"},"homepage":"https://github.com/alittlemore-dev/design-system#readme","_integrity":"sha512-bJ4qHbbbGaOKGoMmccKyhwdxJ/3NQ38dkaXQtdcOB45Qe1EeMsYP6/ubhpn0t8bqjZTEyTbvw91OqDt4SzkEeQ==","_resolved":"/home/runner/work/design-system/design-system/dist/releases/alittlemore.dev-design-system-0.3.1.tgz","_from":"file:dist/releases/alittlemore.dev-design-system-0.3.1.tgz","_nodeVersion":"24.16.0","_npmVersion":"11.13.0","dist":{"integrity":"sha512-bJ4qHbbbGaOKGoMmccKyhwdxJ/3NQ38dkaXQtdcOB45Qe1EeMsYP6/ubhpn0t8bqjZTEyTbvw91OqDt4SzkEeQ==","shasum":"ba5bc7ea75885ae7fd69c84e76b956105551308e","tarball":"https://registry.npmjs.org/@alittlemore.dev/design-system/-/design-system-0.3.1.tgz","fileCount":21,"unpackedSize":1996682,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@alittlemore.dev%2fdesign-system@0.3.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIHs5hSvYpuYr8QyVgITLW0q7eoTXQcAHpDp6v5KBPIOzAiEAqy8Ro4CQY929TCq7vJyYVBiSN0RrHX7NEpGQNhLR38o="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:a3eb9212-0cc7-49d3-8969-8a9e372c0f26"}},"directories":{},"maintainers":[{"name":"alittlemoron","email":"dima.lunev14@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/design-system_0.3.1_1789337274446_0.43084128654363063"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-04T10:56:27.465Z","modified":"2026-09-13T22:07:54.972Z","0.2.0":"2026-09-04T10:56:27.770Z","0.2.1":"2026-09-04T11:42:34.347Z","0.3.0":"2026-09-12T21:21:09.366Z","0.3.1":"2026-09-13T22:07:54.612Z"},"bugs":{"url":"https://github.com/alittlemore-dev/design-system/issues"},"author":{"name":"Dmitriy Lunev"},"license":"MIT","homepage":"https://github.com/alittlemore-dev/design-system#readme","repository":{"type":"git","url":"git+https://github.com/alittlemore-dev/design-system.git"},"description":"Application-independent Angular UI and Markdown building blocks.","maintainers":[{"name":"alittlemoron","email":"dima.lunev14@gmail.com"}],"readme":"# @alittlemore.dev/design-system\n\nApplication-independent Angular UI and Markdown building blocks.\n\nThe package exposes these TypeScript APIs:\n\n- `@alittlemore.dev/design-system` for application-independent UI;\n- `@alittlemore.dev/design-system/markdown` for Markdown rendering;\n- `@alittlemore.dev/design-system/markdown-editor` for the interactive Markdown editor;\n- `@alittlemore.dev/design-system/testing` for public test utilities.\n\n## Independent UI components\n\nImport UI components only from the primary entry point:\n\n```ts\nimport {\n  EmptyStateComponent,\n  ErrorMessageComponent,\n  FoldableTreeComponent,\n  LoadingSpinnerComponent,\n  LocalizedDatePickerComponent,\n  LocalizedDateRangePickerComponent,\n  LocalizedDateTimePickerComponent,\n  LocalizedDateTimeRangePickerComponent,\n  LocalizedTimePickerComponent,\n  LocalizedTimeRangePickerComponent,\n  NotificationAreaComponent,\n  NotificationService,\n  SiteSelectComponent,\n  type LocalizedDateRange,\n  type LocalizedDatePickerLabels,\n  type LocalizedDateRangePickerLabels,\n  type LocalizedDateTimeRange,\n  type LocalizedDateTimePickerLabels,\n  type LocalizedDateTimeRangePickerLabels,\n  type LocalizedRangeRequirements,\n  type LocalizedTimePickerLabels,\n  type LocalizedTimePickerMode,\n  type LocalizedTimeRange,\n  type LocalizedTimeRangePickerLabels,\n  type SiteSelectOption,\n} from '@alittlemore.dev/design-system';\n```\n\n| Import                                  | Selector                             | Purpose                                                         |\n| --------------------------------------- | ------------------------------------ | --------------------------------------------------------------- |\n| `EmptyStateComponent`                   | `ds-empty-state`                     | Displays a consumer-supplied empty-state message.               |\n| `LoadingSpinnerComponent`               | `ds-loading-spinner`                 | Displays a named loading status.                                |\n| `ErrorMessageComponent`                 | `ds-error-message`                   | Displays an `ErrorDisplay` and emits `retry`.                   |\n| `FoldableTreeComponent`                 | `ds-foldable-tree`                   | Renders consumer-owned tree data and emits selected item keys.  |\n| `LocalizedDatePickerComponent`          | `ds-localized-date-picker`           | Provides a localized calendar-date form control.                |\n| `LocalizedDateRangePickerComponent`     | `ds-localized-date-range-picker`     | Provides an inclusive localized calendar-date range control.    |\n| `LocalizedDateTimePickerComponent`      | `ds-localized-datetime-picker`       | Provides a localized local-wall-clock datetime control.         |\n| `LocalizedDateTimeRangePickerComponent` | `ds-localized-datetime-range-picker` | Provides an inclusive local-wall-clock datetime range control.  |\n| `LocalizedTimePickerComponent`          | `ds-localized-time-picker`           | Provides a localized local-wall-clock time control.             |\n| `LocalizedTimeRangePickerComponent`     | `ds-localized-time-range-picker`     | Provides a same-day local-wall-clock time range control.        |\n| `NotificationAreaComponent`             | `ds-notification-area`               | Renders and dismisses notifications from `NotificationService`. |\n| `SiteSelectComponent`                   | `ds-site-select`                     | Provides a select-only combobox form control.                   |\n\nAll labels, messages, option text, and localized date-picker strings are consumer-owned. Supply\nthem through component inputs and application i18n; the package does not provide translations or\ndepend on an i18n service.\n\nPlace one notification area in the application shell and provide its translated close label from\nthe consumer. Inject `NotificationService` wherever the application needs to publish a success or\nerror message; both kinds auto-dismiss after five seconds.\n\n```ts\nimport { Component, inject } from '@angular/core';\nimport { NotificationAreaComponent, NotificationService } from '@alittlemore.dev/design-system';\n\n@Component({\n  standalone: true,\n  imports: [NotificationAreaComponent],\n  template: `\n    <ds-notification-area closeLabel=\"Close notification\" />\n    <button type=\"button\" (click)=\"notifications.success('Saved')\">Save</button>\n  `,\n})\nexport class ApplicationShellComponent {\n  readonly notifications = inject(NotificationService);\n}\n```\n\n`SiteSelectComponent` integrates with Angular Reactive Forms. It receives its value, touched and\ndisabled state through the control, while the consumer continues to own validation presentation:\n\n```ts\nimport { Component } from '@angular/core';\nimport { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';\nimport { SiteSelectComponent, type SiteSelectOption } from '@alittlemore.dev/design-system';\n\n@Component({\n  standalone: true,\n  imports: [ReactiveFormsModule, SiteSelectComponent],\n  template: `\n    <ds-site-select\n      inputId=\"site\"\n      [options]=\"siteOptions\"\n      controlSize=\"default\"\n      appearance=\"default\"\n      [required]=\"true\"\n      [invalid]=\"site.invalid && site.touched\"\n      [controlDisabled]=\"false\"\n      [formControl]=\"site\"\n    />\n  `,\n})\nexport class SiteFormComponent {\n  readonly site = new FormControl('', { nonNullable: true, validators: [Validators.required] });\n  readonly siteOptions: readonly SiteSelectOption[] = [\n    { value: 'alpha', label: 'Alpha site' },\n    { value: 'beta', label: 'Beta site' },\n  ];\n}\n```\n\nBoth form controls also support controlled use. Bind `value` and update the owning state from\n`valueChange`; when `value` is supplied it is the external source of truth:\n\n```html\n<ds-site-select\n  inputId=\"site\"\n  [options]=\"siteOptions\"\n  [value]=\"selectedSite()\"\n  (valueChange)=\"selectedSite.set($event)\"\n  controlSize=\"default\"\n  appearance=\"default\"\n  [required]=\"false\"\n  [invalid]=\"false\"\n  [controlDisabled]=\"false\"\n/>\n```\n\n### Localized date, time, and datetime pickers\n\nThe six localized temporal controls keep canonical model values separate from localized display.\nUse `LocalizedDatePickerComponent`, `LocalizedTimePickerComponent`, or\n`LocalizedDateTimePickerComponent` for a single value, and use the corresponding range component\nfor a composite start/end field. Their selectors and imports are listed in the table above.\n\nThe canonical TypeScript values are exactly:\n\n```ts\n// LocalizedDatePickerComponent\nconst date: string | null = '2026-09-11'; // YYYY-MM-DD or null\n\n// LocalizedTimePickerComponent\nconst time: string | null = '14:30'; // HH:mm or null\n\n// LocalizedDateTimePickerComponent\nconst dateTime: string | null = '2026-09-11T14:30'; // YYYY-MM-DDTHH:mm or null\n\ninterface LocalizedDateRange {\n  readonly start: string | null; // YYYY-MM-DD or null\n  readonly end: string | null; // YYYY-MM-DD or null\n}\n\ninterface LocalizedTimeRange {\n  readonly start: string | null; // HH:mm or null\n  readonly end: string | null; // HH:mm or null\n}\n\ninterface LocalizedDateTimeRange {\n  readonly start: string | null; // YYYY-MM-DDTHH:mm or null\n  readonly end: string | null; // YYYY-MM-DDTHH:mm or null\n}\n```\n\n`null` is the only canonical empty single value or range endpoint; an empty range is the non-null\nobject `{ start: null, end: null }`. Empty text commits `null`. Malformed, incomplete, out-of-range,\nor unavailable text remains visible as an invalid draft and does not replace the last canonical\nvalue. Manual entry commits a valid value, valid range, or permitted empty value on Enter or blur.\nOpening a picker dialog instead creates an isolated draft: calendar and time selections, Today,\nNow, and Clear do not emit a model or Angular Forms change. Done commits the complete valid draft;\nCancel, Escape, and outside dismissal restore the last canonical value. Clear therefore becomes a\ncommitted empty value only after Done.\n\nEach range is one accessible, composite field with one trigger and start/end endpoints inside the\nsame shell. Date and datetime range dialogs preview the interval from the selected start through a\nhovered or keyboard-focused candidate end without committing it, and announce that preview with\nconsumer-supplied localized text. Calendar selections alternate between replacing start and end;\nwhen a replacement crosses the other endpoint, the two dates are ordered automatically. This cycle\ncontinues after a complete range, so changing either boundary never requires Clear or a visible\nendpoint-mode switch.\n\nSingle-value controls use `required`. Range controls instead use the breaking\n`LocalizedRangeRequirements` contract:\n\n```ts\ninterface LocalizedRangeRequirements {\n  readonly start: boolean;\n  readonly end: boolean;\n  readonly paired: boolean;\n}\n```\n\n`paired` leaves an entirely empty optional range valid, but once either endpoint is present it\nrequires the opposite endpoint. The complete requirements truth table is:\n\n| `start` | `end` | `paired` | Effective start requirement | Effective end requirement |\n| ------- | ----- | -------- | --------------------------- | ------------------------- |\n| false   | false | false    | Never                       | Never                     |\n| false   | false | true     | When end is present         | When start is present     |\n| false   | true  | false    | Never                       | Always                    |\n| false   | true  | true     | When end is present         | Always                    |\n| true    | false | false    | Always                      | Never                     |\n| true    | false | true     | Always                      | When start is present     |\n| true    | true  | false    | Always                      | Always                    |\n| true    | true  | true     | Always                      | Always                    |\n\nRange endpoints may be independently nullable when the requirements permit it. Non-null endpoints\nmust still be canonical, available, and ordered. Equal endpoints are valid. A\n`LocalizedTimeRange` is always a same-day interval, so `start` must be earlier than or equal to\n`end`; an overnight range such as `22:00` to `02:00` is invalid. Datetime ranges use their complete\ndate and time values, so the same time-order rule applies when both endpoints are on the same date.\n\nValid `min` and `max` inputs are inclusive and use the canonical format for that picker. Invalid\nbound strings are ignored. `disabledDates` uses canonical `YYYY-MM-DD` values on date-capable\npickers and disables each whole local calendar date. A complete date or datetime range cannot cross\na disabled date.\n\nTime-capable controls accept `timePickerMode: LocalizedTimePickerMode`, where the value is `auto`,\n`native`, or `custom`. `custom` uses the package's segmented 24-hour HH:mm control with direct\nnumeric entry, keyboard stepping, and visible mouse increment/decrement buttons above and below\neach segment. `native` uses a minute-precision browser time input when running in a browser. `auto`\nchooses native UI for a coarse pointer and custom UI otherwise, evaluating that capability whenever\nthe dialog opens; server rendering remains custom. Time-range and datetime-range dialogs show both\ntime endpoints together and route edits to the focused endpoint. Today applies the current local\ncalendar date to the alternating calendar boundary, and Now applies the current local time at\nminute precision (plus the current local date for a focused datetime endpoint). Both honor\navailability and remain draft-only until Done.\n\nDates and times are local wall-clock strings. They have no offset or zone and the package never\nconverts them to UTC. `dateLocale` affects date parsing, display, calendar labels, and week layout\nonly; application code decides which timezone, if any, gives a value meaning.\n\nEvery picker label contract is required and exhaustive:\n`LocalizedDatePickerLabels`, `LocalizedDateRangePickerLabels`, `LocalizedTimePickerLabels`,\n`LocalizedTimeRangePickerLabels`, `LocalizedDateTimePickerLabels`, and\n`LocalizedDateTimeRangePickerLabels`. Supply every visible label, message, hint, action, and range\npreview announcement through application i18n.\n\nAll six pickers support controlled `value`/`valueChange` use and implement `ControlValueAccessor`\nand `Validator`. In Reactive Forms, bind one `FormControl` to the whole range component rather than\ncreating a control for each endpoint:\n\n```ts\nimport { FormControl } from '@angular/forms';\nimport type { LocalizedDateTimeRange } from '@alittlemore.dev/design-system';\n\nexport class AvailabilityForm {\n  readonly availability = new FormControl<LocalizedDateTimeRange>(\n    { start: null, end: null },\n    { nonNullable: true },\n  );\n}\n```\n\n```html\n<ds-localized-datetime-range-picker\n  inputId=\"availability\"\n  [formControl]=\"availability\"\n  controlSize=\"default\"\n  dateLocale=\"en-US\"\n  [labels]=\"dateTimeRangeLabels\"\n  [requirements]=\"{ start: false, end: false, paired: true }\"\n  [invalid]=\"availability.invalid && availability.touched\"\n  [controlDisabled]=\"false\"\n  [readonly]=\"false\"\n  timePickerMode=\"auto\"\n/>\n```\n\nIf an application payload represents missing endpoints by omitting properties, map the canonical\nrange at the application boundary:\n\n```ts\nfunction createAvailabilityPayload(range: LocalizedDateTimeRange) {\n  const payload = {\n    ...(range.start === null ? {} : { availableFrom: range.start }),\n    ...(range.end === null ? {} : { availableTo: range.end }),\n  };\n  return payload;\n}\n```\n\n## Markdown rendering\n\nImport the renderer independently from the editor:\n\n```ts\nimport {\n  MarkdownRendererService,\n  type MarkdownWikiLinkRenderConfig,\n} from '@alittlemore.dev/design-system/markdown';\n\nconst wikiLinks: MarkdownWikiLinkRenderConfig = {\n  namespaces: [{ key: 'docs', label: 'Documentation' }],\n  resolve: ({ namespace, key }) =>\n    namespace === 'docs' ? { href: `/docs/${encodeURIComponent(key)}`, openIn: 'same-tab' } : null,\n};\n\nconst html = inject(MarkdownRendererService).render(markdown, { wikiLinks });\n```\n\nThe renderer drops authored HTML, rejects unsafe link and image schemes, sanitizes the generated\nHTML, highlights supported fenced-code languages, and emits resolved wiki-links as native anchors.\nUnknown namespaces, unresolved targets, and wiki-like text inside inline or fenced code remain\nplain Markdown text. Import `styles/markdown` in the consuming application's global SCSS for the\ncode and Prism presentation.\n\nWiki syntax is application-independent and fixed as `[[namespace:key|label]]`; the label is\noptional. The package also exports `parseMarkdownWikiLinks`,\n`createMarkdownWikiLinkTargetLookup`, and `findMissingMarkdownWikiLinkTargets` for validation and\ncontent tooling.\n\n## Markdown editor\n\nImport the standalone editor from its secondary entry point:\n\n```ts\nimport {\n  MarkdownEditorComponent,\n  type MarkdownEditorImageConfig,\n  type MarkdownEditorLabels,\n  type MarkdownEditorWikiLinkConfig,\n} from '@alittlemore.dev/design-system/markdown-editor';\n```\n\n```html\n<ds-markdown-editor\n  [value]=\"markdown()\"\n  accessibleLabel=\"Article body\"\n  [labels]=\"editorLabels\"\n  [imageConfig]=\"imageConfig\"\n  [imageInteractionsDisabled]=\"saving()\"\n  [wikiLinks]=\"wikiLinks\"\n  (valueChange)=\"markdown.set($event)\"\n  (imageUploadPendingChange)=\"imageUploadPending.set($event)\"\n/>\n```\n\n`MarkdownEditorLabels` is exhaustive by design: the consumer owns every visible editor, search,\ntable, upload, preview, shortcut, and accessibility string. This keeps the package independent of\nan application i18n service. `value` is controlled state; external synchronization does not emit a\nsecond `valueChange`. `focus()` is the only imperative public editor action.\n\nImage transport and image preview are deliberately separate. Choose any combination of `picker`,\n`paste`, and `drop` for one editor instance:\n\n```ts\nconst publicImages: MarkdownEditorImageConfig = {\n  upload: {\n    sources: ['picker', 'paste', 'drop'],\n    acceptedMimeTypes: ['image/jpeg', 'image/png', 'image/webp'],\n    upload: (file) => articleImages.upload(file), // Observable<{ source: string }>\n  },\n  preview: { kind: 'direct' },\n};\n\nconst protectedImages: MarkdownEditorImageConfig = {\n  upload: {\n    sources: ['paste', 'drop'],\n    acceptedMimeTypes: ['image/png'],\n    upload: (file) => attachments.upload(file),\n  },\n  preview: {\n    kind: 'blob',\n    revision: attachmentRevision(),\n    load: (source) => attachments.loadAuthorizedBlob(source),\n  },\n};\n```\n\n`direct` leaves safe rendered image URLs in the preview DOM. `blob` removes each protected source\nbefore binding the preview, loads it through the supplied observable, uses a temporary object URL,\nand revokes that URL on retry, revision or configuration changes, document changes, mode changes,\nand destruction. Set `upload: null` for a read-only editor that still needs either preview strategy,\nor set the whole `imageConfig` to `null` when no image integration is needed. MIME matching is\nexact. Uploads run sequentially in stable insertion order; a failure pauses the queue until the\nconsumer-facing retry or dismiss action is used. `imageInteractionsDisabled` pauses new actions and\nqueued retries without removing active previews or discarding queued work.\n\nOrdinary file attachments are intentionally not part of the current contract; only images are\naccepted in this version.\n\nWiki completion and rendering share one neutral configuration:\n\n```ts\nconst wikiLinks: MarkdownEditorWikiLinkConfig = {\n  namespaces: [\n    { key: 'articles', label: 'Articles' },\n    { key: 'people', label: 'People' },\n  ],\n  loadTargets: () => contentIndex.snapshot(), // Observable<MarkdownWikiLinkTargetGroup[]>\n  resolve: ({ namespace, key }) => ({\n    href: `/content/${namespace}/${encodeURIComponent(key)}`,\n    openIn: 'same-tab',\n  }),\n};\n```\n\nThe target snapshot supplies completion labels, descriptions, and badges. A registry error is\nreported in the editor status area but never blocks manual editing or preview. Set `wikiLinks` to\n`null` to disable wiki completion and interpretation.\n\n`MarkdownEditorStickyBottomInsetDirective` is available as\n`[dsMarkdownEditorStickyBottomInset]` for forms with a sticky action footer. It maintains the\neditor's bottom inset with a narrowly scoped, nonce-bearing runtime style and remains SSR-safe.\n\n## Styles\n\nThe public SCSS entry points are:\n\n- `styles/theme-tokens`: light and dark custom properties except Bootstrap's `--bs-*` mappings;\n- `styles/bootstrap-overrides`: Bootstrap's base styles, theme mappings, button overrides, and focused\n  form-control presentation;\n- `styles/cdk-overlay`: the official Angular CDK overlay structural styles;\n- `styles/ui`: application-independent global UI selectors;\n- `styles/markdown`: `.markdown-code` and Prism token presentation.\n\nImport tokens first, then Bootstrap, followed by only the optional capabilities an application uses:\n\n```scss\n@use '@alittlemore.dev/design-system/styles/theme-tokens';\n@use '@alittlemore.dev/design-system/styles/bootstrap-overrides';\n@use '@alittlemore.dev/design-system/styles/cdk-overlay';\n@use '@alittlemore.dev/design-system/styles/ui';\n@use '@alittlemore.dev/design-system/styles/markdown';\n```\n\n`bootstrap-overrides` already emits the Bootstrap base. Do not import Bootstrap separately or the\napplication will duplicate its global CSS.\n\nThe independent UI components currently require this style composition and the package's Bootstrap\npeer prerequisite. Keep `theme-tokens` before `bootstrap-overrides`; include `cdk-overlay` when\nusing CDK overlays and `ui` when using the UI components. Bootstrap retirement is a separate\nroadmap item, so consumers must continue to satisfy the current Bootstrap peer dependency.\n\nThe package deliberately excludes unused `.accent-border` and `.link-inactive` selectors. It also\nexcludes application-owned `.form-bordered`, `.force-display-block`, article and matrix selectors,\nthe cookie-consent banner, and application Markdown-wrapper selectors.\n\n## Initial theme preload\n\n`@alittlemore.dev/design-system/theme-preload` exports the classic `theme-preload.js` web asset. Copy\nthat file from the installed package into the application's web output, then load the self-hosted\ncopy synchronously in `<head>` before application code and without `async` or `defer`:\n\n```json\n{\n  \"glob\": \"theme-preload.js\",\n  \"input\": \"node_modules/@alittlemore.dev/design-system\",\n  \"output\": \"/assets/design-system\"\n}\n```\n\n```html\n<script src=\"/assets/design-system/theme-preload.js\" nonce=\"YOUR_CSP_NONCE\"></script>\n```\n\nOmit `nonce` when the application's policy does not require it. Keep both the preload and compiled\nCSS external under strict CSP; do not copy the script into an inline block.\n\nThe preload accepts only `light` or `dark`. It applies a valid `chosenTheme` value from\n`localStorage`, otherwise a valid server-rendered `data-bs-theme`, otherwise `light`. Missing or\nunavailable browser globals and storage do not throw; server execution is a no-op.\n","readmeFilename":"README.md"}