forty-cdk 0.7.0 → 0.8.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 (158) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +3 -3
  3. package/aspect-ratio/README.md +19 -0
  4. package/avatar/README.md +3 -3
  5. package/breadcrumbs/README.md +12 -0
  6. package/calendar/README.md +20 -17
  7. package/date-picker/README.md +22 -63
  8. package/dialog/README.md +1 -1
  9. package/drag-drop/README.md +2 -2
  10. package/fesm2022/forty-cdk-accordion.mjs +2 -15
  11. package/fesm2022/forty-cdk-accordion.mjs.map +1 -1
  12. package/fesm2022/forty-cdk-aspect-ratio.mjs +11 -15
  13. package/fesm2022/forty-cdk-aspect-ratio.mjs.map +1 -1
  14. package/fesm2022/forty-cdk-avatar.mjs +8 -4
  15. package/fesm2022/forty-cdk-avatar.mjs.map +1 -1
  16. package/fesm2022/forty-cdk-breadcrumbs.mjs +32 -21
  17. package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -1
  18. package/fesm2022/forty-cdk-button.mjs +253 -20
  19. package/fesm2022/forty-cdk-button.mjs.map +1 -1
  20. package/fesm2022/forty-cdk-calendar.mjs +109 -76
  21. package/fesm2022/forty-cdk-calendar.mjs.map +1 -1
  22. package/fesm2022/forty-cdk-checkbox.mjs +2 -15
  23. package/fesm2022/forty-cdk-checkbox.mjs.map +1 -1
  24. package/fesm2022/forty-cdk-combobox.mjs +124 -97
  25. package/fesm2022/forty-cdk-combobox.mjs.map +1 -1
  26. package/fesm2022/forty-cdk-core.mjs +1446 -1174
  27. package/fesm2022/forty-cdk-core.mjs.map +1 -1
  28. package/fesm2022/forty-cdk-date-field.mjs +3 -0
  29. package/fesm2022/forty-cdk-date-field.mjs.map +1 -1
  30. package/fesm2022/forty-cdk-date-picker.mjs +35 -70
  31. package/fesm2022/forty-cdk-date-picker.mjs.map +1 -1
  32. package/fesm2022/forty-cdk-date-range-field.mjs +25 -106
  33. package/fesm2022/forty-cdk-date-range-field.mjs.map +1 -1
  34. package/fesm2022/forty-cdk-dialog.mjs +31 -17
  35. package/fesm2022/forty-cdk-dialog.mjs.map +1 -1
  36. package/fesm2022/forty-cdk-disclosure.mjs +2 -15
  37. package/fesm2022/forty-cdk-disclosure.mjs.map +1 -1
  38. package/fesm2022/forty-cdk-drag-drop.mjs +81 -15
  39. package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -1
  40. package/fesm2022/forty-cdk-drawer.mjs +37 -23
  41. package/fesm2022/forty-cdk-drawer.mjs.map +1 -1
  42. package/fesm2022/forty-cdk-field.mjs +2 -15
  43. package/fesm2022/forty-cdk-field.mjs.map +1 -1
  44. package/fesm2022/forty-cdk-fieldset.mjs +2 -15
  45. package/fesm2022/forty-cdk-fieldset.mjs.map +1 -1
  46. package/fesm2022/forty-cdk-file-upload.mjs +44 -46
  47. package/fesm2022/forty-cdk-file-upload.mjs.map +1 -1
  48. package/fesm2022/forty-cdk-input.mjs +2 -15
  49. package/fesm2022/forty-cdk-input.mjs.map +1 -1
  50. package/fesm2022/forty-cdk-internationalized-date.mjs +10 -8
  51. package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
  52. package/fesm2022/forty-cdk-listbox.mjs +87 -3
  53. package/fesm2022/forty-cdk-listbox.mjs.map +1 -1
  54. package/fesm2022/forty-cdk-menu.mjs.map +1 -1
  55. package/fesm2022/forty-cdk-menubar.mjs.map +1 -1
  56. package/fesm2022/forty-cdk-meter.mjs +31 -29
  57. package/fesm2022/forty-cdk-meter.mjs.map +1 -1
  58. package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -1
  59. package/fesm2022/forty-cdk-number-input.mjs +1 -1
  60. package/fesm2022/forty-cdk-number-input.mjs.map +1 -1
  61. package/fesm2022/forty-cdk-otp-input.mjs +4 -17
  62. package/fesm2022/forty-cdk-otp-input.mjs.map +1 -1
  63. package/fesm2022/forty-cdk-pane-resizer.mjs +2 -15
  64. package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -1
  65. package/fesm2022/forty-cdk-popover.mjs +8 -1
  66. package/fesm2022/forty-cdk-popover.mjs.map +1 -1
  67. package/fesm2022/forty-cdk-progress.mjs +25 -14
  68. package/fesm2022/forty-cdk-progress.mjs.map +1 -1
  69. package/fesm2022/forty-cdk-radio-group.mjs +6 -5
  70. package/fesm2022/forty-cdk-radio-group.mjs.map +1 -1
  71. package/fesm2022/forty-cdk-search.mjs +136 -38
  72. package/fesm2022/forty-cdk-search.mjs.map +1 -1
  73. package/fesm2022/forty-cdk-select.mjs +308 -162
  74. package/fesm2022/forty-cdk-select.mjs.map +1 -1
  75. package/fesm2022/forty-cdk-separator.mjs +1 -15
  76. package/fesm2022/forty-cdk-separator.mjs.map +1 -1
  77. package/fesm2022/forty-cdk-slider.mjs +79 -90
  78. package/fesm2022/forty-cdk-slider.mjs.map +1 -1
  79. package/fesm2022/forty-cdk-stepper.mjs +13 -6
  80. package/fesm2022/forty-cdk-stepper.mjs.map +1 -1
  81. package/fesm2022/forty-cdk-switch.mjs +2 -15
  82. package/fesm2022/forty-cdk-switch.mjs.map +1 -1
  83. package/fesm2022/forty-cdk-table.mjs +244 -84
  84. package/fesm2022/forty-cdk-table.mjs.map +1 -1
  85. package/fesm2022/forty-cdk-time-field.mjs +3 -0
  86. package/fesm2022/forty-cdk-time-field.mjs.map +1 -1
  87. package/fesm2022/forty-cdk-time-picker.mjs +61 -129
  88. package/fesm2022/forty-cdk-time-picker.mjs.map +1 -1
  89. package/fesm2022/forty-cdk-time-range-field.mjs +64 -111
  90. package/fesm2022/forty-cdk-time-range-field.mjs.map +1 -1
  91. package/fesm2022/forty-cdk-toggle.mjs +5 -6
  92. package/fesm2022/forty-cdk-toggle.mjs.map +1 -1
  93. package/fesm2022/forty-cdk-toolbar.mjs +10 -3
  94. package/fesm2022/forty-cdk-toolbar.mjs.map +1 -1
  95. package/fesm2022/forty-cdk-tree.mjs +36 -19
  96. package/fesm2022/forty-cdk-tree.mjs.map +1 -1
  97. package/fesm2022/forty-cdk-virtualization.mjs +34 -18
  98. package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
  99. package/file-upload/README.md +4 -4
  100. package/internationalized-date/README.md +23 -23
  101. package/listbox/README.md +1 -1
  102. package/meter/README.md +12 -10
  103. package/package.json +1 -1
  104. package/progress/README.md +3 -1
  105. package/radio-group/README.md +2 -2
  106. package/search/README.md +34 -15
  107. package/select/README.md +7 -1
  108. package/signal-forms/README.md +1 -1
  109. package/stepper/README.md +13 -0
  110. package/table/README.md +21 -28
  111. package/time-range-field/README.md +17 -12
  112. package/toggle/README.md +1 -1
  113. package/toolbar/README.md +4 -4
  114. package/tree/README.md +11 -9
  115. package/types/forty-cdk-accordion.d.ts +3 -20
  116. package/types/forty-cdk-aspect-ratio.d.ts +11 -20
  117. package/types/forty-cdk-avatar.d.ts +2 -2
  118. package/types/forty-cdk-breadcrumbs.d.ts +18 -8
  119. package/types/forty-cdk-button.d.ts +6 -22
  120. package/types/forty-cdk-calendar.d.ts +32 -9
  121. package/types/forty-cdk-checkbox.d.ts +2 -20
  122. package/types/forty-cdk-combobox.d.ts +66 -9
  123. package/types/forty-cdk-core.d.ts +655 -307
  124. package/types/forty-cdk-date-field.d.ts +1 -0
  125. package/types/forty-cdk-date-picker.d.ts +21 -31
  126. package/types/forty-cdk-date-range-field.d.ts +1 -0
  127. package/types/forty-cdk-dialog.d.ts +21 -7
  128. package/types/forty-cdk-disclosure.d.ts +3 -20
  129. package/types/forty-cdk-drag-drop.d.ts +42 -7
  130. package/types/forty-cdk-drawer.d.ts +21 -6
  131. package/types/forty-cdk-field.d.ts +1 -20
  132. package/types/forty-cdk-fieldset.d.ts +1 -20
  133. package/types/forty-cdk-file-upload.d.ts +31 -34
  134. package/types/forty-cdk-input.d.ts +1 -20
  135. package/types/forty-cdk-internationalized-date.d.ts +8 -6
  136. package/types/forty-cdk-listbox.d.ts +34 -2
  137. package/types/forty-cdk-menu.d.ts +6 -2
  138. package/types/forty-cdk-menubar.d.ts +6 -7
  139. package/types/forty-cdk-meter.d.ts +29 -27
  140. package/types/forty-cdk-navigation-menu.d.ts +7 -2
  141. package/types/forty-cdk-otp-input.d.ts +6 -23
  142. package/types/forty-cdk-pane-resizer.d.ts +1 -20
  143. package/types/forty-cdk-popover.d.ts +18 -2
  144. package/types/forty-cdk-progress.d.ts +31 -11
  145. package/types/forty-cdk-radio-group.d.ts +8 -7
  146. package/types/forty-cdk-search.d.ts +137 -34
  147. package/types/forty-cdk-select.d.ts +122 -125
  148. package/types/forty-cdk-separator.d.ts +1 -20
  149. package/types/forty-cdk-slider.d.ts +7 -17
  150. package/types/forty-cdk-stepper.d.ts +20 -4
  151. package/types/forty-cdk-switch.d.ts +1 -20
  152. package/types/forty-cdk-table.d.ts +58 -39
  153. package/types/forty-cdk-time-field.d.ts +1 -0
  154. package/types/forty-cdk-time-picker.d.ts +47 -77
  155. package/types/forty-cdk-time-range-field.d.ts +30 -5
  156. package/types/forty-cdk-toggle.d.ts +9 -4
  157. package/types/forty-cdk-toolbar.d.ts +1 -0
  158. package/types/forty-cdk-tree.d.ts +21 -1
