@cdevhub/ngx-tw 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +134 -0
  3. package/fesm2022/cdevhub-ngx-tw-accordion.mjs +108 -0
  4. package/fesm2022/cdevhub-ngx-tw-accordion.mjs.map +1 -0
  5. package/fesm2022/cdevhub-ngx-tw-alert.mjs +240 -0
  6. package/fesm2022/cdevhub-ngx-tw-alert.mjs.map +1 -0
  7. package/fesm2022/cdevhub-ngx-tw-avatar.mjs +287 -0
  8. package/fesm2022/cdevhub-ngx-tw-avatar.mjs.map +1 -0
  9. package/fesm2022/cdevhub-ngx-tw-badge.mjs +332 -0
  10. package/fesm2022/cdevhub-ngx-tw-badge.mjs.map +1 -0
  11. package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs +452 -0
  12. package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs.map +1 -0
  13. package/fesm2022/cdevhub-ngx-tw-button.mjs +227 -0
  14. package/fesm2022/cdevhub-ngx-tw-button.mjs.map +1 -0
  15. package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs +417 -0
  16. package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs.map +1 -0
  17. package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs +232 -0
  18. package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs.map +1 -0
  19. package/fesm2022/cdevhub-ngx-tw-calendar.mjs +4482 -0
  20. package/fesm2022/cdevhub-ngx-tw-calendar.mjs.map +1 -0
  21. package/fesm2022/cdevhub-ngx-tw-card.mjs +157 -0
  22. package/fesm2022/cdevhub-ngx-tw-card.mjs.map +1 -0
  23. package/fesm2022/cdevhub-ngx-tw-carousel.mjs +1454 -0
  24. package/fesm2022/cdevhub-ngx-tw-carousel.mjs.map +1 -0
  25. package/fesm2022/cdevhub-ngx-tw-checkbox.mjs +593 -0
  26. package/fesm2022/cdevhub-ngx-tw-checkbox.mjs.map +1 -0
  27. package/fesm2022/cdevhub-ngx-tw-code-block.mjs +228 -0
  28. package/fesm2022/cdevhub-ngx-tw-code-block.mjs.map +1 -0
  29. package/fesm2022/cdevhub-ngx-tw-collapsible.mjs +510 -0
  30. package/fesm2022/cdevhub-ngx-tw-collapsible.mjs.map +1 -0
  31. package/fesm2022/cdevhub-ngx-tw-combobox.mjs +1721 -0
  32. package/fesm2022/cdevhub-ngx-tw-combobox.mjs.map +1 -0
  33. package/fesm2022/cdevhub-ngx-tw-command-palette.mjs +721 -0
  34. package/fesm2022/cdevhub-ngx-tw-command-palette.mjs.map +1 -0
  35. package/fesm2022/cdevhub-ngx-tw-core.mjs +861 -0
  36. package/fesm2022/cdevhub-ngx-tw-core.mjs.map +1 -0
  37. package/fesm2022/cdevhub-ngx-tw-date-picker.mjs +1558 -0
  38. package/fesm2022/cdevhub-ngx-tw-date-picker.mjs.map +1 -0
  39. package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs +1833 -0
  40. package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs.map +1 -0
  41. package/fesm2022/cdevhub-ngx-tw-dialog.mjs +693 -0
  42. package/fesm2022/cdevhub-ngx-tw-dialog.mjs.map +1 -0
  43. package/fesm2022/cdevhub-ngx-tw-empty-state.mjs +331 -0
  44. package/fesm2022/cdevhub-ngx-tw-empty-state.mjs.map +1 -0
  45. package/fesm2022/cdevhub-ngx-tw-flip-card.mjs +289 -0
  46. package/fesm2022/cdevhub-ngx-tw-flip-card.mjs.map +1 -0
  47. package/fesm2022/cdevhub-ngx-tw-form-field.mjs +572 -0
  48. package/fesm2022/cdevhub-ngx-tw-form-field.mjs.map +1 -0
  49. package/fesm2022/cdevhub-ngx-tw-icon-lucide.mjs +36 -0
  50. package/fesm2022/cdevhub-ngx-tw-icon-lucide.mjs.map +1 -0
  51. package/fesm2022/cdevhub-ngx-tw-icon.mjs +278 -0
  52. package/fesm2022/cdevhub-ngx-tw-icon.mjs.map +1 -0
  53. package/fesm2022/cdevhub-ngx-tw-input.mjs +355 -0
  54. package/fesm2022/cdevhub-ngx-tw-input.mjs.map +1 -0
  55. package/fesm2022/cdevhub-ngx-tw-item.mjs +241 -0
  56. package/fesm2022/cdevhub-ngx-tw-item.mjs.map +1 -0
  57. package/fesm2022/cdevhub-ngx-tw-menu.mjs +433 -0
  58. package/fesm2022/cdevhub-ngx-tw-menu.mjs.map +1 -0
  59. package/fesm2022/cdevhub-ngx-tw-paginator.mjs +691 -0
  60. package/fesm2022/cdevhub-ngx-tw-paginator.mjs.map +1 -0
  61. package/fesm2022/cdevhub-ngx-tw-popover.mjs +678 -0
  62. package/fesm2022/cdevhub-ngx-tw-popover.mjs.map +1 -0
  63. package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs +335 -0
  64. package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs.map +1 -0
  65. package/fesm2022/cdevhub-ngx-tw-radio.mjs +708 -0
  66. package/fesm2022/cdevhub-ngx-tw-radio.mjs.map +1 -0
  67. package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs +319 -0
  68. package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs.map +1 -0
  69. package/fesm2022/cdevhub-ngx-tw-select.mjs +1722 -0
  70. package/fesm2022/cdevhub-ngx-tw-select.mjs.map +1 -0
  71. package/fesm2022/cdevhub-ngx-tw-separator.mjs +115 -0
  72. package/fesm2022/cdevhub-ngx-tw-separator.mjs.map +1 -0
  73. package/fesm2022/cdevhub-ngx-tw-sheet.mjs +768 -0
  74. package/fesm2022/cdevhub-ngx-tw-sheet.mjs.map +1 -0
  75. package/fesm2022/cdevhub-ngx-tw-skeleton.mjs +149 -0
  76. package/fesm2022/cdevhub-ngx-tw-skeleton.mjs.map +1 -0
  77. package/fesm2022/cdevhub-ngx-tw-slider.mjs +1147 -0
  78. package/fesm2022/cdevhub-ngx-tw-slider.mjs.map +1 -0
  79. package/fesm2022/cdevhub-ngx-tw-sort.mjs +346 -0
  80. package/fesm2022/cdevhub-ngx-tw-sort.mjs.map +1 -0
  81. package/fesm2022/cdevhub-ngx-tw-spinner.mjs +186 -0
  82. package/fesm2022/cdevhub-ngx-tw-spinner.mjs.map +1 -0
  83. package/fesm2022/cdevhub-ngx-tw-split.mjs +1082 -0
  84. package/fesm2022/cdevhub-ngx-tw-split.mjs.map +1 -0
  85. package/fesm2022/cdevhub-ngx-tw-stat.mjs +579 -0
  86. package/fesm2022/cdevhub-ngx-tw-stat.mjs.map +1 -0
  87. package/fesm2022/cdevhub-ngx-tw-stepper.mjs +443 -0
  88. package/fesm2022/cdevhub-ngx-tw-stepper.mjs.map +1 -0
  89. package/fesm2022/cdevhub-ngx-tw-switch.mjs +391 -0
  90. package/fesm2022/cdevhub-ngx-tw-switch.mjs.map +1 -0
  91. package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs +315 -0
  92. package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs.map +1 -0
  93. package/fesm2022/cdevhub-ngx-tw-table.mjs +994 -0
  94. package/fesm2022/cdevhub-ngx-tw-table.mjs.map +1 -0
  95. package/fesm2022/cdevhub-ngx-tw-tabs.mjs +508 -0
  96. package/fesm2022/cdevhub-ngx-tw-tabs.mjs.map +1 -0
  97. package/fesm2022/cdevhub-ngx-tw-textarea.mjs +147 -0
  98. package/fesm2022/cdevhub-ngx-tw-textarea.mjs.map +1 -0
  99. package/fesm2022/cdevhub-ngx-tw-theme.mjs +178 -0
  100. package/fesm2022/cdevhub-ngx-tw-theme.mjs.map +1 -0
  101. package/fesm2022/cdevhub-ngx-tw-time-picker.mjs +1344 -0
  102. package/fesm2022/cdevhub-ngx-tw-time-picker.mjs.map +1 -0
  103. package/fesm2022/cdevhub-ngx-tw-timeline.mjs +1136 -0
  104. package/fesm2022/cdevhub-ngx-tw-timeline.mjs.map +1 -0
  105. package/fesm2022/cdevhub-ngx-tw-toast.mjs +1311 -0
  106. package/fesm2022/cdevhub-ngx-tw-toast.mjs.map +1 -0
  107. package/fesm2022/cdevhub-ngx-tw-tooltip.mjs +533 -0
  108. package/fesm2022/cdevhub-ngx-tw-tooltip.mjs.map +1 -0
  109. package/fesm2022/cdevhub-ngx-tw.mjs +61 -0
  110. package/fesm2022/cdevhub-ngx-tw.mjs.map +1 -0
  111. package/package.json +265 -0
  112. package/theme/_base.css +394 -0
  113. package/theme/_dark.css +510 -0
  114. package/theme/_high-contrast.css +290 -0
  115. package/theme/_semantic.css +317 -0
  116. package/theme/_typography.css +9 -0
  117. package/theme/index.css +54 -0
  118. package/types/cdevhub-ngx-tw-accordion.d.ts +48 -0
  119. package/types/cdevhub-ngx-tw-alert.d.ts +67 -0
  120. package/types/cdevhub-ngx-tw-avatar.d.ts +79 -0
  121. package/types/cdevhub-ngx-tw-badge.d.ts +94 -0
  122. package/types/cdevhub-ngx-tw-breadcrumbs.d.ts +227 -0
  123. package/types/cdevhub-ngx-tw-button.d.ts +74 -0
  124. package/types/cdevhub-ngx-tw-calendar-luxon.d.ts +200 -0
  125. package/types/cdevhub-ngx-tw-calendar-testing.d.ts +113 -0
  126. package/types/cdevhub-ngx-tw-calendar.d.ts +1704 -0
  127. package/types/cdevhub-ngx-tw-card.d.ts +48 -0
  128. package/types/cdevhub-ngx-tw-carousel.d.ts +406 -0
  129. package/types/cdevhub-ngx-tw-checkbox.d.ts +136 -0
  130. package/types/cdevhub-ngx-tw-code-block.d.ts +93 -0
  131. package/types/cdevhub-ngx-tw-collapsible.d.ts +151 -0
  132. package/types/cdevhub-ngx-tw-combobox.d.ts +389 -0
  133. package/types/cdevhub-ngx-tw-command-palette.d.ts +215 -0
  134. package/types/cdevhub-ngx-tw-core.d.ts +719 -0
  135. package/types/cdevhub-ngx-tw-date-picker.d.ts +317 -0
  136. package/types/cdevhub-ngx-tw-date-range-picker.d.ts +313 -0
  137. package/types/cdevhub-ngx-tw-dialog.d.ts +303 -0
  138. package/types/cdevhub-ngx-tw-empty-state.d.ts +104 -0
  139. package/types/cdevhub-ngx-tw-flip-card.d.ts +95 -0
  140. package/types/cdevhub-ngx-tw-form-field.d.ts +210 -0
  141. package/types/cdevhub-ngx-tw-icon-lucide.d.ts +29 -0
  142. package/types/cdevhub-ngx-tw-icon.d.ts +134 -0
  143. package/types/cdevhub-ngx-tw-input.d.ts +110 -0
  144. package/types/cdevhub-ngx-tw-item.d.ts +70 -0
  145. package/types/cdevhub-ngx-tw-menu.d.ts +99 -0
  146. package/types/cdevhub-ngx-tw-paginator.d.ts +305 -0
  147. package/types/cdevhub-ngx-tw-popover.d.ts +157 -0
  148. package/types/cdevhub-ngx-tw-progress-bar.d.ts +100 -0
  149. package/types/cdevhub-ngx-tw-radio.d.ts +174 -0
  150. package/types/cdevhub-ngx-tw-segmented-control.d.ts +66 -0
  151. package/types/cdevhub-ngx-tw-select.d.ts +384 -0
  152. package/types/cdevhub-ngx-tw-separator.d.ts +28 -0
  153. package/types/cdevhub-ngx-tw-sheet.d.ts +335 -0
  154. package/types/cdevhub-ngx-tw-skeleton.d.ts +52 -0
  155. package/types/cdevhub-ngx-tw-slider.d.ts +258 -0
  156. package/types/cdevhub-ngx-tw-sort.d.ts +136 -0
  157. package/types/cdevhub-ngx-tw-spinner.d.ts +50 -0
  158. package/types/cdevhub-ngx-tw-split.d.ts +265 -0
  159. package/types/cdevhub-ngx-tw-stat.d.ts +140 -0
  160. package/types/cdevhub-ngx-tw-stepper.d.ts +127 -0
  161. package/types/cdevhub-ngx-tw-switch.d.ts +88 -0
  162. package/types/cdevhub-ngx-tw-tab-nav.d.ts +107 -0
  163. package/types/cdevhub-ngx-tw-table.d.ts +479 -0
  164. package/types/cdevhub-ngx-tw-tabs.d.ts +121 -0
  165. package/types/cdevhub-ngx-tw-textarea.d.ts +62 -0
  166. package/types/cdevhub-ngx-tw-theme.d.ts +106 -0
  167. package/types/cdevhub-ngx-tw-time-picker.d.ts +303 -0
  168. package/types/cdevhub-ngx-tw-timeline.d.ts +278 -0
  169. package/types/cdevhub-ngx-tw-toast.d.ts +388 -0
  170. package/types/cdevhub-ngx-tw-tooltip.d.ts +95 -0
  171. package/types/cdevhub-ngx-tw.d.ts +49 -0