@@ -156,13 +156,15 @@ class ForAvatarImage {
156
156
  * if the consumer wants to log analytics, swap to a different src on
157
157
  * error, etc.
158
158
  */
159
- loadStatusChanged = output();
159
+ loadStatusChange = output();
160
160
  constructor() {
161
161
  if (!this.#parent) {
162
162
  throw new Error('[forty-cdk/avatar] ForAvatarImage must be used inside a [forAvatar] element.');
163
163
  }
164
164
  const parent = this.#parent;
165
165
  const host = this.#host;
166
+ let destroyed = false;
167
+ inject(DestroyRef).onDestroy(() => (destroyed = true));
166
168
  afterNextRender(() => {
167
169
  this.#syncFromAttr();
168
170
  });
@@ -181,12 +183,14 @@ class ForAvatarImage {
181
183
  let last = null;
182
184
  let lastReportedToken = -1;
183
185
  const emit = (status) => {
186
+ if (destroyed)
187
+ return;
184
188
  if (last === status && lastReportedToken === this.#requestToken)
185
189
  return;
186
190
  last = status;
187
191
  lastReportedToken = this.#requestToken;
188
192
  parent.reportStatus(status);
189
- this.loadStatusChanged.emit(status);
193
+ this.loadStatusChange.emit(status);
190
194
  };
191
195
  this.#emit = emit;
192
196
  }
@@ -262,7 +266,7 @@ class ForAvatarImage {
262
266
  /** The `src` the latest `#syncFromAttr` observed, used to match native events. */
263
267
  #requestSrc = null;
264
268
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForAvatarImage, deps: [], target: i0.ɵɵFactoryTarget.Directive });
265
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForAvatarImage, isStandalone: true, selector: "img[forAvatarImage]", outputs: { loadStatusChanged: "loadStatusChanged" }, host: { listeners: { "load": "onLoad()", "error": "onError()" }, properties: { "attr.data-status": "status()" } }, exportAs: ["forAvatarImage"], ngImport: i0 });
269
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForAvatarImage, isStandalone: true, selector: "img[forAvatarImage]", outputs: { loadStatusChange: "loadStatusChange" }, host: { listeners: { "load": "onLoad()", "error": "onError()" }, properties: { "attr.data-status": "status()" } }, exportAs: ["forAvatarImage"], ngImport: i0 });
266
270
  }
267
271
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForAvatarImage, decorators: [{
268
272
  type: Directive,
@@ -275,7 +279,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
275
279
  '(error)': 'onError()',
276
280
  },
277
281
  }]
278
- }], ctorParameters: () => [], propDecorators: { loadStatusChanged: [{ type: i0.Output, args: ["loadStatusChanged"] }] } });
282
+ }], ctorParameters: () => [], propDecorators: { loadStatusChange: [{ type: i0.Output, args: ["loadStatusChange"] }] } });
279
283
 
280
284
  /**
281
285
  * Marker for the avatar's fallback content (initials, generic icon, …).
@@ -1 +1 @@
1
- {"version":3,"file":"forty-cdk-avatar.mjs","sources":["../../../projects/forty-cdk/avatar/src/avatar-context.ts","../../../projects/forty-cdk/avatar/src/avatar-defaults.ts","../../../projects/forty-cdk/avatar/src/avatar.ts","../../../projects/forty-cdk/avatar/src/avatar-image.ts","../../../projects/forty-cdk/avatar/src/avatar-fallback.ts","../../../projects/forty-cdk/avatar/src/forty-cdk-avatar.ts"],"sourcesContent":["import { inject, InjectionToken, type Signal } from '@angular/core';\n\n/**\n * Lifecycle stages of the avatar's image:\n * - `idle`: no `src` set yet.\n * - `loading`: `src` set, waiting for the network.\n * - `loaded`: image rendered successfully.\n * - `error`: image failed to load.\n */\nexport type ForAvatarStatus = 'idle' | 'loading' | 'loaded' | 'error';\n\nexport interface ForAvatarContext {\n /** Current status of the avatar's image. */\n readonly status: Signal<ForAvatarStatus>;\n /**\n * `true` when the consumer should render the fallback. Drives `@if` in\n * the consumer's template. Honors `fallbackDelayMs` for `idle` / `loading`\n * states; `error` flips it to `true` immediately.\n */\n readonly shouldShowFallback: Signal<boolean>;\n /**\n * Internal — reported by `[forAvatarImage]` so the root can transition\n * its state machine. Consumers should not call this.\n */\n reportStatus(status: ForAvatarStatus): void;\n}\n\nexport const FOR_AVATAR_CONTEXT = new InjectionToken<ForAvatarContext>('FOR_AVATAR_CONTEXT');\n\n/**\n * Injects the nearest {@link ForAvatarContext}, throwing a descriptive error\n * when used outside a `[forAvatar]` element.\n *\n * @param piece Name of the calling directive, used in the error message.\n */\nexport function injectAvatarContext(piece: string): ForAvatarContext {\n const ctx = inject(FOR_AVATAR_CONTEXT, { optional: true });\n if (!ctx) {\n throw new Error(`[forty-cdk/avatar] ${piece} must be used inside a [forAvatar] element.`);\n }\n return ctx;\n}\n","import { type Provider } from '@angular/core';\n\nimport { createDefaults } from 'forty-cdk/core';\n\n/**\n * Defaults inherited by descendant avatars in the surrounding injector\n * scope. Configure with `provideForAvatarDefaults` either at the\n * application root or in any component's `providers` array; partial\n * overrides merge with the parent scope.\n */\nexport interface ForAvatarDefaults {\n /**\n * Milliseconds to defer the fallback for fast loads, avoiding a brief\n * \"initials → image\" flicker. Set to `0` to show immediately during\n * `idle` / `loading`, or to e.g. `500` to skip rendering during quick\n * cached loads. `error` always shows the fallback immediately.\n */\n fallbackDelayMs: number;\n}\n\n/**\n * Library fallback for avatar defaults, read at the root injector when no\n * consumer has called `provideForAvatarDefaults`. Exported for the shared defaults\n * contract spec; not re-exported from the primitive's public entry.\n */\nexport const FOR_AVATAR_FALLBACK_DEFAULTS: ForAvatarDefaults = {\n fallbackDelayMs: 0,\n};\n\nconst { token, provideDefaults } = createDefaults<ForAvatarDefaults>(\n 'FOR_AVATAR_DEFAULTS',\n FOR_AVATAR_FALLBACK_DEFAULTS,\n);\n\n/** Token holding the resolved avatar defaults for the current scope. */\nexport const FOR_AVATAR_DEFAULTS = token;\n\n/**\n * Configures forty-cdk avatar defaults for this injector scope. Partial\n * overrides inherit unspecified keys from the parent scope (or library\n * defaults at the root).\n */\nexport function provideForAvatarDefaults(defaults: Partial<ForAvatarDefaults> = {}): Provider[] {\n return provideDefaults(defaults);\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n computed,\n DestroyRef,\n Directive,\n effect,\n inject,\n input,\n linkedSignal,\n numberAttribute,\n PLATFORM_ID,\n signal,\n} from '@angular/core';\n\nimport { FOR_AVATAR_CONTEXT, type ForAvatarContext, type ForAvatarStatus } from './avatar-context';\nimport { FOR_AVATAR_DEFAULTS } from './avatar-defaults';\n\n/**\n * Headless avatar root. Tracks the load lifecycle of an inner\n * `[forAvatarImage]` and exposes `status` + `shouldShowFallback` so the\n * consumer can swap the image for fallback content (initials, generic icon)\n * via `@if`.\n *\n * No ARIA role is imposed — avatars are typically presentational. If the\n * surrounding component conveys identity (a person's name in the header,\n * a chat bubble), the consumer should provide an `aria-label` on the\n * relevant ancestor or pair the avatar with visible text.\n *\n * @example\n * ```html\n * <span forAvatar #a=\"forAvatar\" fallbackDelayMs=\"500\">\n * <img forAvatarImage [src]=\"user.avatarUrl\" [alt]=\"user.name\" />\n * @if (a.shouldShowFallback()) {\n * <span forAvatarFallback>{{ user.initials }}</span>\n * }\n * </span>\n * ```\n */\n@Directive({\n selector: '[forAvatar]',\n exportAs: 'forAvatar',\n host: {\n '[attr.data-status]': 'status()',\n },\n providers: [{ provide: FOR_AVATAR_CONTEXT, useExisting: ForAvatar }],\n})\nexport class ForAvatar implements ForAvatarContext {\n readonly #defaults = inject(FOR_AVATAR_DEFAULTS);\n\n /**\n * Milliseconds to defer the fallback for fast loads, avoiding a brief\n * \"initials → image\" flicker. Set to `0` (default) to show immediately\n * during `idle` / `loading`, or to e.g. `500` to skip rendering during\n * quick cached loads. `error` always shows the fallback immediately.\n * The default is read from `provideForAvatarDefaults` for the\n * surrounding scope.\n */\n readonly fallbackDelayMs = input(this.#defaults.fallbackDelayMs, {\n transform: numberAttribute,\n });\n\n readonly #status = signal<ForAvatarStatus>('idle');\n readonly status = this.#status.asReadonly();\n\n // Whether the deferred-fallback timer has fired for the current status/delay.\n // `linkedSignal` resets it to `false` whenever the source changes (the\n // canonical replacement for resetting state inside an effect); the timer\n // callback in the constructor flips it to `true`.\n readonly #timerElapsed = linkedSignal<{ status: ForAvatarStatus; delay: number }, boolean>({\n source: () => ({ status: this.#status(), delay: this.fallbackDelayMs() }),\n computation: () => false,\n });\n #timer: ReturnType<typeof setTimeout> | null = null;\n readonly #isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n readonly shouldShowFallback = computed(() => {\n const status = this.#status();\n if (status === 'loaded') return false;\n if (status === 'error') return true;\n // idle | loading: an immediate (delay <= 0) gate shows the fallback right\n // away; a deferred gate waits for the timer armed in the effect below.\n if (this.fallbackDelayMs() <= 0) return true;\n return this.#timerElapsed();\n });\n\n /** @internal */\n reportStatus(status: ForAvatarStatus): void {\n this.#status.set(status);\n }\n\n constructor() {\n // Imperative timer integration: the effect's only job is to (re)arm a\n // setTimeout for the deferred-fallback case (idle | loading with a positive\n // delay). The synchronous branches (loaded / error / delay <= 0) are pure\n // derivations and live in `shouldShowFallback`. The signal write happens\n // inside the timer callback, which runs outside the effect's reactive\n // scope, so this does NOT create a self-cycle in the reactive graph.\n effect(() => {\n const status = this.#status();\n const delay = this.fallbackDelayMs();\n this.#cancelTimer();\n if (this.#isBrowser && (status === 'idle' || status === 'loading') && delay > 0) {\n this.#timer = setTimeout(() => {\n this.#timer = null;\n this.#timerElapsed.set(true);\n }, delay);\n }\n });\n\n inject(DestroyRef).onDestroy(() => this.#cancelTimer());\n }\n\n #cancelTimer(): void {\n if (this.#timer !== null) {\n clearTimeout(this.#timer);\n this.#timer = null;\n }\n }\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n afterNextRender,\n DestroyRef,\n Directive,\n ElementRef,\n inject,\n output,\n PLATFORM_ID,\n} from '@angular/core';\n\nimport { FOR_AVATAR_CONTEXT, type ForAvatarStatus } from './avatar-context';\n\n/**\n * Wraps an `<img>` and reports its load lifecycle to the surrounding\n * `[forAvatar]`. The native `src` / `alt` bindings are owned by the\n * consumer — this directive only observes.\n *\n * Reflects `data-status` on the image element so the consumer can hide it\n * via CSS while loading or after an error (e.g. `img[data-status=\"error\"]\n * { display: none }`).\n */\n@Directive({\n selector: 'img[forAvatarImage]',\n exportAs: 'forAvatarImage',\n host: {\n '[attr.data-status]': 'status()',\n '(load)': 'onLoad()',\n '(error)': 'onError()',\n },\n})\nexport class ForAvatarImage {\n readonly #host = inject<ElementRef<HTMLImageElement>>(ElementRef).nativeElement;\n readonly #parent = inject(FOR_AVATAR_CONTEXT, { optional: true });\n\n /**\n * Emits whenever the image lifecycle transitions to a new state. Useful\n * if the consumer wants to log analytics, swap to a different src on\n * error, etc.\n */\n readonly loadStatusChanged = output<ForAvatarStatus>();\n\n constructor() {\n if (!this.#parent) {\n throw new Error(\n '[forty-cdk/avatar] ForAvatarImage must be used inside a [forAvatar] element.',\n );\n }\n const parent = this.#parent;\n const host = this.#host;\n\n afterNextRender(() => {\n this.#syncFromAttr();\n });\n\n if (isPlatformBrowser(inject(PLATFORM_ID)) && typeof MutationObserver !== 'undefined') {\n const observer = new MutationObserver(() => {\n this.#syncFromAttr();\n });\n observer.observe(host, { attributes: true, attributeFilter: ['src'] });\n inject(DestroyRef).onDestroy(() => observer.disconnect());\n }\n\n // Re-emit upstream whenever the parent's status changes due to our writes.\n // De-dupe on the (request, status) pair rather than status alone: a new\n // `src` opens a fresh request token, so an identical status (e.g. a second\n // image that also `loaded`) still re-reports for the new request, while a\n // repeated status within the same request is suppressed.\n let last: ForAvatarStatus | null = null;\n let lastReportedToken = -1;\n const emit = (status: ForAvatarStatus): void => {\n if (last === status && lastReportedToken === this.#requestToken) return;\n last = status;\n lastReportedToken = this.#requestToken;\n parent.reportStatus(status);\n this.loadStatusChanged.emit(status);\n };\n this.#emit = emit;\n }\n\n protected status(): ForAvatarStatus {\n return this.#parent!.status();\n }\n\n protected onLoad(): void {\n // Ignore a `load` that fires for a request the host has already moved past\n // (the native event can arrive after `src` was reassigned).\n if (!this.#isCurrentRequest()) return;\n this.#emit('loaded');\n }\n\n protected onError(): void {\n if (!this.#isCurrentRequest()) return;\n this.#emit('error');\n }\n\n /**\n * True when the `src` the host currently carries is the one the latest\n * `#syncFromAttr` observed, so a native load/error event belongs to the\n * in-flight request rather than a superseded one.\n */\n #isCurrentRequest(): boolean {\n return this.#host.getAttribute('src') === this.#requestSrc;\n }\n\n #syncFromAttr(): void {\n const src = this.#host.getAttribute('src');\n this.#requestToken++;\n this.#requestSrc = src;\n if (!src) {\n this.#emit('idle');\n return;\n }\n if (this.#host.complete) {\n // Cached image: load/error events may not fire — derive from naturalWidth.\n if (this.#host.naturalWidth > 0) {\n this.#emit('loaded');\n return;\n }\n // A complete image with zero intrinsic width is ambiguous: it may be a\n // broken image OR a valid SVG with no intrinsic dimensions. Don't\n // pessimistically flag `error` and hide a valid image — verify with\n // `decode()`, which resolves for a decodable (even zero-size) image and\n // rejects for a broken one. Stay `loading` until it settles.\n this.#emit('loading');\n this.#verifyCachedZeroSize(src);\n return;\n }\n this.#emit('loading');\n }\n\n #verifyCachedZeroSize(src: string): void {\n const decode = this.#host.decode?.bind(this.#host);\n if (!decode) {\n // No `decode()` (older engines / jsdom): keep the legacy heuristic.\n this.#emit('error');\n return;\n }\n decode().then(\n () => {\n if (this.#host.getAttribute('src') === src) {\n this.#emit('loaded');\n }\n },\n () => {\n if (this.#host.getAttribute('src') === src) {\n this.#emit('error');\n }\n },\n );\n }\n\n // Initialized in constructor.\n #emit!: (status: ForAvatarStatus) => void;\n\n /** Monotonic id bumped on every `src` change so stale events are ignored. */\n #requestToken = 0;\n /** The `src` the latest `#syncFromAttr` observed, used to match native events. */\n #requestSrc: string | null = null;\n}\n","import { Directive } from '@angular/core';\n\nimport { injectAvatarContext } from './avatar-context';\n\n/**\n * Marker for the avatar's fallback content (initials, generic icon, …).\n * Reflects `data-status` from the parent `[forAvatar]` so the consumer can\n * style for the `error` state separately from `idle` / `loading` if needed.\n *\n * The decision of *whether* to render the fallback lives on the root —\n * drive `@if` with `forAvatar.shouldShowFallback()`.\n */\n@Directive({\n selector: '[forAvatarFallback]',\n exportAs: 'forAvatarFallback',\n host: {\n '[attr.data-status]': 'parent.status()',\n },\n})\nexport class ForAvatarFallback {\n protected readonly parent = injectAvatarContext('ForAvatarFallback');\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"forty-cdk-avatar.mjs","sources":["../../../projects/forty-cdk/avatar/src/avatar-context.ts","../../../projects/forty-cdk/avatar/src/avatar-defaults.ts","../../../projects/forty-cdk/avatar/src/avatar.ts","../../../projects/forty-cdk/avatar/src/avatar-image.ts","../../../projects/forty-cdk/avatar/src/avatar-fallback.ts","../../../projects/forty-cdk/avatar/src/forty-cdk-avatar.ts"],"sourcesContent":["import { inject, InjectionToken, type Signal } from '@angular/core';\n\n/**\n * Lifecycle stages of the avatar's image:\n * - `idle`: no `src` set yet.\n * - `loading`: `src` set, waiting for the network.\n * - `loaded`: image rendered successfully.\n * - `error`: image failed to load.\n */\nexport type ForAvatarStatus = 'idle' | 'loading' | 'loaded' | 'error';\n\nexport interface ForAvatarContext {\n /** Current status of the avatar's image. */\n readonly status: Signal<ForAvatarStatus>;\n /**\n * `true` when the consumer should render the fallback. Drives `@if` in\n * the consumer's template. Honors `fallbackDelayMs` for `idle` / `loading`\n * states; `error` flips it to `true` immediately.\n */\n readonly shouldShowFallback: Signal<boolean>;\n /**\n * Internal — reported by `[forAvatarImage]` so the root can transition\n * its state machine. Consumers should not call this.\n */\n reportStatus(status: ForAvatarStatus): void;\n}\n\nexport const FOR_AVATAR_CONTEXT = new InjectionToken<ForAvatarContext>('FOR_AVATAR_CONTEXT');\n\n/**\n * Injects the nearest {@link ForAvatarContext}, throwing a descriptive error\n * when used outside a `[forAvatar]` element.\n *\n * @param piece Name of the calling directive, used in the error message.\n */\nexport function injectAvatarContext(piece: string): ForAvatarContext {\n const ctx = inject(FOR_AVATAR_CONTEXT, { optional: true });\n if (!ctx) {\n throw new Error(`[forty-cdk/avatar] ${piece} must be used inside a [forAvatar] element.`);\n }\n return ctx;\n}\n","import { type Provider } from '@angular/core';\n\nimport { createDefaults } from 'forty-cdk/core';\n\n/**\n * Defaults inherited by descendant avatars in the surrounding injector\n * scope. Configure with `provideForAvatarDefaults` either at the\n * application root or in any component's `providers` array; partial\n * overrides merge with the parent scope.\n */\nexport interface ForAvatarDefaults {\n /**\n * Milliseconds to defer the fallback for fast loads, avoiding a brief\n * \"initials → image\" flicker. Set to `0` to show immediately during\n * `idle` / `loading`, or to e.g. `500` to skip rendering during quick\n * cached loads. `error` always shows the fallback immediately.\n */\n fallbackDelayMs: number;\n}\n\n/**\n * Library fallback for avatar defaults, read at the root injector when no\n * consumer has called `provideForAvatarDefaults`. Exported for the shared defaults\n * contract spec; not re-exported from the primitive's public entry.\n */\nexport const FOR_AVATAR_FALLBACK_DEFAULTS: ForAvatarDefaults = {\n fallbackDelayMs: 0,\n};\n\nconst { token, provideDefaults } = createDefaults<ForAvatarDefaults>(\n 'FOR_AVATAR_DEFAULTS',\n FOR_AVATAR_FALLBACK_DEFAULTS,\n);\n\n/** Token holding the resolved avatar defaults for the current scope. */\nexport const FOR_AVATAR_DEFAULTS = token;\n\n/**\n * Configures forty-cdk avatar defaults for this injector scope. Partial\n * overrides inherit unspecified keys from the parent scope (or library\n * defaults at the root).\n */\nexport function provideForAvatarDefaults(defaults: Partial<ForAvatarDefaults> = {}): Provider[] {\n return provideDefaults(defaults);\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n computed,\n DestroyRef,\n Directive,\n effect,\n inject,\n input,\n linkedSignal,\n numberAttribute,\n PLATFORM_ID,\n signal,\n} from '@angular/core';\n\nimport { FOR_AVATAR_CONTEXT, type ForAvatarContext, type ForAvatarStatus } from './avatar-context';\nimport { FOR_AVATAR_DEFAULTS } from './avatar-defaults';\n\n/**\n * Headless avatar root. Tracks the load lifecycle of an inner\n * `[forAvatarImage]` and exposes `status` + `shouldShowFallback` so the\n * consumer can swap the image for fallback content (initials, generic icon)\n * via `@if`.\n *\n * No ARIA role is imposed — avatars are typically presentational. If the\n * surrounding component conveys identity (a person's name in the header,\n * a chat bubble), the consumer should provide an `aria-label` on the\n * relevant ancestor or pair the avatar with visible text.\n *\n * @example\n * ```html\n * <span forAvatar #a=\"forAvatar\" fallbackDelayMs=\"500\">\n * <img forAvatarImage [src]=\"user.avatarUrl\" [alt]=\"user.name\" />\n * @if (a.shouldShowFallback()) {\n * <span forAvatarFallback>{{ user.initials }}</span>\n * }\n * </span>\n * ```\n */\n@Directive({\n selector: '[forAvatar]',\n exportAs: 'forAvatar',\n host: {\n '[attr.data-status]': 'status()',\n },\n providers: [{ provide: FOR_AVATAR_CONTEXT, useExisting: ForAvatar }],\n})\nexport class ForAvatar implements ForAvatarContext {\n readonly #defaults = inject(FOR_AVATAR_DEFAULTS);\n\n /**\n * Milliseconds to defer the fallback for fast loads, avoiding a brief\n * \"initials → image\" flicker. Set to `0` (default) to show immediately\n * during `idle` / `loading`, or to e.g. `500` to skip rendering during\n * quick cached loads. `error` always shows the fallback immediately.\n * The default is read from `provideForAvatarDefaults` for the\n * surrounding scope.\n */\n readonly fallbackDelayMs = input(this.#defaults.fallbackDelayMs, {\n transform: numberAttribute,\n });\n\n readonly #status = signal<ForAvatarStatus>('idle');\n readonly status = this.#status.asReadonly();\n\n // Whether the deferred-fallback timer has fired for the current status/delay.\n // `linkedSignal` resets it to `false` whenever the source changes (the\n // canonical replacement for resetting state inside an effect); the timer\n // callback in the constructor flips it to `true`.\n readonly #timerElapsed = linkedSignal<{ status: ForAvatarStatus; delay: number }, boolean>({\n source: () => ({ status: this.#status(), delay: this.fallbackDelayMs() }),\n computation: () => false,\n });\n #timer: ReturnType<typeof setTimeout> | null = null;\n readonly #isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n readonly shouldShowFallback = computed(() => {\n const status = this.#status();\n if (status === 'loaded') return false;\n if (status === 'error') return true;\n // idle | loading: an immediate (delay <= 0) gate shows the fallback right\n // away; a deferred gate waits for the timer armed in the effect below.\n if (this.fallbackDelayMs() <= 0) return true;\n return this.#timerElapsed();\n });\n\n /** @internal */\n reportStatus(status: ForAvatarStatus): void {\n this.#status.set(status);\n }\n\n constructor() {\n // Imperative timer integration: the effect's only job is to (re)arm a\n // setTimeout for the deferred-fallback case (idle | loading with a positive\n // delay). The synchronous branches (loaded / error / delay <= 0) are pure\n // derivations and live in `shouldShowFallback`. The signal write happens\n // inside the timer callback, which runs outside the effect's reactive\n // scope, so this does NOT create a self-cycle in the reactive graph.\n effect(() => {\n const status = this.#status();\n const delay = this.fallbackDelayMs();\n this.#cancelTimer();\n if (this.#isBrowser && (status === 'idle' || status === 'loading') && delay > 0) {\n this.#timer = setTimeout(() => {\n this.#timer = null;\n this.#timerElapsed.set(true);\n }, delay);\n }\n });\n\n inject(DestroyRef).onDestroy(() => this.#cancelTimer());\n }\n\n #cancelTimer(): void {\n if (this.#timer !== null) {\n clearTimeout(this.#timer);\n this.#timer = null;\n }\n }\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n afterNextRender,\n DestroyRef,\n Directive,\n ElementRef,\n inject,\n output,\n PLATFORM_ID,\n} from '@angular/core';\n\nimport { FOR_AVATAR_CONTEXT, type ForAvatarStatus } from './avatar-context';\n\n/**\n * Wraps an `<img>` and reports its load lifecycle to the surrounding\n * `[forAvatar]`. The native `src` / `alt` bindings are owned by the\n * consumer — this directive only observes.\n *\n * Reflects `data-status` on the image element so the consumer can hide it\n * via CSS while loading or after an error (e.g. `img[data-status=\"error\"]\n * { display: none }`).\n */\n@Directive({\n selector: 'img[forAvatarImage]',\n exportAs: 'forAvatarImage',\n host: {\n '[attr.data-status]': 'status()',\n '(load)': 'onLoad()',\n '(error)': 'onError()',\n },\n})\nexport class ForAvatarImage {\n readonly #host = inject<ElementRef<HTMLImageElement>>(ElementRef).nativeElement;\n readonly #parent = inject(FOR_AVATAR_CONTEXT, { optional: true });\n\n /**\n * Emits whenever the image lifecycle transitions to a new state. Useful\n * if the consumer wants to log analytics, swap to a different src on\n * error, etc.\n */\n readonly loadStatusChange = output<ForAvatarStatus>();\n\n constructor() {\n if (!this.#parent) {\n throw new Error(\n '[forty-cdk/avatar] ForAvatarImage must be used inside a [forAvatar] element.',\n );\n }\n const parent = this.#parent;\n const host = this.#host;\n\n let destroyed = false;\n inject(DestroyRef).onDestroy(() => (destroyed = true));\n\n afterNextRender(() => {\n this.#syncFromAttr();\n });\n\n if (isPlatformBrowser(inject(PLATFORM_ID)) && typeof MutationObserver !== 'undefined') {\n const observer = new MutationObserver(() => {\n this.#syncFromAttr();\n });\n observer.observe(host, { attributes: true, attributeFilter: ['src'] });\n inject(DestroyRef).onDestroy(() => observer.disconnect());\n }\n\n // Re-emit upstream whenever the parent's status changes due to our writes.\n // De-dupe on the (request, status) pair rather than status alone: a new\n // `src` opens a fresh request token, so an identical status (e.g. a second\n // image that also `loaded`) still re-reports for the new request, while a\n // repeated status within the same request is suppressed.\n let last: ForAvatarStatus | null = null;\n let lastReportedToken = -1;\n const emit = (status: ForAvatarStatus): void => {\n if (destroyed) return;\n if (last === status && lastReportedToken === this.#requestToken) return;\n last = status;\n lastReportedToken = this.#requestToken;\n parent.reportStatus(status);\n this.loadStatusChange.emit(status);\n };\n this.#emit = emit;\n }\n\n protected status(): ForAvatarStatus {\n return this.#parent!.status();\n }\n\n protected onLoad(): void {\n // Ignore a `load` that fires for a request the host has already moved past\n // (the native event can arrive after `src` was reassigned).\n if (!this.#isCurrentRequest()) return;\n this.#emit('loaded');\n }\n\n protected onError(): void {\n if (!this.#isCurrentRequest()) return;\n this.#emit('error');\n }\n\n /**\n * True when the `src` the host currently carries is the one the latest\n * `#syncFromAttr` observed, so a native load/error event belongs to the\n * in-flight request rather than a superseded one.\n */\n #isCurrentRequest(): boolean {\n return this.#host.getAttribute('src') === this.#requestSrc;\n }\n\n #syncFromAttr(): void {\n const src = this.#host.getAttribute('src');\n this.#requestToken++;\n this.#requestSrc = src;\n if (!src) {\n this.#emit('idle');\n return;\n }\n if (this.#host.complete) {\n // Cached image: load/error events may not fire — derive from naturalWidth.\n if (this.#host.naturalWidth > 0) {\n this.#emit('loaded');\n return;\n }\n // A complete image with zero intrinsic width is ambiguous: it may be a\n // broken image OR a valid SVG with no intrinsic dimensions. Don't\n // pessimistically flag `error` and hide a valid image — verify with\n // `decode()`, which resolves for a decodable (even zero-size) image and\n // rejects for a broken one. Stay `loading` until it settles.\n this.#emit('loading');\n this.#verifyCachedZeroSize(src);\n return;\n }\n this.#emit('loading');\n }\n\n #verifyCachedZeroSize(src: string): void {\n const decode = this.#host.decode?.bind(this.#host);\n if (!decode) {\n // No `decode()` (older engines / jsdom): keep the legacy heuristic.\n this.#emit('error');\n return;\n }\n decode().then(\n () => {\n if (this.#host.getAttribute('src') === src) {\n this.#emit('loaded');\n }\n },\n () => {\n if (this.#host.getAttribute('src') === src) {\n this.#emit('error');\n }\n },\n );\n }\n\n // Initialized in constructor.\n #emit!: (status: ForAvatarStatus) => void;\n\n /** Monotonic id bumped on every `src` change so stale events are ignored. */\n #requestToken = 0;\n /** The `src` the latest `#syncFromAttr` observed, used to match native events. */\n #requestSrc: string | null = null;\n}\n","import { Directive } from '@angular/core';\n\nimport { injectAvatarContext } from './avatar-context';\n\n/**\n * Marker for the avatar's fallback content (initials, generic icon, …).\n * Reflects `data-status` from the parent `[forAvatar]` so the consumer can\n * style for the `error` state separately from `idle` / `loading` if needed.\n *\n * The decision of *whether* to render the fallback lives on the root —\n * drive `@if` with `forAvatar.shouldShowFallback()`.\n */\n@Directive({\n selector: '[forAvatarFallback]',\n exportAs: 'forAvatarFallback',\n host: {\n '[attr.data-status]': 'parent.status()',\n },\n})\nexport class ForAvatarFallback {\n protected readonly parent = injectAvatarContext('ForAvatarFallback');\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -1,7 +1,27 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, Directive, booleanAttribute } from '@angular/core';
2
+ import { inject, input, Directive, booleanAttribute } from '@angular/core';
3
3
  import { createDefaults } from 'forty-cdk/core';
4
4
 
5
+ /**
6
+ * Library fallback for breadcrumbs defaults, read at the root injector when no
7
+ * consumer has called `provideForBreadcrumbsDefaults`. Exported for the shared
8
+ * defaults contract spec; not re-exported from the primitive's public entry.
9
+ */
10
+ const FOR_BREADCRUMBS_FALLBACK_DEFAULTS = {
11
+ label: 'Breadcrumb',
12
+ };
13
+ const { token, provideDefaults } = createDefaults('FOR_BREADCRUMBS_DEFAULTS', FOR_BREADCRUMBS_FALLBACK_DEFAULTS);
14
+ /** Token holding the resolved breadcrumbs defaults for the current scope. */
15
+ const FOR_BREADCRUMBS_DEFAULTS = token;
16
+ /**
17
+ * Configures forty-cdk breadcrumbs defaults for this injector scope. Partial
18
+ * overrides inherit unspecified keys from the parent scope (or library
19
+ * defaults at the root).
20
+ */
21
+ function provideForBreadcrumbsDefaults(defaults = {}) {
22
+ return provideDefaults(defaults);
23
+ }
24
+
5
25
  /**
6
26
  * Headless breadcrumb trail implementing the
7
27
  * [WAI-ARIA Breadcrumb pattern](https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/):
@@ -32,17 +52,21 @@ import { createDefaults } from 'forty-cdk/core';
32
52
  * ```
33
53
  */
34
54
  class ForBreadcrumbs {
55
+ #defaults = inject(FOR_BREADCRUMBS_DEFAULTS);
35
56
  /**
36
- * Accessible label for the navigation landmark. Defaults to `'Breadcrumb'`
37
- * so a bare `<nav forBreadcrumbs>` is already a correctly labelled landmark —
38
- * the conventional name for this pattern. Override it (or point a native
57
+ * Accessible label for the navigation landmark. Defaults to the scope's
58
+ * `label` (`'Breadcrumb'` unless overridden via
59
+ * `provideForBreadcrumbsDefaults`), so a bare `<nav forBreadcrumbs>` is
60
+ * already a correctly labelled landmark — the conventional name for this
61
+ * pattern. Override it per-instance via `[ariaLabel]` (or point a native
39
62
  * `aria-labelledby` at a visible heading) when the page hosts more than one
40
- * breadcrumb trail and they need to be told apart.
63
+ * breadcrumb trail and they need to be told apart; set it to `null` to drop
64
+ * the attribute and rely on `aria-labelledby`.
41
65
  */
42
- ariaLabel = input(null, /* @ts-ignore */
66
+ ariaLabel = input(this.#defaults.label, /* @ts-ignore */
43
67
  ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
44
68
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForBreadcrumbs, deps: [], target: i0.ɵɵFactoryTarget.Directive });
45
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForBreadcrumbs, isStandalone: true, selector: "[forBreadcrumbs]", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "navigation" }, properties: { "attr.aria-label": "ariaLabel() || 'Breadcrumb'" } }, exportAs: ["forBreadcrumbs"], ngImport: i0 });
69
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForBreadcrumbs, isStandalone: true, selector: "[forBreadcrumbs]", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "navigation" }, properties: { "attr.aria-label": "ariaLabel()" } }, exportAs: ["forBreadcrumbs"], ngImport: i0 });
46
70
  }