@@ -0,0 +1,147 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, input, booleanAttribute, numberAttribute, computed, effect, forwardRef, Directive } from '@angular/core';
3
+ import * as i1 from '@angular/cdk/text-field';
4
+ import { CdkTextareaAutosize } from '@angular/cdk/text-field';
5
+ import { tv } from 'tailwind-variants';
6
+ import { TW_FORM_FIELD_CONTROL } from '@cdevhub/ngx-tw/form-field';
7
+ import { InputDirective } from '@cdevhub/ngx-tw/input';
8
+
9
+ const textareaVariants = tv({
10
+ base: '',
11
+ variants: {
12
+ resize: {
13
+ none: 'resize-none',
14
+ vertical: 'resize-y',
15
+ both: 'resize',
16
+ },
17
+ },
18
+ defaultVariants: {
19
+ resize: 'vertical',
20
+ },
21
+ }, { twMerge: true });
22
+ /**
23
+ * Adapts a native `<textarea>` into an ngx-tw form-field-compatible multi-line
24
+ * control. Extends {@link InputDirective} — inherits its form-field
25
+ * integration, error-state machinery, autofill / focus tracking, ARIA wiring,
26
+ * and standalone styling. Adds textarea-specific behavior: autosize composition
27
+ * via CDK's `CdkTextareaAutosize`, a `resize` axis for the user-resize handle,
28
+ * `rows` / `minRows` / `maxRows`, and `maxLength` with a `valueLength` signal
29
+ * for character counters.
30
+ *
31
+ * Like `InputDirective`, this directive deliberately does NOT implement
32
+ * `ControlValueAccessor`. Angular's built-in `DefaultValueAccessor` attaches to
33
+ * the native `<textarea>` and handles value I/O for template-driven
34
+ * (`ngModel`), reactive (`FormControl` / `formControlName`), and signal-forms
35
+ * (`formField`) bindings — no library glue required.
36
+ *
37
+ * Form-control input-cap exception applies (codified in CLAUDE.md): ARIA +
38
+ * forms baseline plus textarea-specific surface exceeds the 5–6 cap;
39
+ * `checkbox` is the canonical 12+ exemplar.
40
+ */
41
+ class TextareaDirective extends InputDirective {
42
+ cdkAutosize = inject(CdkTextareaAutosize, {
43
+ self: true,
44
+ optional: true,
45
+ });
46
+ // @internal Re-declares the `size` input inherited from `InputDirective` so
47
+ // it lands on this directive's own `ɵdir` input metadata. ng-packagr emits
48
+ // `ɵdir` with only the child's directly declared inputs, so without this the
49
+ // consumer-side strict template-check rejects `<textarea twTextarea
50
+ // [size]="…">` with NG8002. Both signals share the same `'md'` default and
51
+ // the inherited `classes()` computed reads through polymorphism, so runtime
52
+ // behaviour is unchanged.
53
+ //
54
+ // The `@ts-ignore` is needed for a CI-only quirk: under
55
+ // `noImplicitOverride`, partial compilation resolves `InputDirective`
56
+ // through the just-built `dist/` d.ts on CI, where TypeScript decides the
57
+ // base member isn't visible and rejects `override` with TS4113. Locally
58
+ // the same resolution sees the member, so `@ts-expect-error` would itself
59
+ // flag as unused. `@ts-ignore` suppresses whichever error fires and is
60
+ // a no-op when neither does.
61
+ /** Density of a standalone textarea. Maps to the inline-padding + font scale (`xs` … `xl`). Ignored inside a `<tw-form-field>` — the wrapper's `size` carries density. Defaults to `'md'`. */
62
+ // @ts-ignore — TS4113 (CI) vs TS4114 (local); see comment above.
63
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
64
+ /** Grows the textarea with its content (composed from CDK's `CdkTextareaAutosize`). When `true` the user-resize handle is forced off — autosize owns the height. Defaults to `false`. */
65
+ autosize = input(false, { ...(ngDevMode ? { debugName: "autosize" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
66
+ /** Minimum number of rows the textarea collapses to when `autosize` is `true`. Ignored when autosize is off. Defaults to `1`. */
67
+ minRows = input(1, { ...(ngDevMode ? { debugName: "minRows" } : /* istanbul ignore next */ {}), transform: numberAttribute });
68
+ /** Maximum number of rows the textarea expands to before scrolling, when `autosize` is `true`. `undefined` removes the cap. Ignored when autosize is off. Defaults to `undefined`. */
69
+ maxRows = input(undefined, { ...(ngDevMode ? { debugName: "maxRows" } : /* istanbul ignore next */ {}), transform: (v) => (v === undefined || v === null || v === '' ? undefined : numberAttribute(v)) });
70
+ /** Number of rows for the initial render height (native `rows` attribute). Browsers honor this even when `autosize` is `true`, so first paint uses this value. Defaults to `3`. */
71
+ rows = input(3, { ...(ngDevMode ? { debugName: "rows" } : /* istanbul ignore next */ {}), transform: numberAttribute });
72
+ /** Controls the user-resize handle: `'none'` locks the size, `'vertical'` (default) allows vertical drag, `'both'` allows both axes. Forced to `'none'` when `autosize` is `true`. Horizontal-only is intentionally not supported. */
73
+ resize = input('vertical', ...(ngDevMode ? [{ debugName: "resize" }] : /* istanbul ignore next */ []));
74
+ /** Maximum character count. Mirrors to the native `maxlength` attribute when defined and is exposed via the `valueLength` signal so consumers can render a "X / N" hint. Defaults to `undefined`. */
75
+ maxLength = input(undefined, { ...(ngDevMode ? { debugName: "maxLength" } : /* istanbul ignore next */ {}), transform: (v) => (v === undefined || v === null || v === '' ? undefined : numberAttribute(v)) });
76
+ /** Current value length, updates on every `input` event. Wire `<span twHint align="end">{{ ta.valueLength() }} / {{ ta.maxLength() }}</span>` for a character counter. */
77
+ valueLength = computed(() => this.value()?.length ?? 0, ...(ngDevMode ? [{ debugName: "valueLength" }] : /* istanbul ignore next */ []));
78
+ /** @internal Textarea-specific Tailwind classes (resize axis). Combined with the inherited `classes()` in the host `[class]` binding. */
79
+ textareaClasses = computed(() =>
80
+ // Short-circuit the resize/autosize conflict deterministically — don't
81
+ // rely on tv variant emission order or twMerge "last wins" semantics.
82
+ this.autosize()
83
+ ? textareaVariants({ resize: 'none' })
84
+ : textareaVariants({ resize: this.resize() }), ...(ngDevMode ? [{ debugName: "textareaClasses" }] : /* istanbul ignore next */ []));
85
+ constructor() {
86
+ super();
87
+ // Wire the autosize toggle. The CDK directive is always mounted via
88
+ // hostDirectives; we just flip its `enabled` setter based on our input.
89
+ effect(() => {
90
+ if (this.cdkAutosize) {
91
+ this.cdkAutosize.enabled = this.autosize();
92
+ }
93
+ });
94
+ }
95
+ /** Triggers a CDK autosize recalculation. Useful after programmatic value changes that bypass the native `(input)` event (e.g., clipboard write APIs). No-op when `autosize` is `false`. */
96
+ resizeToFitContent(force = false) {
97
+ if (this.cdkAutosize && this.autosize()) {
98
+ this.cdkAutosize.resizeToFitContent(force);
99
+ }
100
+ }
101
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: TextareaDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
102
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.14", type: TextareaDirective, isStandalone: true, selector: "textarea[twTextarea]", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, autosize: { classPropertyName: "autosize", publicName: "autosize", isSignal: true, isRequired: false, transformFunction: null }, minRows: { classPropertyName: "minRows", publicName: "minRows", isSignal: true, isRequired: false, transformFunction: null }, maxRows: { classPropertyName: "maxRows", publicName: "maxRows", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, resize: { classPropertyName: "resize", publicName: "resize", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "input": "_onInput()" }, properties: { "class": "classes() + \" \" + textareaClasses()", "attr.id": "id()", "attr.rows": "rows()", "attr.maxlength": "maxLength() ?? null", "disabled": "disabled()", "attr.aria-invalid": "errorState() || null", "attr.aria-required": "required() || null" } }, providers: [
103
+ {
104
+ provide: TW_FORM_FIELD_CONTROL,
105
+ useExisting: forwardRef(() => TextareaDirective),
106
+ },
107
+ ], exportAs: ["twTextarea"], usesInheritance: true, hostDirectives: [{ directive: i1.CdkTextareaAutosize, inputs: ["cdkAutosizeMinRows", "minRows", "cdkAutosizeMaxRows", "maxRows"] }], ngImport: i0 });
108
+ }
109
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: TextareaDirective, decorators: [{
110
+ type: Directive,
111
+ args: [{
112
+ selector: 'textarea[twTextarea]',
113
+ exportAs: 'twTextarea',
114
+ hostDirectives: [
115
+ {
116
+ directive: CdkTextareaAutosize,
117
+ inputs: [
118
+ 'cdkAutosizeMinRows: minRows',
119
+ 'cdkAutosizeMaxRows: maxRows',
120
+ ],
121
+ },
122
+ ],
123
+ providers: [
124
+ {
125
+ provide: TW_FORM_FIELD_CONTROL,
126
+ useExisting: forwardRef(() => TextareaDirective),
127
+ },
128
+ ],
129
+ host: {
130
+ '[class]': 'classes() + " " + textareaClasses()',
131
+ '[attr.id]': 'id()',
132
+ '[attr.rows]': 'rows()',
133
+ '[attr.maxlength]': 'maxLength() ?? null',
134
+ '[disabled]': 'disabled()',
135
+ '[attr.aria-invalid]': 'errorState() || null',
136
+ '[attr.aria-required]': 'required() || null',
137
+ '(input)': '_onInput()',
138
+ },
139
+ }]
140
+ }], ctorParameters: () => [], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], autosize: [{ type: i0.Input, args: [{ isSignal: true, alias: "autosize", required: false }] }], minRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "minRows", required: false }] }], maxRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxRows", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], resize: [{ type: i0.Input, args: [{ isSignal: true, alias: "resize", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }] } });
141
+
142
+ /**
143
+ * Generated bundle index. Do not edit.
144
+ */
145
+
146
+ export { TextareaDirective };
147
+ //# sourceMappingURL=cdevhub-ngx-tw-textarea.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cdevhub-ngx-tw-textarea.mjs","sources":["../../../projects/ngx-tw/textarea/textarea.ts","../../../projects/ngx-tw/textarea/cdevhub-ngx-tw-textarea.ts"],"sourcesContent":["import {\n booleanAttribute,\n computed,\n Directive,\n effect,\n forwardRef,\n inject,\n input,\n numberAttribute,\n type Signal,\n} from '@angular/core';\nimport { CdkTextareaAutosize } from '@angular/cdk/text-field';\nimport { tv } from 'tailwind-variants';\nimport type { TwSize } from '@cdevhub/ngx-tw/core';\nimport { TW_FORM_FIELD_CONTROL } from '@cdevhub/ngx-tw/form-field';\nimport { InputDirective } from '@cdevhub/ngx-tw/input';\n\n/**\n * How the user-resize handle behaves on the textarea. `'vertical'` (the\n * default) lets users drag the bottom edge to grow the textarea; `'none'`\n * locks the size; `'both'` allows free resizing on both axes (rarely useful\n * inside a form-field — the textarea can overflow the wrapper). Horizontal\n * is intentionally omitted — it breaks form-field layout in practice.\n */\nexport type TwTextareaResize = 'none' | 'vertical' | 'both';\n\nconst textareaVariants = tv(\n {\n base: '',\n variants: {\n resize: {\n none: 'resize-none',\n vertical: 'resize-y',\n both: 'resize',\n },\n },\n defaultVariants: {\n resize: 'vertical',\n },\n },\n { twMerge: true },\n);\n\n/**\n * Adapts a native `<textarea>` into an ngx-tw form-field-compatible multi-line\n * control. Extends {@link InputDirective} — inherits its form-field\n * integration, error-state machinery, autofill / focus tracking, ARIA wiring,\n * and standalone styling. Adds textarea-specific behavior: autosize composition\n * via CDK's `CdkTextareaAutosize`, a `resize` axis for the user-resize handle,\n * `rows` / `minRows` / `maxRows`, and `maxLength` with a `valueLength` signal\n * for character counters.\n *\n * Like `InputDirective`, this directive deliberately does NOT implement\n * `ControlValueAccessor`. Angular's built-in `DefaultValueAccessor` attaches to\n * the native `<textarea>` and handles value I/O for template-driven\n * (`ngModel`), reactive (`FormControl` / `formControlName`), and signal-forms\n * (`formField`) bindings — no library glue required.\n *\n * Form-control input-cap exception applies (codified in CLAUDE.md): ARIA +\n * forms baseline plus textarea-specific surface exceeds the 5–6 cap;\n * `checkbox` is the canonical 12+ exemplar.\n */\n@Directive({\n selector: 'textarea[twTextarea]',\n exportAs: 'twTextarea',\n hostDirectives: [\n {\n directive: CdkTextareaAutosize,\n inputs: [\n 'cdkAutosizeMinRows: minRows',\n 'cdkAutosizeMaxRows: maxRows',\n ],\n },\n ],\n providers: [\n {\n provide: TW_FORM_FIELD_CONTROL,\n useExisting: forwardRef(() => TextareaDirective),\n },\n ],\n host: {\n '[class]': 'classes() + \" \" + textareaClasses()',\n '[attr.id]': 'id()',\n '[attr.rows]': 'rows()',\n '[attr.maxlength]': 'maxLength() ?? null',\n '[disabled]': 'disabled()',\n '[attr.aria-invalid]': 'errorState() || null',\n '[attr.aria-required]': 'required() || null',\n '(input)': '_onInput()',\n },\n})\nexport class TextareaDirective extends InputDirective {\n private readonly cdkAutosize = inject(CdkTextareaAutosize, {\n self: true,\n optional: true,\n });\n\n // @internal Re-declares the `size` input inherited from `InputDirective` so\n // it lands on this directive's own `ɵdir` input metadata. ng-packagr emits\n // `ɵdir` with only the child's directly declared inputs, so without this the\n // consumer-side strict template-check rejects `<textarea twTextarea\n // [size]=\"…\">` with NG8002. Both signals share the same `'md'` default and\n // the inherited `classes()` computed reads through polymorphism, so runtime\n // behaviour is unchanged.\n //\n // The `@ts-ignore` is needed for a CI-only quirk: under\n // `noImplicitOverride`, partial compilation resolves `InputDirective`\n // through the just-built `dist/` d.ts on CI, where TypeScript decides the\n // base member isn't visible and rejects `override` with TS4113. Locally\n // the same resolution sees the member, so `@ts-expect-error` would itself\n // flag as unused. `@ts-ignore` suppresses whichever error fires and is\n // a no-op when neither does.\n /** Density of a standalone textarea. Maps to the inline-padding + font scale (`xs` … `xl`). Ignored inside a `<tw-form-field>` — the wrapper's `size` carries density. Defaults to `'md'`. */\n // @ts-ignore — TS4113 (CI) vs TS4114 (local); see comment above.\n override readonly size = input<TwSize>('md');\n\n /** Grows the textarea with its content (composed from CDK's `CdkTextareaAutosize`). When `true` the user-resize handle is forced off — autosize owns the height. Defaults to `false`. */\n readonly autosize = input<boolean, unknown>(false, {\n transform: booleanAttribute,\n });\n\n /** Minimum number of rows the textarea collapses to when `autosize` is `true`. Ignored when autosize is off. Defaults to `1`. */\n readonly minRows = input<number, unknown>(1, { transform: numberAttribute });\n\n /** Maximum number of rows the textarea expands to before scrolling, when `autosize` is `true`. `undefined` removes the cap. Ignored when autosize is off. Defaults to `undefined`. */\n readonly maxRows = input<number | undefined, unknown>(undefined, {\n transform: (v) => (v === undefined || v === null || v === '' ? undefined : numberAttribute(v)),\n });\n\n /** Number of rows for the initial render height (native `rows` attribute). Browsers honor this even when `autosize` is `true`, so first paint uses this value. Defaults to `3`. */\n readonly rows = input<number, unknown>(3, { transform: numberAttribute });\n\n /** Controls the user-resize handle: `'none'` locks the size, `'vertical'` (default) allows vertical drag, `'both'` allows both axes. Forced to `'none'` when `autosize` is `true`. Horizontal-only is intentionally not supported. */\n readonly resize = input<TwTextareaResize>('vertical');\n\n /** Maximum character count. Mirrors to the native `maxlength` attribute when defined and is exposed via the `valueLength` signal so consumers can render a \"X / N\" hint. Defaults to `undefined`. */\n readonly maxLength = input<number | undefined, unknown>(undefined, {\n transform: (v) => (v === undefined || v === null || v === '' ? undefined : numberAttribute(v)),\n });\n\n /** Current value length, updates on every `input` event. Wire `<span twHint align=\"end\">{{ ta.valueLength() }} / {{ ta.maxLength() }}</span>` for a character counter. */\n readonly valueLength: Signal<number> = computed(() => this.value()?.length ?? 0);\n\n /** @internal Textarea-specific Tailwind classes (resize axis). Combined with the inherited `classes()` in the host `[class]` binding. */\n readonly textareaClasses = computed(() =>\n // Short-circuit the resize/autosize conflict deterministically — don't\n // rely on tv variant emission order or twMerge \"last wins\" semantics.\n this.autosize()\n ? textareaVariants({ resize: 'none' })\n : textareaVariants({ resize: this.resize() }),\n );\n\n constructor() {\n super();\n\n // Wire the autosize toggle. The CDK directive is always mounted via\n // hostDirectives; we just flip its `enabled` setter based on our input.\n effect(() => {\n if (this.cdkAutosize) {\n this.cdkAutosize.enabled = this.autosize();\n }\n });\n }\n\n /** Triggers a CDK autosize recalculation. Useful after programmatic value changes that bypass the native `(input)` event (e.g., clipboard write APIs). No-op when `autosize` is `false`. */\n resizeToFitContent(force = false): void {\n if (this.cdkAutosize && this.autosize()) {\n this.cdkAutosize.resizeToFitContent(force);\n }\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,178 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, makeEnvironmentProviders, inject, PLATFORM_ID, NgZone, signal, computed, effect, Injectable, input, Directive } from '@angular/core';
3
+ import { DOCUMENT, isPlatformBrowser } from '@angular/common';
4
+
5
+ /** Ordered list of every {@link TwTheme} value, used by `cycleTheme()` and for UI iteration. */
6
+ const TW_THEMES = ['light', 'dark', 'high-contrast', 'system'];
7
+ /** Ordered list of every {@link TwResolvedTheme} value (i.e. {@link TW_THEMES} minus `'system'`). */
8
+ const TW_RESOLVED_THEMES = ['light', 'dark', 'high-contrast'];
9
+ /** Built-in defaults merged under any user-provided {@link TwThemeConfig} by `provideTheme()`. */
10
+ const DEFAULT_TW_THEME_CONFIG = {
11
+ defaultTheme: 'system',
12
+ storageKey: 'ngx-tw-theme',
13
+ attribute: 'data-theme',
14
+ target: 'documentElement',
15
+ };
16
+
17
+ /** Injection token carrying the resolved {@link TwThemeConfig} (storage key, attribute, target element, default theme). */
18
+ const THEME_CONFIG = new InjectionToken('THEME_CONFIG');
19
+ /**
20
+ * Registers {@link ThemeService} and a {@link THEME_CONFIG} value built by
21
+ * merging `config` over {@link DEFAULT_TW_THEME_CONFIG}. Call once in the
22
+ * app's environment providers.
23
+ */
24
+ function provideTheme(config) {
25
+ return makeEnvironmentProviders([
26
+ {
27
+ provide: THEME_CONFIG,
28
+ useValue: { ...DEFAULT_TW_THEME_CONFIG, ...config },
29
+ },
30
+ ThemeService,
31
+ ]);
32
+ }
33
+
34
+ /**
35
+ * Stateful runtime service that owns the active theme, reacts to OS
36
+ * `prefers-color-scheme` changes, persists the user selection to
37
+ * `localStorage`, and writes the resolved theme onto the configured DOM
38
+ * target as a `data-theme` attribute.
39
+ *
40
+ * The selected {@link theme} may be `'system'` (defer to the OS); the
41
+ * {@link resolvedTheme} computed from it is always one of `'light'`,
42
+ * `'dark'`, or `'high-contrast'` — never `'system'`. Register via
43
+ * {@link provideTheme} in the app's environment providers.
44
+ */
45
+ class ThemeService {
46
+ config = inject(THEME_CONFIG);
47
+ document = inject(DOCUMENT);
48
+ isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
49
+ ngZone = inject(NgZone);
50
+ mediaQuery = null;
51
+ mediaListener = (e) => {
52
+ this.systemTheme.set(e.matches ? 'dark' : 'light');
53
+ };
54
+ /** The user-selected theme (may be `'system'`). */
55
+ theme = signal(this.loadInitialTheme(), ...(ngDevMode ? [{ debugName: "theme" }] : /* istanbul ignore next */ []));
56
+ /** The OS color scheme preference. */
57
+ systemTheme = signal(this.detectSystemTheme(), ...(ngDevMode ? [{ debugName: "systemTheme" }] : /* istanbul ignore next */ []));
58
+ /** The resolved theme actually applied to the DOM (never `'system'`). */
59
+ resolvedTheme = computed(() => {
60
+ const t = this.theme();
61
+ return t === 'system' ? this.systemTheme() : t;
62
+ }, ...(ngDevMode ? [{ debugName: "resolvedTheme" }] : /* istanbul ignore next */ []));
63
+ /** True when the resolved theme is `'dark'`. */
64
+ isDark = computed(() => this.resolvedTheme() === 'dark', ...(ngDevMode ? [{ debugName: "isDark" }] : /* istanbul ignore next */ []));
65
+ /** True when the resolved theme is `'light'`. */
66
+ isLight = computed(() => this.resolvedTheme() === 'light', ...(ngDevMode ? [{ debugName: "isLight" }] : /* istanbul ignore next */ []));
67
+ /** True when the resolved theme is `'high-contrast'`. */
68
+ isHighContrast = computed(() => this.resolvedTheme() === 'high-contrast', ...(ngDevMode ? [{ debugName: "isHighContrast" }] : /* istanbul ignore next */ []));
69
+ /** Snapshot of the full theme state — selected, resolved, system, and boolean flags. */
70
+ state = computed(() => ({
71
+ theme: this.theme(),
72
+ resolvedTheme: this.resolvedTheme(),
73
+ systemTheme: this.systemTheme(),
74
+ isDark: this.isDark(),
75
+ isLight: this.isLight(),
76
+ isHighContrast: this.isHighContrast(),
77
+ }), ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
78
+ constructor() {
79
+ if (this.isBrowser) {
80
+ // Register the media-query listener outside the Angular zone — it fires
81
+ // on OS theme changes, not user interaction, and we don't want zone.js
82
+ // bookkeeping for every system colour-scheme tick. The listener calls
83
+ // `signal.set()`, which schedules its own change detection.
84
+ this.ngZone.runOutsideAngular(() => {
85
+ this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
86
+ this.mediaQuery.addEventListener('change', this.mediaListener);
87
+ });
88
+ }
89
+ effect(() => {
90
+ const resolved = this.resolvedTheme();
91
+ const selected = this.theme();
92
+ this.applyToDocument(resolved);
93
+ this.persistTheme(selected);
94
+ });
95
+ }
96
+ /** Sets the selected theme. Pass `'system'` to follow the OS preference. */
97
+ setTheme(theme) {
98
+ this.theme.set(theme);
99
+ }
100
+ /** Advances the selected theme to the next entry in {@link TW_THEMES}, wrapping around. */
101
+ cycleTheme() {
102
+ const current = this.theme();
103
+ const idx = TW_THEMES.indexOf(current);
104
+ this.theme.set(TW_THEMES[(idx + 1) % TW_THEMES.length]);
105
+ }
106
+ /** Writes the configured theme attribute onto an arbitrary element — used to scope themes to a subtree. */
107
+ applyToElement(element, theme) {
108
+ element.setAttribute(this.config.attribute, theme);
109
+ }
110
+ ngOnDestroy() {
111
+ this.mediaQuery?.removeEventListener('change', this.mediaListener);
112
+ }
113
+ loadInitialTheme() {
114
+ if (!this.isBrowser)
115
+ return this.config.defaultTheme;
116
+ try {
117
+ const stored = localStorage.getItem(this.config.storageKey);
118
+ if (stored && TW_THEMES.includes(stored)) {
119
+ return stored;
120
+ }
121
+ }
122
+ catch {
123
+ /* localStorage unavailable */
124
+ }
125
+ return this.config.defaultTheme;
126
+ }
127
+ detectSystemTheme() {
128
+ if (!this.isBrowser)
129
+ return 'light';
130
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
131
+ }
132
+ applyToDocument(theme) {
133
+ if (!this.isBrowser)
134
+ return;
135
+ const target = this.config.target === 'documentElement'
136
+ ? this.document.documentElement
137
+ : this.document.body;
138
+ target.setAttribute(this.config.attribute, theme);
139
+ }
140
+ persistTheme(theme) {
141
+ if (!this.isBrowser)
142
+ return;
143
+ try {
144
+ localStorage.setItem(this.config.storageKey, theme);
145
+ }
146
+ catch {
147
+ /* localStorage unavailable */
148
+ }
149
+ }
150
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
151
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ThemeService });
152
+ }
153
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ThemeService, decorators: [{
154
+ type: Injectable
155
+ }], ctorParameters: () => [] });
156
+
157
+ class ThemeDirective {
158
+ /** Scopes a subtree to a specific resolved theme by writing `data-theme` on the host. Required. */
159
+ twTheme = input.required(...(ngDevMode ? [{ debugName: "twTheme" }] : /* istanbul ignore next */ []));
160
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ThemeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
161
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.14", type: ThemeDirective, isStandalone: true, selector: "[twTheme]", inputs: { twTheme: { classPropertyName: "twTheme", publicName: "twTheme", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-theme": "twTheme()" } }, ngImport: i0 });
162
+ }
163
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ThemeDirective, decorators: [{
164
+ type: Directive,
165
+ args: [{
166
+ selector: '[twTheme]',
167
+ host: {
168
+ '[attr.data-theme]': 'twTheme()',
169
+ },
170
+ }]
171
+ }], propDecorators: { twTheme: [{ type: i0.Input, args: [{ isSignal: true, alias: "twTheme", required: true }] }] } });
172
+
173
+ /**
174
+ * Generated bundle index. Do not edit.
175
+ */
176
+
177
+ export { DEFAULT_TW_THEME_CONFIG, THEME_CONFIG, TW_RESOLVED_THEMES, TW_THEMES, ThemeDirective, ThemeService, provideTheme };
178
+ //# sourceMappingURL=cdevhub-ngx-tw-theme.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cdevhub-ngx-tw-theme.mjs","sources":["../../../projects/ngx-tw/theme/theme.types.ts","../../../projects/ngx-tw/theme/theme.config.ts","../../../projects/ngx-tw/theme/theme.service.ts","../../../projects/ngx-tw/theme/theme.directive.ts","../../../projects/ngx-tw/theme/cdevhub-ngx-tw-theme.ts"],"sourcesContent":["/** The user-selectable theme. `'system'` defers to the OS `prefers-color-scheme` setting. */\nexport type TwTheme = 'light' | 'dark' | 'high-contrast' | 'system';\n\n/** The theme actually applied to the DOM after resolving `'system'` against the OS preference. */\nexport type TwResolvedTheme = 'light' | 'dark' | 'high-contrast';\n\n/** Ordered list of every {@link TwTheme} value, used by `cycleTheme()` and for UI iteration. */\nexport const TW_THEMES = ['light', 'dark', 'high-contrast', 'system'] as const satisfies readonly TwTheme[];\n\n/** Ordered list of every {@link TwResolvedTheme} value (i.e. {@link TW_THEMES} minus `'system'`). */\nexport const TW_RESOLVED_THEMES = ['light', 'dark', 'high-contrast'] as const satisfies readonly TwResolvedTheme[];\n\n/** Runtime configuration for {@link provideTheme}; controls storage, attribute name, target element, and default. */\nexport interface TwThemeConfig {\n /** The default theme when no preference is stored. Defaults to `'system'`. */\n defaultTheme: TwTheme;\n /** localStorage key for persisting theme preference. Defaults to `'ngx-tw-theme'`. */\n storageKey: string;\n /** The HTML attribute written to the target element. Defaults to `'data-theme'`. */\n attribute: string;\n /** Which element receives the theme attribute. Defaults to `'documentElement'`. */\n target: 'documentElement' | 'body';\n}\n\n/** Composite snapshot of `ThemeService` state — selected, resolved, system, and boolean flags. */\nexport interface TwThemeState {\n /** The user-selected theme — may be `'system'`. */\n readonly theme: TwTheme;\n /** The theme actually applied to the DOM — never `'system'`. */\n readonly resolvedTheme: TwResolvedTheme;\n /** The OS color-scheme preference detected via `prefers-color-scheme`. */\n readonly systemTheme: TwResolvedTheme;\n /** True when {@link resolvedTheme} is `'dark'`. */\n readonly isDark: boolean;\n /** True when {@link resolvedTheme} is `'light'`. */\n readonly isLight: boolean;\n /** True when {@link resolvedTheme} is `'high-contrast'`. */\n readonly isHighContrast: boolean;\n}\n\n/** Built-in defaults merged under any user-provided {@link TwThemeConfig} by `provideTheme()`. */\nexport const DEFAULT_TW_THEME_CONFIG: TwThemeConfig = {\n defaultTheme: 'system',\n storageKey: 'ngx-tw-theme',\n attribute: 'data-theme',\n target: 'documentElement',\n};\n","import { InjectionToken, makeEnvironmentProviders, type EnvironmentProviders } from '@angular/core';\nimport { type TwThemeConfig, DEFAULT_TW_THEME_CONFIG } from './theme.types';\nimport { ThemeService } from './theme.service';\n\n/** Injection token carrying the resolved {@link TwThemeConfig} (storage key, attribute, target element, default theme). */\nexport const THEME_CONFIG = new InjectionToken<TwThemeConfig>('THEME_CONFIG');\n\n/**\n * Registers {@link ThemeService} and a {@link THEME_CONFIG} value built by\n * merging `config` over {@link DEFAULT_TW_THEME_CONFIG}. Call once in the\n * app's environment providers.\n */\nexport function provideTheme(config?: Partial<TwThemeConfig>): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: THEME_CONFIG,\n useValue: { ...DEFAULT_TW_THEME_CONFIG, ...config },\n },\n ThemeService,\n ]);\n}\n","import {\n Injectable,\n NgZone,\n computed,\n effect,\n inject,\n signal,\n PLATFORM_ID,\n type OnDestroy,\n} from '@angular/core';\nimport { DOCUMENT, isPlatformBrowser } from '@angular/common';\nimport { THEME_CONFIG } from './theme.config';\nimport { TW_THEMES, type TwTheme, type TwResolvedTheme, type TwThemeState } from './theme.types';\n\n/**\n * Stateful runtime service that owns the active theme, reacts to OS\n * `prefers-color-scheme` changes, persists the user selection to\n * `localStorage`, and writes the resolved theme onto the configured DOM\n * target as a `data-theme` attribute.\n *\n * The selected {@link theme} may be `'system'` (defer to the OS); the\n * {@link resolvedTheme} computed from it is always one of `'light'`,\n * `'dark'`, or `'high-contrast'` — never `'system'`. Register via\n * {@link provideTheme} in the app's environment providers.\n */\n@Injectable()\nexport class ThemeService implements OnDestroy {\n private readonly config = inject(THEME_CONFIG);\n private readonly document = inject(DOCUMENT);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly ngZone = inject(NgZone);\n\n private mediaQuery: MediaQueryList | null = null;\n private readonly mediaListener = (e: MediaQueryListEvent) => {\n this.systemTheme.set(e.matches ? 'dark' : 'light');\n };\n\n /** The user-selected theme (may be `'system'`). */\n readonly theme = signal<TwTheme>(this.loadInitialTheme());\n\n /** The OS color scheme preference. */\n readonly systemTheme = signal<TwResolvedTheme>(this.detectSystemTheme());\n\n /** The resolved theme actually applied to the DOM (never `'system'`). */\n readonly resolvedTheme = computed<TwResolvedTheme>(() => {\n const t = this.theme();\n return t === 'system' ? this.systemTheme() : t;\n });\n\n /** True when the resolved theme is `'dark'`. */\n readonly isDark = computed(() => this.resolvedTheme() === 'dark');\n /** True when the resolved theme is `'light'`. */\n readonly isLight = computed(() => this.resolvedTheme() === 'light');\n /** True when the resolved theme is `'high-contrast'`. */\n readonly isHighContrast = computed(() => this.resolvedTheme() === 'high-contrast');\n\n /** Snapshot of the full theme state — selected, resolved, system, and boolean flags. */\n readonly state = computed<TwThemeState>(() => ({\n theme: this.theme(),\n resolvedTheme: this.resolvedTheme(),\n systemTheme: this.systemTheme(),\n isDark: this.isDark(),\n isLight: this.isLight(),\n isHighContrast: this.isHighContrast(),\n }));\n\n constructor() {\n if (this.isBrowser) {\n // Register the media-query listener outside the Angular zone — it fires\n // on OS theme changes, not user interaction, and we don't want zone.js\n // bookkeeping for every system colour-scheme tick. The listener calls\n // `signal.set()`, which schedules its own change detection.\n this.ngZone.runOutsideAngular(() => {\n this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n this.mediaQuery.addEventListener('change', this.mediaListener);\n });\n }\n\n effect(() => {\n const resolved = this.resolvedTheme();\n const selected = this.theme();\n this.applyToDocument(resolved);\n this.persistTheme(selected);\n });\n }\n\n /** Sets the selected theme. Pass `'system'` to follow the OS preference. */\n setTheme(theme: TwTheme): void {\n this.theme.set(theme);\n }\n\n /** Advances the selected theme to the next entry in {@link TW_THEMES}, wrapping around. */\n cycleTheme(): void {\n const current = this.theme();\n const idx = TW_THEMES.indexOf(current);\n this.theme.set(TW_THEMES[(idx + 1) % TW_THEMES.length]);\n }\n\n /** Writes the configured theme attribute onto an arbitrary element — used to scope themes to a subtree. */\n applyToElement(element: HTMLElement, theme: TwResolvedTheme): void {\n element.setAttribute(this.config.attribute, theme);\n }\n\n ngOnDestroy(): void {\n this.mediaQuery?.removeEventListener('change', this.mediaListener);\n }\n\n private loadInitialTheme(): TwTheme {\n if (!this.isBrowser) return this.config.defaultTheme;\n try {\n const stored = localStorage.getItem(this.config.storageKey);\n if (stored && (TW_THEMES as readonly string[]).includes(stored)) {\n return stored as TwTheme;\n }\n } catch {\n /* localStorage unavailable */\n }\n return this.config.defaultTheme;\n }\n\n private detectSystemTheme(): TwResolvedTheme {\n if (!this.isBrowser) return 'light';\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n }\n\n private applyToDocument(theme: TwResolvedTheme): void {\n if (!this.isBrowser) return;\n const target =\n this.config.target === 'documentElement'\n ? this.document.documentElement\n : this.document.body;\n target.setAttribute(this.config.attribute, theme);\n }\n\n private persistTheme(theme: TwTheme): void {\n if (!this.isBrowser) return;\n try {\n localStorage.setItem(this.config.storageKey, theme);\n } catch {\n /* localStorage unavailable */\n }\n }\n}\n","import { Directive, input } from '@angular/core';\nimport type { TwResolvedTheme } from './theme.types';\n\n@Directive({\n selector: '[twTheme]',\n host: {\n '[attr.data-theme]': 'twTheme()',\n },\n})\nexport class ThemeDirective {\n /** Scopes a subtree to a specific resolved theme by writing `data-theme` on the host. Required. */\n readonly twTheme = input.required<TwResolvedTheme>();\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}