47
71
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForBreadcrumbs, decorators: [{
48
72
  type: Directive,
@@ -51,7 +75,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
51
75
  exportAs: 'forBreadcrumbs',
52
76
  host: {
53
77
  role: 'navigation',
54
- '[attr.aria-label]': "ariaLabel() || 'Breadcrumb'",
78
+ '[attr.aria-label]': 'ariaLabel()',
55
79
  },
56
80
  }]
57
81
  }], propDecorators: { ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
@@ -104,19 +128,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
104
128
  }]
105
129
  }] });
106
130
 
107
- const FALLBACK = {};
108
- const { token, provideDefaults } = createDefaults('FOR_BREADCRUMBS_DEFAULTS', FALLBACK);
109
- /** Token holding the resolved breadcrumbs defaults for the current scope. */
110
- const FOR_BREADCRUMBS_DEFAULTS = token;
111
- /**
112
- * Configures forty-cdk breadcrumbs defaults for this injector scope. Partial
113
- * overrides inherit unspecified keys from the parent scope (or library
114
- * defaults at the root).
115
- */
116
- function provideForBreadcrumbsDefaults(defaults = {}) {
117
- return provideDefaults(defaults);
118
- }
119
-
120
131
  /**
121
132
  * Generated bundle index. Do not edit.
122
133
  */
@@ -1 +1 @@
1
- {"version":3,"file":"forty-cdk-breadcrumbs.mjs","sources":["../../../projects/forty-cdk/breadcrumbs/src/breadcrumbs.ts","../../../projects/forty-cdk/breadcrumbs/src/breadcrumb-item.ts","../../../projects/forty-cdk/breadcrumbs/src/breadcrumb-separator.ts","../../../projects/forty-cdk/breadcrumbs/src/breadcrumbs-defaults.ts","../../../projects/forty-cdk/breadcrumbs/src/forty-cdk-breadcrumbs.ts"],"sourcesContent":["import { Directive, input } from '@angular/core';\n\n/**\n * Headless breadcrumb trail implementing the\n * [WAI-ARIA Breadcrumb pattern](https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/):\n * a `navigation` landmark wrapping a set of links that reveal the page's\n * position within the site hierarchy.\n *\n * The directive contributes only the landmark role + accessible name; the\n * consumer owns the markup structure (an `<ol>` of `<li>` links is the APG\n * recommendation) and applies their own styles. Each link decorated with\n * `[forBreadcrumbItem]` can mark itself the current page via `current`, and\n * visual dividers decorated with `[forBreadcrumbSeparator]` are hidden from\n * assistive technology.\n *\n * There is no keyboard interaction or focus management beyond what the native\n * links provide — breadcrumb links are ordinary tab stops.\n *\n * @example\n * ```html\n * <nav forBreadcrumbs>\n * <ol>\n * <li><a forBreadcrumbItem href=\"/\">Home</a></li>\n * <li forBreadcrumbSeparator aria-hidden=\"true\">/</li>\n * <li><a forBreadcrumbItem href=\"/library\">Library</a></li>\n * <li forBreadcrumbSeparator aria-hidden=\"true\">/</li>\n * <li><a forBreadcrumbItem href=\"/library/data\" current>Data</a></li>\n * </ol>\n * </nav>\n * ```\n */\n@Directive({\n selector: '[forBreadcrumbs]',\n exportAs: 'forBreadcrumbs',\n host: {\n role: 'navigation',\n '[attr.aria-label]': \"ariaLabel() || 'Breadcrumb'\",\n },\n})\nexport class ForBreadcrumbs {\n /**\n * Accessible label for the navigation landmark. Defaults to `'Breadcrumb'`\n * so a bare `<nav forBreadcrumbs>` is already a correctly labelled landmark —\n * the conventional name for this pattern. Override it (or point a native\n * `aria-labelledby` at a visible heading) when the page hosts more than one\n * breadcrumb trail and they need to be told apart.\n */\n readonly ariaLabel = input<string | null>(null);\n}\n","import { booleanAttribute, Directive, input } from '@angular/core';\n\n/**\n * A single link within a breadcrumb trail. Apply on an `<a>` so navigation is\n * native. Set `current` on the link that points to the page the user is on —\n * it reflects [`aria-current=\"page\"`](https://www.w3.org/TR/wai-aria-1.2/#aria-current)\n * so assistive technology announces it as the current location.\n */\n@Directive({\n selector: '[forBreadcrumbItem]',\n exportAs: 'forBreadcrumbItem',\n host: {\n '[attr.aria-current]': 'current() ? \"page\" : null',\n },\n})\nexport class ForBreadcrumbItem {\n /**\n * Whether this item represents the page the user is currently on. The\n * current item reflects `aria-current=\"page\"`; all others emit no\n * `aria-current` attribute.\n */\n readonly current = input(false, { transform: booleanAttribute });\n}\n","import { Directive } from '@angular/core';\n\n/**\n * Decorative divider rendered between breadcrumb items (a `/`, `>`, chevron,\n * etc.). Reflects `aria-hidden=\"true\"` so assistive technology skips it — the\n * trail's structure is already conveyed by the list and the links, making the\n * visual separator redundant in the accessibility tree.\n */\n@Directive({\n selector: '[forBreadcrumbSeparator]',\n exportAs: 'forBreadcrumbSeparator',\n host: {\n 'aria-hidden': 'true',\n },\n})\nexport class ForBreadcrumbSeparator {}\n","import { type Provider } from '@angular/core';\n\nimport { createDefaults } from 'forty-cdk/core';\n\n/**\n * Defaults inherited by descendant breadcrumbs in the surrounding injector\n * scope. Configure with `provideForBreadcrumbsDefaults`. The shape is a stub\n * today — present so future per-scope tuning can land without churning the\n * public surface.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface ForBreadcrumbsDefaults {}\n\nconst FALLBACK: ForBreadcrumbsDefaults = {};\n\nconst { token, provideDefaults } = createDefaults<ForBreadcrumbsDefaults>(\n 'FOR_BREADCRUMBS_DEFAULTS',\n FALLBACK,\n);\n\n/** Token holding the resolved breadcrumbs defaults for the current scope. */\nexport const FOR_BREADCRUMBS_DEFAULTS = token;\n\n/**\n * Configures forty-cdk breadcrumbs defaults for this injector scope. Partial\n * overrides inherit unspecified keys from the parent scope (or library\n * defaults at the root).\n */\nexport function provideForBreadcrumbsDefaults(\n defaults: Partial<ForBreadcrumbsDefaults> = {},\n): Provider[] {\n return provideDefaults(defaults);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"forty-cdk-breadcrumbs.mjs","sources":["../../../projects/forty-cdk/breadcrumbs/src/breadcrumbs-defaults.ts","../../../projects/forty-cdk/breadcrumbs/src/breadcrumbs.ts","../../../projects/forty-cdk/breadcrumbs/src/breadcrumb-item.ts","../../../projects/forty-cdk/breadcrumbs/src/breadcrumb-separator.ts","../../../projects/forty-cdk/breadcrumbs/src/forty-cdk-breadcrumbs.ts"],"sourcesContent":["import { type Provider } from '@angular/core';\n\nimport { createDefaults } from 'forty-cdk/core';\n\n/**\n * Defaults inherited by descendant breadcrumb trails in the surrounding\n * injector scope. Configure with `provideForBreadcrumbsDefaults` either at the\n * application root or in any component's `providers` array; partial overrides\n * merge with the parent scope.\n */\nexport interface ForBreadcrumbsDefaults {\n /**\n * Accessible name for the breadcrumb `navigation` landmark, for trails that\n * don't set `[ariaLabel]` locally. Localize it here to translate every\n * breadcrumb landmark in the scope.\n */\n label: string;\n}\n\n/**\n * Library fallback for breadcrumbs defaults, read at the root injector when no\n * consumer has called `provideForBreadcrumbsDefaults`. Exported for the shared\n * defaults contract spec; not re-exported from the primitive's public entry.\n */\nexport const FOR_BREADCRUMBS_FALLBACK_DEFAULTS: ForBreadcrumbsDefaults = {\n label: 'Breadcrumb',\n};\n\nconst { token, provideDefaults } = createDefaults<ForBreadcrumbsDefaults>(\n 'FOR_BREADCRUMBS_DEFAULTS',\n FOR_BREADCRUMBS_FALLBACK_DEFAULTS,\n);\n\n/** Token holding the resolved breadcrumbs defaults for the current scope. */\nexport const FOR_BREADCRUMBS_DEFAULTS = token;\n\n/**\n * Configures forty-cdk breadcrumbs defaults for this injector scope. Partial\n * overrides inherit unspecified keys from the parent scope (or library\n * defaults at the root).\n */\nexport function provideForBreadcrumbsDefaults(\n defaults: Partial<ForBreadcrumbsDefaults> = {},\n): Provider[] {\n return provideDefaults(defaults);\n}\n","import { Directive, inject, input } from '@angular/core';\n\nimport { FOR_BREADCRUMBS_DEFAULTS } from './breadcrumbs-defaults';\n\n/**\n * Headless breadcrumb trail implementing the\n * [WAI-ARIA Breadcrumb pattern](https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/):\n * a `navigation` landmark wrapping a set of links that reveal the page's\n * position within the site hierarchy.\n *\n * The directive contributes only the landmark role + accessible name; the\n * consumer owns the markup structure (an `<ol>` of `<li>` links is the APG\n * recommendation) and applies their own styles. Each link decorated with\n * `[forBreadcrumbItem]` can mark itself the current page via `current`, and\n * visual dividers decorated with `[forBreadcrumbSeparator]` are hidden from\n * assistive technology.\n *\n * There is no keyboard interaction or focus management beyond what the native\n * links provide — breadcrumb links are ordinary tab stops.\n *\n * @example\n * ```html\n * <nav forBreadcrumbs>\n * <ol>\n * <li><a forBreadcrumbItem href=\"/\">Home</a></li>\n * <li forBreadcrumbSeparator aria-hidden=\"true\">/</li>\n * <li><a forBreadcrumbItem href=\"/library\">Library</a></li>\n * <li forBreadcrumbSeparator aria-hidden=\"true\">/</li>\n * <li><a forBreadcrumbItem href=\"/library/data\" current>Data</a></li>\n * </ol>\n * </nav>\n * ```\n */\n@Directive({\n selector: '[forBreadcrumbs]',\n exportAs: 'forBreadcrumbs',\n host: {\n role: 'navigation',\n '[attr.aria-label]': 'ariaLabel()',\n },\n})\nexport class ForBreadcrumbs {\n readonly #defaults = inject(FOR_BREADCRUMBS_DEFAULTS);\n\n /**\n * Accessible label for the navigation landmark. Defaults to the scope's\n * `label` (`'Breadcrumb'` unless overridden via\n * `provideForBreadcrumbsDefaults`), so a bare `<nav forBreadcrumbs>` is\n * already a correctly labelled landmark — the conventional name for this\n * pattern. Override it per-instance via `[ariaLabel]` (or point a native\n * `aria-labelledby` at a visible heading) when the page hosts more than one\n * breadcrumb trail and they need to be told apart; set it to `null` to drop\n * the attribute and rely on `aria-labelledby`.\n */\n readonly ariaLabel = input<string | null>(this.#defaults.label);\n}\n","import { booleanAttribute, Directive, input } from '@angular/core';\n\n/**\n * A single link within a breadcrumb trail. Apply on an `<a>` so navigation is\n * native. Set `current` on the link that points to the page the user is on —\n * it reflects [`aria-current=\"page\"`](https://www.w3.org/TR/wai-aria-1.2/#aria-current)\n * so assistive technology announces it as the current location.\n */\n@Directive({\n selector: '[forBreadcrumbItem]',\n exportAs: 'forBreadcrumbItem',\n host: {\n '[attr.aria-current]': 'current() ? \"page\" : null',\n },\n})\nexport class ForBreadcrumbItem {\n /**\n * Whether this item represents the page the user is currently on. The\n * current item reflects `aria-current=\"page\"`; all others emit no\n * `aria-current` attribute.\n */\n readonly current = input(false, { transform: booleanAttribute });\n}\n","import { Directive } from '@angular/core';\n\n/**\n * Decorative divider rendered between breadcrumb items (a `/`, `>`, chevron,\n * etc.). Reflects `aria-hidden=\"true\"` so assistive technology skips it — the\n * trail's structure is already conveyed by the list and the links, making the\n * visual separator redundant in the accessibility tree.\n */\n@Directive({\n selector: '[forBreadcrumbSeparator]',\n exportAs: 'forBreadcrumbSeparator',\n host: {\n 'aria-hidden': 'true',\n },\n})\nexport class ForBreadcrumbSeparator {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -1,6 +1,223 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, ElementRef, input, booleanAttribute, output, signal, computed, Directive } from '@angular/core';
3
- import { injectFocusVisible, injectHovered, injectPressed, createDefaults } from 'forty-cdk/core';
2
+ import { inject, DOCUMENT, PLATFORM_ID, signal, DestroyRef, Injectable, ElementRef, computed, input, booleanAttribute, output, Directive } from '@angular/core';
3
+ import { isPlatformBrowser } from '@angular/common';
4
+
5
+ /**
6
+ * Application-scoped tracker for the last input modality the user employed —
7
+ * the in-house implementation of input-modality (pointer vs keyboard)
8
+ * detection. Created once per Angular bootstrap (one per SSR
9
+ * request), tied to the root injector lifetime.
10
+ *
11
+ * It installs exactly **one** capture-phase `keydown` and **one** capture-phase
12
+ * `pointerdown` listener on `document`, regardless of how many primitives read
13
+ * its state — every `injectFocusVisible()` consumer shares this single
14
+ * singleton, so the document is never listened to more than once. Capture phase
15
+ * is used so the modality is settled before any overlay content can stop
16
+ * propagation.
17
+ *
18
+ * Why a service rather than module-level state:
19
+ *
20
+ * - SSR isolation: module-level globals leak between simultaneous server
21
+ * requests in the same Node process. A `providedIn: 'root'` service is
22
+ * instantiated per application injector.
23
+ * - Bootstrap-safety: `TestBed.resetTestingModule()`, micro-frontend reloads,
24
+ * and anything else that destroys `ApplicationRef` must not leave stale
25
+ * `document` listeners behind. The listeners are registered in the
26
+ * constructor and removed via `DestroyRef`.
27
+ * - SSR safety: `document` is inaccessible on the server. The service is a
28
+ * no-op when `PLATFORM_ID` is not the browser; `keyboard` stays `false` and
29
+ * no listener is installed.
30
+ *
31
+ * Internal — not re-exported from `public-api.ts`.
32
+ */
33
+ class InputModality {
34
+ #document = inject(DOCUMENT);
35
+ #isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
36
+ #keyboard = signal(false, /* @ts-ignore */
37
+ ...(ngDevMode ? [{ debugName: "#keyboard" }] : /* istanbul ignore next */ []));
38
+ /**
39
+ * `true` when the most recent input modality was the keyboard, `false` after
40
+ * a pointer interaction (or before any interaction). Stays `false` on the
41
+ * server. A `keydown` carrying a `Meta` / `Control` / `Alt` modifier is
42
+ * treated as a shortcut rather than keyboard navigation and does **not** flip
43
+ * this to `true` (matching the platform `:focus-visible` heuristic); `Shift`
44
+ * alone still counts, since `Shift`+`Tab` is legitimate keyboard navigation.
45
+ */
46
+ keyboard = this.#keyboard.asReadonly();
47
+ #onKeyDown = (event) => {
48
+ if (event.metaKey || event.ctrlKey || event.altKey) {
49
+ return;
50
+ }
51
+ this.#keyboard.set(true);
52
+ };
53
+ #onPointerDown = () => {
54
+ this.#keyboard.set(false);
55
+ };
56
+ constructor() {
57
+ if (!this.#isBrowser) {
58
+ return;
59
+ }
60
+ this.#document.addEventListener('keydown', this.#onKeyDown, true);
61
+ this.#document.addEventListener('pointerdown', this.#onPointerDown, true);
62
+ inject(DestroyRef).onDestroy(() => {
63
+ this.#document.removeEventListener('keydown', this.#onKeyDown, true);
64
+ this.#document.removeEventListener('pointerdown', this.#onPointerDown, true);
65
+ });
66
+ }
67
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: InputModality, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
68
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: InputModality, providedIn: 'root' });
69
+ }
70
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: InputModality, decorators: [{
71
+ type: Injectable,
72
+ args: [{ providedIn: 'root' }]
73
+ }], ctorParameters: () => [] });
74
+
75
+ /**
76
+ * Returns a `Signal<boolean>` that is `true` while the last global input
77
+ * modality was the keyboard, and `false` after a pointer interaction (or
78
+ * before any interaction). This is the modality half of `:focus-visible`
79
+ * styling: a consumer that wants a focus ring only on keyboard focus combines
80
+ * this with the element's own focused state (`:focus`, or a `focusin` /
81
+ * `focusout` listener) — e.g. `host: { '[attr.data-focus-visible]':
82
+ * "focused() && focusVisible() ? '' : null" }`.
83
+ *
84
+ * Backed by the application-scoped {@link InputModality} singleton, so every
85
+ * consumer shares a single capture-phase `keydown` / `pointerdown` listener on
86
+ * `document` no matter how many call this.
87
+ *
88
+ * SSR-safe: on the server the backing service installs no listener and the
89
+ * signal stays `false`. Must be called from an injection context.
90
+ *
91
+ * Internal — not re-exported from `public-api.ts`.
92
+ */
93
+ function injectFocusVisible() {
94
+ return inject(InputModality).keyboard;
95
+ }
96
+
97
+ /**
98
+ * Returns a `Signal<boolean>` reflecting whether a pointing device is currently
99
+ * hovering the host element. State is set on `pointerenter` and cleared on
100
+ * `pointerleave`.
101
+ *
102
+ * Touch is suppressed: a `pointerenter` whose `pointerType` is `'touch'` does
103
+ * not set the hovered state, so the emulated mouse-enter a tap produces on a
104
+ * touchscreen never leaves the element stuck in a hovered state after the
105
+ * finger lifts. Only `'mouse'` / `'pen'` pointers report hover.
106
+ *
107
+ * Attaches `pointerenter` / `pointerleave` listeners to the host element. The
108
+ * `disabled` option short-circuits the result reactively (a pure `computed`,
109
+ * never an `effect`-written signal) and also stops hover from arming while
110
+ * disabled.
111
+ *
112
+ * SSR-safe: on the server no listener is attached and the signal stays
113
+ * `false`. Must be called from an injection context (injects `ElementRef`,
114
+ * `PLATFORM_ID`, `DestroyRef`).
115
+ *
116
+ * Internal — not re-exported from `public-api.ts`.
117
+ */
118
+ function injectHovered(opts) {
119
+ const disabled = opts?.disabled;
120
+ const isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
121
+ if (!isBrowser) {
122
+ return signal(false).asReadonly();
123
+ }
124
+ const el = inject(ElementRef).nativeElement;
125
+ const hovered = signal(false, /* @ts-ignore */
126
+ ...(ngDevMode ? [{ debugName: "hovered" }] : /* istanbul ignore next */ []));
127
+ const onPointerEnter = (event) => {
128
+ if (event.pointerType === 'touch') {
129
+ return;
130
+ }
131
+ if (disabled?.()) {
132
+ return;
133
+ }
134
+ hovered.set(true);
135
+ };
136
+ const onPointerLeave = () => {
137
+ hovered.set(false);
138
+ };
139
+ el.addEventListener('pointerenter', onPointerEnter);
140
+ el.addEventListener('pointerleave', onPointerLeave);
141
+ inject(DestroyRef).onDestroy(() => {
142
+ el.removeEventListener('pointerenter', onPointerEnter);
143
+ el.removeEventListener('pointerleave', onPointerLeave);
144
+ });
145
+ return computed(() => (disabled?.() ? false : hovered()));
146
+ }
147
+
148
+ /**
149
+ * Returns a `Signal<boolean>` reflecting whether the host element is currently
150
+ * being pressed — held down via the primary pointer button, or via the
151
+ * <kbd>Enter</kbd> / <kbd>Space</kbd> key while focused. The state clears on
152
+ * pointer release, the pointer leaving the element mid-press, key release, or
153
+ * `blur` (covers focus leaving the element while a key is held).
154
+ *
155
+ * Attaches `pointerdown` / `pointerup` / `pointerleave` / `keydown` / `keyup` /
156
+ * `blur` listeners to the host element. The `disabled` option short-circuits
157
+ * the result reactively (a pure `computed`, never an `effect`-written signal)
158
+ * and also stops a fresh press from arming while disabled.
159
+ *
160
+ * SSR-safe: on the server no listener is attached and the signal stays
161
+ * `false`. Must be called from an injection context (injects `ElementRef`,
162
+ * `PLATFORM_ID`, `DestroyRef`).
163
+ *
164
+ * Internal — not re-exported from `public-api.ts`.
165
+ */
166
+ function injectPressed(opts) {
167
+ const disabled = opts?.disabled;
168
+ const isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
169
+ if (!isBrowser) {
170
+ return signal(false).asReadonly();
171
+ }
172
+ const el = inject(ElementRef).nativeElement;
173
+ const pressed = signal(false, /* @ts-ignore */
174
+ ...(ngDevMode ? [{ debugName: "pressed" }] : /* istanbul ignore next */ []));
175
+ const onPointerDown = (event) => {
176
+ if (event.pointerType === 'mouse' && event.button !== 0) {
177
+ return;
178
+ }
179
+ if (disabled?.()) {
180
+ return;
181
+ }
182
+ pressed.set(true);
183
+ };
184
+ const onPointerEnd = () => {
185
+ pressed.set(false);
186
+ };
187
+ const onKeyDown = (event) => {
188
+ if (event.key !== 'Enter' && event.key !== ' ') {
189
+ return;
190
+ }
191
+ if (disabled?.()) {
192
+ return;
193
+ }
194
+ pressed.set(true);
195
+ };
196
+ const onKeyUp = (event) => {
197
+ if (event.key !== 'Enter' && event.key !== ' ') {
198
+ return;
199
+ }
200
+ pressed.set(false);
201
+ };
202
+ const onBlur = () => {
203
+ pressed.set(false);
204
+ };
205
+ el.addEventListener('pointerdown', onPointerDown);
206
+ el.addEventListener('pointerup', onPointerEnd);
207
+ el.addEventListener('pointerleave', onPointerEnd);
208
+ el.addEventListener('keydown', onKeyDown);
209
+ el.addEventListener('keyup', onKeyUp);
210
+ el.addEventListener('blur', onBlur);
211
+ inject(DestroyRef).onDestroy(() => {
212
+ el.removeEventListener('pointerdown', onPointerDown);
213
+ el.removeEventListener('pointerup', onPointerEnd);
214
+ el.removeEventListener('pointerleave', onPointerEnd);
215
+ el.removeEventListener('keydown', onKeyDown);
216
+ el.removeEventListener('keyup', onKeyUp);
217
+ el.removeEventListener('blur', onBlur);
218
+ });
219
+ return computed(() => (disabled?.() ? false : pressed()));
220
+ }
4
221
 
5
222
  /**
6
223
  * Headless implementation of the [WAI-ARIA Button pattern](https://www.w3.org/WAI/ARIA/apg/patterns/button/).
@@ -8,8 +225,10 @@ import { injectFocusVisible, injectHovered, injectPressed, createDefaults } from
8
225
  * Works on a native `<button>` host and on any arbitrary host element (e.g. `<div>`, `<span>`).
9
226
  * On a native button the platform handles Enter/Space → click synthesis and no `role` or
10
227
  * `tabindex` is emitted (the platform owns those). On a non-button host, `role="button"` and
11
- * `tabindex="0"` are applied, and keydown for Enter/Space synthesizes a click so the directive's
12
- * single `onClick` path handles all activations.
228
+ * `tabindex="0"` are applied, and the directive synthesizes a click through its single `onClick`
229
+ * path: Enter activates on `keydown`, while Space activates on `keyup` (its `keydown` always
230
+ * calls `preventDefault()` to stop the page scrolling, even when disabled) — matching native
231
+ * button and APG behavior.
13
232
  *
14
233
  * Disabled stays focusable: per the APG a disabled button must remain reachable by assistive
15
234
  * technology, so the native `disabled` attribute is never set. Instead `aria-disabled="true"` and
@@ -50,6 +269,7 @@ class ForButton {
50
269
  activate = output();
51
270
  #focused = signal(false, /* @ts-ignore */
52
271
  ...(ngDevMode ? [{ debugName: "#focused" }] : /* istanbul ignore next */ []));
272
+ #spaceHeld = false;
53
273
  #keyboardModality = injectFocusVisible();
54
274
  hovered = injectHovered({ disabled: this.disabled });
55
275
  pressed = injectPressed({ disabled: this.disabled });
@@ -73,9 +293,33 @@ class ForButton {
73
293
  if (this.#isNativeButton) {
74
294
  return;
75
295
  }
76
- if (event.key !== 'Enter' && event.key !== ' ') {
296
+ if (event.key === 'Enter') {
297
+ if (this.disabled()) {
298
+ return;
299
+ }
300
+ event.preventDefault();
301
+ this.#host.click();
302
+ return;
303
+ }
304
+ if (event.key === ' ') {
305
+ event.preventDefault();
306
+ if (this.disabled()) {
307
+ return;
308
+ }
309
+ this.#spaceHeld = true;
310
+ }
311
+ }
312
+ onKeyup(event) {
313
+ if (this.#isNativeButton) {
77
314
  return;
78
315
  }
316
+ if (event.key !== ' ') {
317
+ return;
318
+ }
319
+ if (!this.#spaceHeld) {
320
+ return;
321
+ }
322
+ this.#spaceHeld = false;
79
323
  if (this.disabled()) {
80
324
  return;
81
325
  }
@@ -87,9 +331,10 @@ class ForButton {
87
331
  }
88
332
  onFocusOut() {
89
333
  this.#focused.set(false);
334
+ this.#spaceHeld = false;
90
335
  }
91
336
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForButton, deps: [], target: i0.ɵɵFactoryTarget.Directive });
92
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForButton, isStandalone: true, selector: "[forButton]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activate: "activate" }, host: { listeners: { "click": "onClick($event)", "keydown": "onKeydown($event)", "focusin": "onFocusIn()", "focusout": "onFocusOut()" }, properties: { "attr.type": "resolvedType()", "attr.role": "resolvedRole()", "attr.tabindex": "resolvedTabindex()", "attr.aria-disabled": "disabled() ? 'true' : null", "attr.data-disabled": "disabled() ? '' : null", "attr.data-pressed": "pressed() ? '' : null", "attr.data-hovered": "hovered() ? '' : null", "attr.data-focus-visible": "focusVisible() ? '' : null" } }, exportAs: ["forButton"], ngImport: i0 });
337
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForButton, isStandalone: true, selector: "[forButton]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activate: "activate" }, host: { listeners: { "click": "onClick($event)", "keydown": "onKeydown($event)", "keyup": "onKeyup($event)", "focusin": "onFocusIn()", "focusout": "onFocusOut()" }, properties: { "attr.type": "resolvedType()", "attr.role": "resolvedRole()", "attr.tabindex": "resolvedTabindex()", "attr.aria-disabled": "disabled() ? 'true' : null", "attr.data-disabled": "disabled() ? '' : null", "attr.data-pressed": "pressed() ? '' : null", "attr.data-hovered": "hovered() ? '' : null", "attr.data-focus-visible": "focusVisible() ? '' : null" } }, exportAs: ["forButton"], ngImport: i0 });
93
338
  }
94
339
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForButton, decorators: [{
95
340
  type: Directive,
@@ -107,28 +352,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
107
352
  '[attr.data-focus-visible]': "focusVisible() ? '' : null",
108
353
  '(click)': 'onClick($event)',
109
354
  '(keydown)': 'onKeydown($event)',
355
+ '(keyup)': 'onKeyup($event)',
110
356
  '(focusin)': 'onFocusIn()',
111
357
  '(focusout)': 'onFocusOut()',
112
358
  },
113
359
  }]
114
360
  }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], activate: [{ type: i0.Output, args: ["activate"] }] } });
115
361
 
116
- const FALLBACK = {};
117
- const { token, provideDefaults } = createDefaults('FOR_BUTTON_DEFAULTS', FALLBACK);
118
- /** Token holding the resolved button defaults for the current scope. */
119
- const FOR_BUTTON_DEFAULTS = token;
120
- /**
121
- * Configures forty-cdk button defaults for this injector scope. Partial
122
- * overrides inherit unspecified keys from the parent scope (or library
123
- * defaults at the root).
124
- */
125
- function provideForButtonDefaults(defaults = {}) {
126
- return provideDefaults(defaults);
127
- }
128
-
129
362
  /**
130
363
  * Generated bundle index. Do not edit.
131
364
  */
132
365
 
133
- export { FOR_BUTTON_DEFAULTS, ForButton, provideForButtonDefaults };
366
+ export { ForButton };
134
367
  //# sourceMappingURL=forty-cdk-button.mjs.map