@joanroucoux/cairn-ui 0.10.0 → 0.10.1

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 (37) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +2 -0
  3. package/fesm2022/joanroucoux-cairn-ui-async.mjs.map +1 -1
  4. package/fesm2022/joanroucoux-cairn-ui-choice-chips.mjs.map +1 -1
  5. package/fesm2022/joanroucoux-cairn-ui-dialog.mjs.map +1 -1
  6. package/fesm2022/joanroucoux-cairn-ui-donut.mjs +4 -4
  7. package/fesm2022/joanroucoux-cairn-ui-donut.mjs.map +1 -1
  8. package/fesm2022/joanroucoux-cairn-ui-drawer.mjs.map +1 -1
  9. package/fesm2022/joanroucoux-cairn-ui-empty.mjs.map +1 -1
  10. package/fesm2022/joanroucoux-cairn-ui-fact.mjs.map +1 -1
  11. package/fesm2022/joanroucoux-cairn-ui-group-header.mjs.map +1 -1
  12. package/fesm2022/joanroucoux-cairn-ui-input.mjs.map +1 -1
  13. package/fesm2022/joanroucoux-cairn-ui-menu.mjs +8 -1
  14. package/fesm2022/joanroucoux-cairn-ui-menu.mjs.map +1 -1
  15. package/fesm2022/joanroucoux-cairn-ui-result-group.mjs.map +1 -1
  16. package/fesm2022/joanroucoux-cairn-ui-row.mjs.map +1 -1
  17. package/fesm2022/joanroucoux-cairn-ui-stale-link.mjs.map +1 -1
  18. package/fesm2022/joanroucoux-cairn-ui-table.mjs.map +1 -1
  19. package/fesm2022/joanroucoux-cairn-ui-toast.mjs +1 -1
  20. package/fesm2022/joanroucoux-cairn-ui-toast.mjs.map +1 -1
  21. package/mcp-manifest.json +1 -0
  22. package/package.json +5 -2
  23. package/types/joanroucoux-cairn-ui-async.d.ts +3 -3
  24. package/types/joanroucoux-cairn-ui-choice-chips.d.ts +2 -2
  25. package/types/joanroucoux-cairn-ui-dialog.d.ts +3 -0
  26. package/types/joanroucoux-cairn-ui-donut.d.ts +1 -1
  27. package/types/joanroucoux-cairn-ui-drawer.d.ts +1 -1
  28. package/types/joanroucoux-cairn-ui-empty.d.ts +3 -3
  29. package/types/joanroucoux-cairn-ui-fact.d.ts +1 -1
  30. package/types/joanroucoux-cairn-ui-group-header.d.ts +1 -1
  31. package/types/joanroucoux-cairn-ui-input.d.ts +3 -0
  32. package/types/joanroucoux-cairn-ui-menu.d.ts +3 -3
  33. package/types/joanroucoux-cairn-ui-result-group.d.ts +3 -3
  34. package/types/joanroucoux-cairn-ui-row.d.ts +1 -1
  35. package/types/joanroucoux-cairn-ui-stale-link.d.ts +2 -2
  36. package/types/joanroucoux-cairn-ui-table.d.ts +16 -6
  37. package/types/joanroucoux-cairn-ui-toast.d.ts +3 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.1
4
+
5
+ ### Added
6
+
7
+ - The package ships `mcp-manifest.json` (export `@joanroucoux/cairn-ui/mcp-manifest.json`), the machine-readable
8
+ description of this version that the `@joanroucoux/cairn-ui-mcp` server answers from.
9
+
10
+ ### Breaking
11
+
12
+ - `UiToasts.showError(message, closeLabel)`: the close label defaults to `'Close'`; it was `'Fermer'`.
13
+ - Migration: an app that relied on the default and wants another label passes it as the second argument; a test
14
+ that found the button with `getByRole('button', { name: 'Fermer' })` looks for `'Close'` instead.
15
+
16
+ ### Changed
17
+
18
+ - `ui-donut`: the accessible name of the ring reads `label: detail`, without the space before the colon.
19
+ - Stories, specs, JSDoc examples, story descriptions and docs are in English, following the Cairn design system
20
+ wording and glossary (holding, wrapper, avg. cost, price as of). Amounts and percentages keep the French number
21
+ format. Stories whose export name was French are renamed (`Expanded`, `Holdings`, `AddHolding`...), which changes
22
+ their Storybook URLs.
23
+
24
+ ### Fixed
25
+
26
+ - `ui-menu`: a menu of fractional width, placed against the right edge of the window, stays inside the 8px margin.
27
+ It was placed from `offsetWidth`, which rounds to an integer, and overflowed by up to half a pixel.
28
+
3
29
  ## 0.10.0
4
30
 
5
31
  ### Added
package/README.md CHANGED
@@ -65,5 +65,7 @@ export class Example {}
65
65
 
66
66
  - [Repository README](https://github.com/JoanRoucoux/cairn-ui#readme): the component list, design principles and development guide
67
67
  - [Changelog](https://github.com/JoanRoucoux/cairn-ui/blob/main/projects/ui/CHANGELOG.md)
68
+ - [MCP server](https://github.com/JoanRoucoux/cairn-ui/tree/main/projects/mcp#readme): lets a coding agent look up the
69
+ components, tokens and setup of the version you installed
68
70
 
69
71
  MIT licensed.
@@ -1 +1 @@
1
- {"version":3,"file":"joanroucoux-cairn-ui-async.mjs","names":[],"sources":["../../../projects/ui/async/delayed-state.ts","../../../projects/ui/async/async.ts"],"sourcesContent":["import { DestroyRef, type Signal, effect, inject, signal, untracked } from '@angular/core';\n\nimport type { AsyncState } from './async';\n\nexport const SKELETON_DELAY_MS = 150;\nexport const SKELETON_MIN_MS = 400;\n\n/**\n * Turns the live state of a call into the state to draw: `loading` only appears once the call has\n * lasted 150 ms and, once shown, stays at least 400 ms. Until then the previous state stays on\n * screen; `null` means nothing to draw yet (a first load under 150 ms). Call it in an injection context.\n *\n * @example\n * protected readonly shown = delayedState(this.state);\n */\nexport function delayedState(source: Signal<AsyncState>): Signal<AsyncState | null> {\n const displayed = signal<AsyncState | null>(null);\n let timer: ReturnType<typeof setTimeout> | undefined;\n let shownAt = 0;\n\n const clear = (): void => {\n clearTimeout(timer);\n timer = undefined;\n };\n\n inject(DestroyRef).onDestroy(clear);\n\n effect(() => {\n const state = source();\n\n untracked(() => {\n if (state === 'loading') {\n if (displayed() === 'loading') {\n clear();\n } else {\n timer = setTimeout(() => {\n timer = undefined;\n shownAt = Date.now();\n displayed.set('loading');\n }, SKELETON_DELAY_MS);\n }\n\n return;\n }\n\n clear();\n\n const remaining = shownAt + SKELETON_MIN_MS - Date.now();\n\n if (displayed() === 'loading' && remaining > 0) {\n timer = setTimeout(() => {\n timer = undefined;\n displayed.set(untracked(source));\n }, remaining);\n } else {\n displayed.set(state);\n }\n });\n });\n\n return displayed.asReadonly();\n}\n","import { Component, booleanAttribute, computed, input, output } from '@angular/core';\n\nimport { holdTransitionsUntilRendered } from '@joanroucoux/cairn-ui/motion';\n\nimport { delayedState } from './delayed-state';\n\n/** Available async block states. */\nexport const ASYNC_STATES = ['loading', 'error', 'empty', 'ready'] as const;\nexport type AsyncState = (typeof ASYNC_STATES)[number];\n\n/** Looks of the error block. */\nexport const ASYNC_VARIANTS = ['elevated', 'plain', 'emphasis', 'inline'] as const;\nexport type AsyncVariant = (typeof ASYNC_VARIANTS)[number];\n\n/** Alignments of the error block; `auto` is centred from 64rem. */\nexport const ASYNC_ALIGNS = ['start', 'center', 'auto'] as const;\nexport type AsyncAlign = (typeof ASYNC_ALIGNS)[number];\n\nconst RETRY_BASE_CLASSES =\n 'relative inline-flex items-center justify-center rounded-control font-medium whitespace-nowrap cursor-pointer select-none touch-manipulation transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring) text-label';\n\nconst RETRY_OUTLINE_CLASSES =\n 'bg-(--card) text-(--foreground) shadow-[inset_0_0_0_1px_var(--border)] hover:bg-(--glow)';\n\nconst RETRY_PRIMARY_CLASSES = 'bg-(--primary) text-(--primary-foreground) hover:bg-(--primary-to)';\n\nconst RETRY_BLOCK_CLASSES = 'min-h-(--row-min) gap-2 pl-3 pr-4';\n\nconst RETRY_INLINE_CLASSES = 'h-11 pointer-fine:h-8 flex-none';\n\nconst RETRY_INLINE_ICON_CLASSES = 'gap-1.5 pl-2.5 pr-3';\n\nconst RETRY_INLINE_TEXT_CLASSES = 'px-3';\n\nconst ELEVATED_SURFACE_CLASSES = 'rounded-control bg-(--elevated)';\n\nconst CARD_SURFACE_CLASSES = 'rounded-container bg-(--card) shadow-[inset_0_0_0_1px_var(--border)]';\n\ntype BlockSpec = {\n surface: string;\n gap: string;\n padding: Record<AsyncAlign, string>;\n title: string;\n message: Record<AsyncAlign, string>;\n retryMargin: Record<AsyncAlign, string>;\n retry: string;\n};\n\nconst BLOCK_SPECS: Record<Exclude<AsyncVariant, 'inline'>, BlockSpec> = {\n elevated: {\n surface: ELEVATED_SURFACE_CLASSES,\n gap: 'gap-1',\n padding: { start: 'p-4', center: 'px-4 py-10', auto: 'p-4 lg:py-10' },\n title: 'text-body font-medium',\n message: { start: 'text-label', center: 'text-label', auto: 'text-label' },\n retryMargin: { start: 'mt-2', center: 'mt-2', auto: 'mt-2' },\n retry: RETRY_OUTLINE_CLASSES,\n },\n plain: {\n surface: '',\n gap: 'gap-1',\n padding: { start: 'px-4 py-5', center: 'px-4 py-12', auto: 'px-4 py-5 lg:py-12' },\n title: 'text-body leading-[normal] font-medium',\n message: {\n start: 'text-label leading-[normal]',\n center: 'text-label leading-[normal]',\n auto: 'text-label leading-[normal]',\n },\n retryMargin: { start: 'mt-2', center: 'mt-2', auto: 'mt-2' },\n retry: RETRY_OUTLINE_CLASSES,\n },\n emphasis: {\n surface: '',\n gap: 'gap-3',\n padding: { start: 'px-4 py-6', center: 'px-4 py-14', auto: 'px-4 py-6 lg:py-14' },\n title: 'text-title font-semibold',\n message: {\n start: 'text-label',\n center: 'text-body leading-[normal]',\n auto: 'text-label lg:text-body lg:leading-[normal]',\n },\n retryMargin: { start: 'mt-1', center: 'mt-2', auto: 'mt-1 lg:mt-2' },\n retry: RETRY_PRIMARY_CLASSES,\n },\n};\n\nconst ALIGN_CLASSES: Record<AsyncAlign, string> = {\n start: 'items-start',\n center: 'items-center text-center',\n auto: 'items-start lg:items-center lg:text-center',\n};\n\nconst INLINE_BOX_CLASSES = 'flex-row items-center justify-between gap-3 pt-1.5 pr-1.5 pb-2 pl-2.5';\n\n/**\n * The three states of a block that depends on a service call, next to the ready content.\n *\n * @example\n * <ui-async [state]=\"totalState()\" errorTitle=\"Total could not be loaded\" errorMessage=\"The server did not answer.\" retryLabel=\"Retry\" (retry)=\"reload()\">\n * <span asyncLoading><ui-skeleton shape=\"figure\" /></span>\n * <span asyncEmpty>Add a holding to see your total.</span>\n * <span>{{ total() }}</span>\n * </ui-async>\n */\n@Component({\n selector: 'ui-async',\n template: `\n @switch (shown()) {\n @case (null) {}\n @case ('loading') {\n <ng-content select=\"[asyncLoading]\" />\n }\n @case ('error') {\n <div aria-live=\"polite\" role=\"alert\" [class]=\"boxClasses()\">\n @if (variant() === 'inline') {\n <span class=\"text-label text-(--muted-foreground)\">{{ errorMessage() }}</span>\n } @else {\n @if (variant() === 'emphasis') {\n <h2 [class]=\"titleClasses()\">{{ errorTitle() }}</h2>\n } @else {\n <span [class]=\"titleClasses()\">{{ errorTitle() }}</span>\n }\n <span class=\"text-(--muted-foreground)\" [class]=\"messageClasses()\">{{ errorMessage() }}</span>\n }\n <button type=\"button\" [class]=\"retryButtonClasses()\" (click)=\"retry.emit()\">\n @if (retryIcon() || variant() !== 'inline') {\n <svg\n aria-hidden=\"true\"\n class=\"block flex-none fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n viewBox=\"0 0 24 24\"\n [attr.height]=\"iconSize()\"\n [attr.width]=\"iconSize()\"\n >\n <path d=\"M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8\" />\n <path d=\"M21 3v5h-5\" />\n </svg>\n }\n {{ retryLabel() }}\n </button>\n </div>\n }\n @case ('empty') {\n <ng-content select=\"[asyncEmpty]\" />\n }\n @default {\n <ng-content />\n }\n }\n `,\n host: {\n '[attr.aria-busy]': \"state() === 'loading' || null\",\n '[class.flex]': 'filling()',\n '[class.flex-col]': 'filling()',\n },\n})\nexport class UiAsync {\n readonly state = input.required<AsyncState>();\n readonly errorTitle = input<string>();\n readonly errorMessage = input<string>();\n readonly retryLabel = input<string>();\n /** Draws the rotate icon in the retry button. Only the `inline` variant honours `false`. */\n readonly retryIcon = input(true, { transform: booleanAttribute });\n readonly variant = input<AsyncVariant>('elevated');\n readonly align = input<AsyncAlign>('start');\n readonly card = input(false, { transform: booleanAttribute });\n readonly fill = input(false, { transform: booleanAttribute });\n readonly retry = output<void>();\n\n protected readonly shown = delayedState(this.state);\n\n protected readonly filling = computed(() => this.fill() && this.shown() === 'error');\n\n protected readonly iconSize = computed(() => (this.variant() === 'inline' ? 16 : 18));\n\n readonly #spec = computed(() => {\n const variant = this.variant();\n\n return variant === 'inline' ? null : BLOCK_SPECS[variant];\n });\n\n protected readonly boxClasses = computed(() => {\n const spec = this.#spec();\n const fill = this.fill() ? ' flex-1 justify-center' : '';\n\n if (spec === null) {\n return `flex ${INLINE_BOX_CLASSES}${this.card() ? ` ${CARD_SURFACE_CLASSES}` : ''}${this.fill() ? ' flex-1' : ''}`;\n }\n\n const surface = this.card() ? CARD_SURFACE_CLASSES : spec.surface;\n\n return `flex flex-col ${surface} ${spec.gap} ${ALIGN_CLASSES[this.align()]} ${spec.padding[this.align()]}${fill}`;\n });\n\n protected readonly titleClasses = computed(() => this.#spec()!.title);\n\n protected readonly messageClasses = computed(() => this.#spec()!.message[this.align()]);\n\n protected readonly retryButtonClasses = computed(() => {\n const spec = this.#spec();\n\n if (spec === null) {\n const spacing = this.retryIcon() ? RETRY_INLINE_ICON_CLASSES : RETRY_INLINE_TEXT_CLASSES;\n\n return `${RETRY_BASE_CLASSES} ${RETRY_OUTLINE_CLASSES} ${RETRY_INLINE_CLASSES} ${spacing}`;\n }\n\n return `${RETRY_BASE_CLASSES} ${spec.retry} ${RETRY_BLOCK_CLASSES} ${spec.retryMargin[this.align()]}`;\n });\n\n constructor() {\n holdTransitionsUntilRendered();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"joanroucoux-cairn-ui-async.mjs","names":[],"sources":["../../../projects/ui/async/delayed-state.ts","../../../projects/ui/async/async.ts"],"sourcesContent":["import { DestroyRef, type Signal, effect, inject, signal, untracked } from '@angular/core';\n\nimport type { AsyncState } from './async';\n\nexport const SKELETON_DELAY_MS = 150;\nexport const SKELETON_MIN_MS = 400;\n\n/**\n * Turns the live state of a call into the state to draw: `loading` only appears once the call has\n * lasted 150 ms and, once shown, stays at least 400 ms. Until then the previous state stays on\n * screen; `null` means nothing to draw yet (a first load under 150 ms). Call it in an injection context.\n *\n * @example\n * protected readonly shown = delayedState(this.state);\n */\nexport function delayedState(source: Signal<AsyncState>): Signal<AsyncState | null> {\n const displayed = signal<AsyncState | null>(null);\n let timer: ReturnType<typeof setTimeout> | undefined;\n let shownAt = 0;\n\n const clear = (): void => {\n clearTimeout(timer);\n timer = undefined;\n };\n\n inject(DestroyRef).onDestroy(clear);\n\n effect(() => {\n const state = source();\n\n untracked(() => {\n if (state === 'loading') {\n if (displayed() === 'loading') {\n clear();\n } else {\n timer = setTimeout(() => {\n timer = undefined;\n shownAt = Date.now();\n displayed.set('loading');\n }, SKELETON_DELAY_MS);\n }\n\n return;\n }\n\n clear();\n\n const remaining = shownAt + SKELETON_MIN_MS - Date.now();\n\n if (displayed() === 'loading' && remaining > 0) {\n timer = setTimeout(() => {\n timer = undefined;\n displayed.set(untracked(source));\n }, remaining);\n } else {\n displayed.set(state);\n }\n });\n });\n\n return displayed.asReadonly();\n}\n","import { Component, booleanAttribute, computed, input, output } from '@angular/core';\n\nimport { holdTransitionsUntilRendered } from '@joanroucoux/cairn-ui/motion';\n\nimport { delayedState } from './delayed-state';\n\n/** Available async block states. */\nexport const ASYNC_STATES = ['loading', 'error', 'empty', 'ready'] as const;\nexport type AsyncState = (typeof ASYNC_STATES)[number];\n\n/** Looks of the error block. */\nexport const ASYNC_VARIANTS = ['elevated', 'plain', 'emphasis', 'inline'] as const;\nexport type AsyncVariant = (typeof ASYNC_VARIANTS)[number];\n\n/** Alignments of the error block; `auto` is centered from 64rem. */\nexport const ASYNC_ALIGNS = ['start', 'center', 'auto'] as const;\nexport type AsyncAlign = (typeof ASYNC_ALIGNS)[number];\n\nconst RETRY_BASE_CLASSES =\n 'relative inline-flex items-center justify-center rounded-control font-medium whitespace-nowrap cursor-pointer select-none touch-manipulation transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring) text-label';\n\nconst RETRY_OUTLINE_CLASSES =\n 'bg-(--card) text-(--foreground) shadow-[inset_0_0_0_1px_var(--border)] hover:bg-(--glow)';\n\nconst RETRY_PRIMARY_CLASSES = 'bg-(--primary) text-(--primary-foreground) hover:bg-(--primary-to)';\n\nconst RETRY_BLOCK_CLASSES = 'min-h-(--row-min) gap-2 pl-3 pr-4';\n\nconst RETRY_INLINE_CLASSES = 'h-11 pointer-fine:h-8 flex-none';\n\nconst RETRY_INLINE_ICON_CLASSES = 'gap-1.5 pl-2.5 pr-3';\n\nconst RETRY_INLINE_TEXT_CLASSES = 'px-3';\n\nconst ELEVATED_SURFACE_CLASSES = 'rounded-control bg-(--elevated)';\n\nconst CARD_SURFACE_CLASSES = 'rounded-container bg-(--card) shadow-[inset_0_0_0_1px_var(--border)]';\n\ntype BlockSpec = {\n surface: string;\n gap: string;\n padding: Record<AsyncAlign, string>;\n title: string;\n message: Record<AsyncAlign, string>;\n retryMargin: Record<AsyncAlign, string>;\n retry: string;\n};\n\nconst BLOCK_SPECS: Record<Exclude<AsyncVariant, 'inline'>, BlockSpec> = {\n elevated: {\n surface: ELEVATED_SURFACE_CLASSES,\n gap: 'gap-1',\n padding: { start: 'p-4', center: 'px-4 py-10', auto: 'p-4 lg:py-10' },\n title: 'text-body font-medium',\n message: { start: 'text-label', center: 'text-label', auto: 'text-label' },\n retryMargin: { start: 'mt-2', center: 'mt-2', auto: 'mt-2' },\n retry: RETRY_OUTLINE_CLASSES,\n },\n plain: {\n surface: '',\n gap: 'gap-1',\n padding: { start: 'px-4 py-5', center: 'px-4 py-12', auto: 'px-4 py-5 lg:py-12' },\n title: 'text-body leading-[normal] font-medium',\n message: {\n start: 'text-label leading-[normal]',\n center: 'text-label leading-[normal]',\n auto: 'text-label leading-[normal]',\n },\n retryMargin: { start: 'mt-2', center: 'mt-2', auto: 'mt-2' },\n retry: RETRY_OUTLINE_CLASSES,\n },\n emphasis: {\n surface: '',\n gap: 'gap-3',\n padding: { start: 'px-4 py-6', center: 'px-4 py-14', auto: 'px-4 py-6 lg:py-14' },\n title: 'text-title font-semibold',\n message: {\n start: 'text-label',\n center: 'text-body leading-[normal]',\n auto: 'text-label lg:text-body lg:leading-[normal]',\n },\n retryMargin: { start: 'mt-1', center: 'mt-2', auto: 'mt-1 lg:mt-2' },\n retry: RETRY_PRIMARY_CLASSES,\n },\n};\n\nconst ALIGN_CLASSES: Record<AsyncAlign, string> = {\n start: 'items-start',\n center: 'items-center text-center',\n auto: 'items-start lg:items-center lg:text-center',\n};\n\nconst INLINE_BOX_CLASSES = 'flex-row items-center justify-between gap-3 pt-1.5 pr-1.5 pb-2 pl-2.5';\n\n/**\n * The three states of a block that depends on a service call, next to the ready content.\n *\n * @example\n * <ui-async [state]=\"totalState()\" errorTitle=\"The total couldn’t be loaded\" errorMessage=\"The server didn’t respond.\" retryLabel=\"Try again\" (retry)=\"reload()\">\n * <span asyncLoading><ui-skeleton shape=\"figure\" /></span>\n * <span asyncEmpty>Add a holding to see your total.</span>\n * <span>{{ total() }}</span>\n * </ui-async>\n */\n@Component({\n selector: 'ui-async',\n template: `\n @switch (shown()) {\n @case (null) {}\n @case ('loading') {\n <ng-content select=\"[asyncLoading]\" />\n }\n @case ('error') {\n <div aria-live=\"polite\" role=\"alert\" [class]=\"boxClasses()\">\n @if (variant() === 'inline') {\n <span class=\"text-label text-(--muted-foreground)\">{{ errorMessage() }}</span>\n } @else {\n @if (variant() === 'emphasis') {\n <h2 [class]=\"titleClasses()\">{{ errorTitle() }}</h2>\n } @else {\n <span [class]=\"titleClasses()\">{{ errorTitle() }}</span>\n }\n <span class=\"text-(--muted-foreground)\" [class]=\"messageClasses()\">{{ errorMessage() }}</span>\n }\n <button type=\"button\" [class]=\"retryButtonClasses()\" (click)=\"retry.emit()\">\n @if (retryIcon() || variant() !== 'inline') {\n <svg\n aria-hidden=\"true\"\n class=\"block flex-none fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n viewBox=\"0 0 24 24\"\n [attr.height]=\"iconSize()\"\n [attr.width]=\"iconSize()\"\n >\n <path d=\"M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8\" />\n <path d=\"M21 3v5h-5\" />\n </svg>\n }\n {{ retryLabel() }}\n </button>\n </div>\n }\n @case ('empty') {\n <ng-content select=\"[asyncEmpty]\" />\n }\n @default {\n <ng-content />\n }\n }\n `,\n host: {\n '[attr.aria-busy]': \"state() === 'loading' || null\",\n '[class.flex]': 'filling()',\n '[class.flex-col]': 'filling()',\n },\n})\nexport class UiAsync {\n readonly state = input.required<AsyncState>();\n readonly errorTitle = input<string>();\n readonly errorMessage = input<string>();\n readonly retryLabel = input<string>();\n /** Draws the rotate icon in the retry button. Only the `inline` variant honors `false`. */\n readonly retryIcon = input(true, { transform: booleanAttribute });\n readonly variant = input<AsyncVariant>('elevated');\n readonly align = input<AsyncAlign>('start');\n readonly card = input(false, { transform: booleanAttribute });\n readonly fill = input(false, { transform: booleanAttribute });\n readonly retry = output<void>();\n\n protected readonly shown = delayedState(this.state);\n\n protected readonly filling = computed(() => this.fill() && this.shown() === 'error');\n\n protected readonly iconSize = computed(() => (this.variant() === 'inline' ? 16 : 18));\n\n readonly #spec = computed(() => {\n const variant = this.variant();\n\n return variant === 'inline' ? null : BLOCK_SPECS[variant];\n });\n\n protected readonly boxClasses = computed(() => {\n const spec = this.#spec();\n const fill = this.fill() ? ' flex-1 justify-center' : '';\n\n if (spec === null) {\n return `flex ${INLINE_BOX_CLASSES}${this.card() ? ` ${CARD_SURFACE_CLASSES}` : ''}${this.fill() ? ' flex-1' : ''}`;\n }\n\n const surface = this.card() ? CARD_SURFACE_CLASSES : spec.surface;\n\n return `flex flex-col ${surface} ${spec.gap} ${ALIGN_CLASSES[this.align()]} ${spec.padding[this.align()]}${fill}`;\n });\n\n protected readonly titleClasses = computed(() => this.#spec()!.title);\n\n protected readonly messageClasses = computed(() => this.#spec()!.message[this.align()]);\n\n protected readonly retryButtonClasses = computed(() => {\n const spec = this.#spec();\n\n if (spec === null) {\n const spacing = this.retryIcon() ? RETRY_INLINE_ICON_CLASSES : RETRY_INLINE_TEXT_CLASSES;\n\n return `${RETRY_BASE_CLASSES} ${RETRY_OUTLINE_CLASSES} ${RETRY_INLINE_CLASSES} ${spacing}`;\n }\n\n return `${RETRY_BASE_CLASSES} ${spec.retry} ${RETRY_BLOCK_CLASSES} ${spec.retryMargin[this.align()]}`;\n });\n\n constructor() {\n holdTransitionsUntilRendered();\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"joanroucoux-cairn-ui-choice-chips.mjs","names":[],"sources":["../../../projects/ui/choice-chips/choice-chips.ts"],"sourcesContent":["import {\n Component,\n ElementRef,\n booleanAttribute,\n computed,\n forwardRef,\n inject,\n input,\n model,\n output,\n viewChildren,\n} from '@angular/core';\n\nimport { UI_CONTROL, type UiControl, type UiControlError } from '@joanroucoux/cairn-ui/control';\nimport { CONTROL_SURFACE_CLASSES, type ControlSurface } from '@joanroucoux/cairn-ui/input';\n\nexport type ChoiceChipOption = {\n value: string;\n label: string;\n};\n\nconst CHIP_CLASSES =\n 'h-10 px-3.5 pointer-fine:h-8 pointer-fine:px-3 rounded-pill text-label font-medium cursor-pointer transition-transform duration-(--duration-press) ease-out active:scale-(--press-scale) disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring)';\n\nconst SELECTED_CLASSES = 'bg-(--primary) text-(--primary-foreground)';\n\nconst UNSELECTED_CLASSES = 'text-(--foreground) shadow-[inset_0_0_0_1px_var(--border)]';\n\nconst nextId = (() => {\n let count = 0;\n\n return (): string => `ui-choice-chips-${++count}`;\n})();\n\n/**\n * Exclusive choice among a handful of named options, shown as wrapping pills. Binds to Signal Forms\n * with `[formField]`.\n *\n * @example\n * <ui-choice-chips label=\"Envelope\" [options]=\"envelopes\" [(value)]=\"envelope\" />\n *\n * @example\n * <ui-choice-chips label=\"Envelope\" [options]=\"envelopes\" [formField]=\"form.envelope\" />\n */\n@Component({\n selector: 'ui-choice-chips',\n template: `\n <div\n class=\"flex flex-col gap-1.5\"\n role=\"radiogroup\"\n [attr.aria-invalid]=\"invalid() || null\"\n [attr.aria-label]=\"ariaLabel() ?? null\"\n [attr.aria-labelledby]=\"labelledBy()\"\n [attr.aria-required]=\"required() || null\"\n (focusout)=\"onFocusOut($event)\"\n >\n @if (label()) {\n <span class=\"text-label leading-[17px] font-medium text-(--muted-foreground)\" [id]=\"labelId\">{{\n label()\n }}</span>\n }\n\n <div class=\"flex flex-wrap gap-1.5\">\n @for (option of options(); track option.value; let index = $index) {\n <button\n #radio\n role=\"radio\"\n type=\"button\"\n [attr.aria-checked]=\"option.value === value()\"\n [attr.tabindex]=\"tabIndexFor(option.value)\"\n [class]=\"chipClasses(option.value)\"\n [disabled]=\"disabled()\"\n (click)=\"select(option.value)\"\n (keydown)=\"onKeydown($event, index)\"\n >\n {{ option.label }}\n </button>\n }\n </div>\n </div>\n `,\n providers: [{ provide: UI_CONTROL, useExisting: forwardRef(() => UiChoiceChips) }],\n})\nexport class UiChoiceChips implements UiControl {\n readonly options = input.required<ChoiceChipOption[]>();\n readonly value = model.required<string>();\n readonly label = input<string>();\n readonly ariaLabel = input<string>();\n readonly ariaLabelledby = input<string>();\n readonly surface = input<ControlSurface>('background');\n readonly disabled = input(false, { transform: booleanAttribute });\n readonly errors = input<readonly UiControlError[]>([]);\n readonly invalid = input(false, { transform: booleanAttribute });\n readonly required = input(false, { transform: booleanAttribute });\n readonly touched = input(false);\n readonly touch = output<void>();\n\n protected readonly labelId = nextId();\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef);\n protected readonly radios = viewChildren<ElementRef<HTMLButtonElement>>('radio');\n\n protected readonly labelledBy = computed(() => this.ariaLabelledby() ?? (this.label() ? this.labelId : null));\n\n protected chipClasses(optionValue: string): string {\n const state =\n optionValue === this.value()\n ? SELECTED_CLASSES\n : `${CONTROL_SURFACE_CLASSES[this.surface()]} ${UNSELECTED_CLASSES}`;\n\n return `${CHIP_CLASSES} ${state}`;\n }\n\n protected tabIndexFor(optionValue: string): number {\n const opts = this.options();\n const tabStopValue = opts.some((option) => option.value === this.value()) ? this.value() : opts[0]?.value;\n\n return optionValue === tabStopValue ? 0 : -1;\n }\n\n protected select(optionValue: string): void {\n this.value.set(optionValue);\n }\n\n protected onFocusOut(event: FocusEvent): void {\n if (!this.#host.nativeElement.contains(event.relatedTarget as Node | null)) {\n this.touch.emit();\n }\n }\n\n protected onKeydown(event: KeyboardEvent, index: number): void {\n const opts = this.options();\n const target = {\n ArrowRight: index + 1,\n ArrowDown: index + 1,\n ArrowLeft: index - 1,\n ArrowUp: index - 1,\n Home: 0,\n End: opts.length - 1,\n }[event.key];\n\n if (target === undefined) {\n return;\n }\n event.preventDefault();\n\n const nextIndex = ((target % opts.length) + opts.length) % opts.length;\n\n this.value.set(opts[nextIndex]!.value);\n this.radios()[nextIndex]!.nativeElement.focus();\n }\n}\n"],"mappings":";;;;AAqBA,MAAM,eACJ;AAEF,MAAM,mBAAmB;AAEzB,MAAM,qBAAqB;AAE3B,MAAM,gBAAe;CACnB,IAAI,QAAQ;CAEZ,aAAqB,mBAAmB,EAAE;AAC5C,EAAA,CAAE;AAmDF,IAAa,gBAAb,MAAa,cAAa;CACf,UAAU,MAAM,SAA8B,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAC9C,QAAQ,MAAM,SAAkB,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAChC,QAAQ,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CACvB,YAAY,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAC3B,iBAAiB,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAChC,UAAU,MAAsB,cAAa,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAC7C,WAAW,MAAM,OAAK;EAAA,GAAA,YAAA,EAAA,WAAA,WAAA,+BAAA,CAAA;EAAI,WAAW;CAAgB,CAAA;CACrD,SAAS,MAAiC,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAC7C,UAAU,MAAM,OAAK;EAAA,GAAA,YAAA,EAAA,WAAA,UAAA,+BAAA,CAAA;EAAI,WAAW;CAAgB,CAAA;CACpD,WAAW,MAAM,OAAK;EAAA,GAAA,YAAA,EAAA,WAAA,WAAA,+BAAA,CAAA;EAAI,WAAW;CAAgB,CAAA;CACrD,UAAU,MAAM,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CACtB,QAAQ,OAAM;CAEJ,UAAU,OAAM;CAC1B,QAAQ,OAAgC,UAAU;CACxC,SAAS,aAA4C,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAE7D,aAAa,eAAe,KAAK,eAAc,MAAO,KAAK,MAAK,IAAK,KAAK,UAAU,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAEnG,YAAY,aAAmB;EACvC,MAAM,QACJ,gBAAgB,KAAK,MAAK,IACtB,mBACA,GAAG,wBAAwB,KAAK,QAAO,GAAG,GAAI;EAEpD,OAAO,GAAG,aAAY,GAAI;CAC5B;CAEU,YAAY,aAAmB;EACvC,MAAM,OAAO,KAAK,QAAO;EAGzB,OAAO,iBAFc,KAAK,MAAM,WAAW,OAAO,UAAU,KAAK,MAAK,CAAE,IAAI,KAAK,MAAK,IAAK,KAAK,EAAE,EAAE,SAE9D,IAAI;CAC5C;CAEU,OAAO,aAAmB;EAClC,KAAK,MAAM,IAAI,WAAW;CAC5B;CAEU,WAAW,OAAiB;EACpC,IAAI,CAAC,KAAK,MAAM,cAAc,SAAS,MAAM,aAA4B,GACvE,KAAK,MAAM,KAAI;CAEnB;CAEU,UAAU,OAAsB,OAAa;EACrD,MAAM,OAAO,KAAK,QAAO;EACzB,MAAM,SAAS;GACb,YAAY,QAAQ;GACpB,WAAW,QAAQ;GACnB,WAAW,QAAQ;GACnB,SAAS,QAAQ;GACjB,MAAM;GACN,KAAK,KAAK,SAAS;EACpB,EAAC,MAAM;EAER,IAAI,WAAW,QACb;EAEF,MAAM,eAAc;EAEpB,MAAM,aAAc,SAAS,KAAK,SAAU,KAAK,UAAU,KAAK;EAEhE,KAAK,MAAM,IAAI,KAAK,UAAW,CAAC,KAAK;EACrC,KAAK,OAAM,CAAE,CAAC,UAAW,CAAC,cAAc,MAAK;CAC/C;CAlEW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAa,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAb,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAa,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,SAAA;GAAA,OAAA;GAAA,OAAA;EAAA;EAAA,WAFb,CAAC;GAAE,SAAS;GAAY,aAAa,iBAAiB,aAAa;EAAC,CAAE;EAAC,aAAA,CAAA;GAAA,cAAA;GAAA,WAAA,CAAA,OAAA;GAAA,aAAA;GAAA,UAAA;EAAA,CAAA;EAAA,UAAA;EAAA,UAnCxE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkCT,UAAA;CAAA,CAAA;;AAGU,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAa,YAAA,CAAA;EAvCzB,MAAA;EAAU,MAAA,CAAA;GACT,UAAU;GACV,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCV,WAAW,CAAC;IAAE,SAAS;IAAY,aAAa,iBAAgB,aAAc;GAAC,CAAE;EAClF,CAAA;CAiByE,CAAA;CAAA,gBAAA;EAAA,SAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,OAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,GAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,aAAA;EAAA,CAAA;EAAA,OAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,WAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,gBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,SAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,UAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,QAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,SAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,UAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,SAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,OAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,OAAA;EAAA,CAAA;EAAA,QAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,SAAO,EAAA,UAAA,KAAA,CAAA;EAAA,CAAA;CAAA;AAAA,CAAA"}
1
+ {"version":3,"file":"joanroucoux-cairn-ui-choice-chips.mjs","names":[],"sources":["../../../projects/ui/choice-chips/choice-chips.ts"],"sourcesContent":["import {\n Component,\n ElementRef,\n booleanAttribute,\n computed,\n forwardRef,\n inject,\n input,\n model,\n output,\n viewChildren,\n} from '@angular/core';\n\nimport { UI_CONTROL, type UiControl, type UiControlError } from '@joanroucoux/cairn-ui/control';\nimport { CONTROL_SURFACE_CLASSES, type ControlSurface } from '@joanroucoux/cairn-ui/input';\n\nexport type ChoiceChipOption = {\n value: string;\n label: string;\n};\n\nconst CHIP_CLASSES =\n 'h-10 px-3.5 pointer-fine:h-8 pointer-fine:px-3 rounded-pill text-label font-medium cursor-pointer transition-transform duration-(--duration-press) ease-out active:scale-(--press-scale) disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring)';\n\nconst SELECTED_CLASSES = 'bg-(--primary) text-(--primary-foreground)';\n\nconst UNSELECTED_CLASSES = 'text-(--foreground) shadow-[inset_0_0_0_1px_var(--border)]';\n\nconst nextId = (() => {\n let count = 0;\n\n return (): string => `ui-choice-chips-${++count}`;\n})();\n\n/**\n * Exclusive choice among a handful of named options, shown as wrapping pills. Binds to Signal Forms\n * with `[formField]`.\n *\n * @example\n * <ui-choice-chips label=\"Wrapper\" [options]=\"wrappers\" [(value)]=\"wrapper\" />\n *\n * @example\n * <ui-choice-chips label=\"Wrapper\" [options]=\"wrappers\" [formField]=\"form.wrapper\" />\n */\n@Component({\n selector: 'ui-choice-chips',\n template: `\n <div\n class=\"flex flex-col gap-1.5\"\n role=\"radiogroup\"\n [attr.aria-invalid]=\"invalid() || null\"\n [attr.aria-label]=\"ariaLabel() ?? null\"\n [attr.aria-labelledby]=\"labelledBy()\"\n [attr.aria-required]=\"required() || null\"\n (focusout)=\"onFocusOut($event)\"\n >\n @if (label()) {\n <span class=\"text-label leading-[17px] font-medium text-(--muted-foreground)\" [id]=\"labelId\">{{\n label()\n }}</span>\n }\n\n <div class=\"flex flex-wrap gap-1.5\">\n @for (option of options(); track option.value; let index = $index) {\n <button\n #radio\n role=\"radio\"\n type=\"button\"\n [attr.aria-checked]=\"option.value === value()\"\n [attr.tabindex]=\"tabIndexFor(option.value)\"\n [class]=\"chipClasses(option.value)\"\n [disabled]=\"disabled()\"\n (click)=\"select(option.value)\"\n (keydown)=\"onKeydown($event, index)\"\n >\n {{ option.label }}\n </button>\n }\n </div>\n </div>\n `,\n providers: [{ provide: UI_CONTROL, useExisting: forwardRef(() => UiChoiceChips) }],\n})\nexport class UiChoiceChips implements UiControl {\n readonly options = input.required<ChoiceChipOption[]>();\n readonly value = model.required<string>();\n readonly label = input<string>();\n readonly ariaLabel = input<string>();\n readonly ariaLabelledby = input<string>();\n readonly surface = input<ControlSurface>('background');\n readonly disabled = input(false, { transform: booleanAttribute });\n readonly errors = input<readonly UiControlError[]>([]);\n readonly invalid = input(false, { transform: booleanAttribute });\n readonly required = input(false, { transform: booleanAttribute });\n readonly touched = input(false);\n readonly touch = output<void>();\n\n protected readonly labelId = nextId();\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef);\n protected readonly radios = viewChildren<ElementRef<HTMLButtonElement>>('radio');\n\n protected readonly labelledBy = computed(() => this.ariaLabelledby() ?? (this.label() ? this.labelId : null));\n\n protected chipClasses(optionValue: string): string {\n const state =\n optionValue === this.value()\n ? SELECTED_CLASSES\n : `${CONTROL_SURFACE_CLASSES[this.surface()]} ${UNSELECTED_CLASSES}`;\n\n return `${CHIP_CLASSES} ${state}`;\n }\n\n protected tabIndexFor(optionValue: string): number {\n const opts = this.options();\n const tabStopValue = opts.some((option) => option.value === this.value()) ? this.value() : opts[0]?.value;\n\n return optionValue === tabStopValue ? 0 : -1;\n }\n\n protected select(optionValue: string): void {\n this.value.set(optionValue);\n }\n\n protected onFocusOut(event: FocusEvent): void {\n if (!this.#host.nativeElement.contains(event.relatedTarget as Node | null)) {\n this.touch.emit();\n }\n }\n\n protected onKeydown(event: KeyboardEvent, index: number): void {\n const opts = this.options();\n const target = {\n ArrowRight: index + 1,\n ArrowDown: index + 1,\n ArrowLeft: index - 1,\n ArrowUp: index - 1,\n Home: 0,\n End: opts.length - 1,\n }[event.key];\n\n if (target === undefined) {\n return;\n }\n event.preventDefault();\n\n const nextIndex = ((target % opts.length) + opts.length) % opts.length;\n\n this.value.set(opts[nextIndex]!.value);\n this.radios()[nextIndex]!.nativeElement.focus();\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"joanroucoux-cairn-ui-dialog.mjs","names":[],"sources":["../../../projects/ui/dialog/internal/dialog-styles.ts","../../../projects/ui/dialog/internal/dialog-exit.ts","../../../projects/ui/dialog/internal/modal-close.ts","../../../projects/ui/dialog/internal/sheet-drag.ts","../../../projects/ui/dialog/dialog.ts"],"sourcesContent":["export const DIALOG_STYLES = `\ndialog {\n --dialog-ease: var(--ease-sheet);\n transition:\n transform var(--duration-base) var(--dialog-ease),\n overlay var(--duration-base) allow-discrete,\n display var(--duration-base) allow-discrete;\n transform: translateY(var(--drag-y, 0px));\n}\n\ndialog:not([open]) {\n transform: translateY(100%);\n transition-duration: var(--duration-exit);\n}\n\n@starting-style {\n dialog[open] {\n transform: translateY(100%);\n }\n}\n\ndialog[data-dragging] {\n transition: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n dialog {\n transition:\n opacity var(--duration-base) var(--dialog-ease),\n overlay var(--duration-base) allow-discrete,\n display var(--duration-base) allow-discrete;\n opacity: 1;\n }\n\n dialog:not([open]) {\n opacity: 0;\n transform: translateY(var(--drag-y, 0px));\n }\n\n @starting-style {\n dialog[open] {\n opacity: 0;\n transform: translateY(0);\n }\n }\n}\n\n@media (min-width: 64rem) {\n dialog {\n --dialog-ease: var(--ease-out);\n transition:\n opacity var(--duration-base) var(--dialog-ease),\n transform var(--duration-base) var(--dialog-ease),\n overlay var(--duration-base) allow-discrete,\n display var(--duration-base) allow-discrete;\n opacity: 1;\n transform: scale(1);\n }\n\n dialog:not([open]) {\n opacity: 0;\n transform: scale(var(--enter-scale));\n }\n\n @starting-style {\n dialog[open] {\n opacity: 0;\n transform: scale(var(--enter-scale));\n }\n }\n}\n\ndialog::backdrop {\n transition: opacity var(--duration-base) var(--ease-out);\n opacity: 1;\n}\n\ndialog:not([open])::backdrop {\n opacity: 0;\n transition-duration: var(--duration-exit);\n}\n\n@starting-style {\n dialog[open]::backdrop {\n opacity: 0;\n }\n}\n`;\n","const GRACE = 50;\n\nexport function afterExit(element: HTMLElement, done: () => void): () => void {\n const style = getComputedStyle(element);\n const wait = longest(style.transitionDuration) + longest(style.transitionDelay);\n\n if (wait === 0) {\n done();\n return () => undefined;\n }\n\n const onEnd = (event: TransitionEvent): void => {\n if (event.target === element && !event.pseudoElement) {\n cancel();\n done();\n }\n };\n const timer = setTimeout(() => {\n cancel();\n done();\n }, wait + GRACE);\n const cancel = (): void => {\n clearTimeout(timer);\n element.removeEventListener('transitionend', onEnd);\n };\n\n element.addEventListener('transitionend', onEnd);\n\n return cancel;\n}\n\nfunction longest(list: string): number {\n return Math.max(0, ...list.split(',').map(toMs));\n}\n\nfunction toMs(value: string): number {\n const amount = Number.parseFloat(value);\n\n if (Number.isNaN(amount)) {\n return 0;\n }\n\n return value.trim().endsWith('ms') ? amount : amount * 1000;\n}\n","import { DestroyRef, ElementRef, type OutputEmitterRef, type Signal, afterRenderEffect, inject } from '@angular/core';\n\nimport { afterExit } from './dialog-exit';\n\ntype SharedReason = 'escape' | 'backdrop' | 'cross' | 'programmatic';\n\nexport type ModalCloseConfig<R extends string> = {\n readonly open: Signal<boolean>;\n readonly busy: Signal<boolean>;\n readonly dismissed: OutputEmitterRef<void>;\n readonly closed: OutputEmitterRef<R | SharedReason>;\n readonly reset?: () => void;\n readonly opening?: () => void;\n};\n\n/**\n * The close protocol of a modal on the native <dialog> under the host: `open` drives `showModal()` and `close()`,\n * Escape and a press-and-release on the veil close it unless `busy`, `dismissed` reports a close the user asked for\n * and `closed` its reason once the exit transition has played. `opening` runs just before `showModal()`. Must be\n * created in an injection context.\n */\nexport class ModalClose<R extends string> {\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef);\n readonly #config: ModalCloseConfig<R>;\n\n #pressedOnBackdrop = false;\n #reason: R | SharedReason | null = null;\n #cancelExit: () => void = () => undefined;\n\n constructor(config: ModalCloseConfig<R>) {\n this.#config = config;\n inject(DestroyRef).onDestroy(() => this.#cancelExit());\n\n afterRenderEffect(() => {\n const dialog = this.#dialog;\n\n if (config.open() && !dialog.open) {\n this.#cancelExit();\n config.reset?.();\n config.opening?.();\n dialog.showModal();\n } else if (!config.open() && dialog.open) {\n this.close('programmatic');\n }\n });\n }\n\n close(reason: R | SharedReason): void {\n if (this.#dialog.open) {\n this.#reason = reason;\n this.#dialog.close();\n }\n }\n\n onCancel(event: Event): void {\n if (event.target !== event.currentTarget) {\n return;\n }\n event.preventDefault();\n if (!this.#config.busy()) {\n this.close('escape');\n }\n }\n\n onKeydown(event: KeyboardEvent): void {\n if (event.key === 'Escape' && this.#config.busy() && (event.target as Element).closest('dialog') === this.#dialog) {\n event.preventDefault();\n }\n }\n\n onPointerDown(event: MouseEvent): void {\n this.#pressedOnBackdrop = event.target === this.#dialog && this.#outside(event);\n }\n\n onClick(event: MouseEvent): void {\n if (this.#pressedOnBackdrop && !this.#config.busy() && event.target === this.#dialog && this.#outside(event)) {\n this.close('backdrop');\n }\n this.#pressedOnBackdrop = false;\n }\n\n onNativeClose(): void {\n const asked = this.#reason;\n\n this.#reason = null;\n if (this.#dialog.open) {\n return;\n }\n if (asked === null && this.#config.busy()) {\n this.#dialog.showModal();\n return;\n }\n const reason = asked ?? 'escape';\n if (reason !== 'programmatic') {\n this.#config.dismissed.emit();\n }\n this.#cancelExit();\n this.#cancelExit = afterExit(this.#dialog, () => {\n this.#cancelExit = () => undefined;\n this.#config.reset?.();\n this.#config.closed.emit(reason);\n });\n }\n\n get #dialog(): HTMLDialogElement {\n return this.#host.nativeElement.querySelector('dialog') as HTMLDialogElement;\n }\n\n #outside(event: MouseEvent): boolean {\n const rect = this.#dialog.getBoundingClientRect();\n\n return (\n event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom\n );\n }\n}\n","const DISMISS_RATIO = 0.3;\nconst DISMISS_SPEED = 0.5;\nconst SPEED_WINDOW = 100;\nconst SLOP = 6;\nconst CONTROLS = 'button, a, input, select, textarea, [role=\"button\"]';\n\ntype Sample = { y: number; at: number };\n\ntype Gesture = {\n id: number;\n startX: number;\n startY: number;\n dragging: boolean;\n height: number;\n offset: number;\n samples: Sample[];\n};\n\nexport class SheetDrag {\n readonly #panel: HTMLElement;\n readonly #grips: readonly HTMLElement[];\n readonly #dismiss: () => void;\n readonly #enabled: () => boolean;\n #gesture: Gesture | null = null;\n\n constructor(panel: HTMLElement, grips: readonly HTMLElement[], dismiss: () => void, enabled: () => boolean) {\n this.#panel = panel;\n this.#grips = grips;\n this.#dismiss = dismiss;\n this.#enabled = enabled;\n\n for (const grip of grips) {\n grip.addEventListener('pointerdown', this.#onDown);\n grip.addEventListener('pointermove', this.#onMove);\n grip.addEventListener('pointerup', this.#onUp);\n grip.addEventListener('pointercancel', this.#onCancel);\n }\n }\n\n reset(): void {\n this.#gesture = null;\n this.#panel.removeAttribute('data-dragging');\n this.#panel.style.removeProperty('--drag-y');\n }\n\n destroy(): void {\n for (const grip of this.#grips) {\n grip.removeEventListener('pointerdown', this.#onDown);\n grip.removeEventListener('pointermove', this.#onMove);\n grip.removeEventListener('pointerup', this.#onUp);\n grip.removeEventListener('pointercancel', this.#onCancel);\n }\n }\n\n readonly #onDown = (event: PointerEvent): void => {\n const grip = event.currentTarget as HTMLElement;\n\n if (event.button !== 0 || !this.#enabled() || !isSheet() || (event.target as Element).closest(CONTROLS)) {\n return;\n }\n\n grip.setPointerCapture(event.pointerId);\n this.#gesture = {\n id: event.pointerId,\n startX: event.clientX,\n startY: event.clientY,\n dragging: false,\n height: 0,\n offset: 0,\n samples: [{ y: event.clientY, at: event.timeStamp }],\n };\n };\n\n readonly #onMove = (event: PointerEvent): void => {\n const gesture = this.#current(event);\n\n if (!gesture) {\n return;\n }\n\n const dx = event.clientX - gesture.startX;\n const dy = event.clientY - gesture.startY;\n\n if (!gesture.dragging) {\n if (Math.max(Math.abs(dx), Math.abs(dy)) < SLOP) {\n return;\n }\n if (Math.abs(dx) > Math.abs(dy)) {\n this.#gesture = null;\n return;\n }\n gesture.dragging = true;\n gesture.height = this.#panel.getBoundingClientRect().height;\n this.#panel.setAttribute('data-dragging', '');\n }\n\n gesture.offset = Math.max(0, dy);\n gesture.samples.push({ y: event.clientY, at: event.timeStamp });\n this.#panel.style.setProperty('--drag-y', `${gesture.offset}px`);\n };\n\n readonly #onUp = (event: PointerEvent): void => {\n const gesture = this.#current(event);\n\n if (!gesture) {\n return;\n }\n\n this.#gesture = null;\n\n if (!gesture.dragging) {\n return;\n }\n\n this.#panel.removeAttribute('data-dragging');\n\n const release = { y: event.clientY, at: event.timeStamp };\n\n if (gesture.offset > gesture.height * DISMISS_RATIO || releaseSpeed(gesture.samples, release) > DISMISS_SPEED) {\n this.#dismiss();\n } else {\n this.reset();\n }\n };\n\n readonly #onCancel = (event: PointerEvent): void => {\n if (this.#current(event)) {\n this.#gesture = null;\n this.#panel.removeAttribute('data-dragging');\n this.reset();\n }\n };\n\n #current(event: PointerEvent): Gesture | null {\n return this.#gesture?.id === event.pointerId ? this.#gesture : null;\n }\n}\n\nfunction isSheet(): boolean {\n return typeof matchMedia === 'function' && !matchMedia('(min-width: 64rem)').matches;\n}\n\nfunction releaseSpeed(samples: readonly Sample[], last: Sample): number {\n const first = samples.find((sample) => sample.at >= last.at - SPEED_WINDOW) ?? last;\n const elapsed = last.at - first.at;\n\n return elapsed > 0 ? (last.y - first.y) / elapsed : 0;\n}\n","import {\n Component,\n DestroyRef,\n ElementRef,\n afterNextRender,\n booleanAttribute,\n computed,\n inject,\n input,\n output,\n signal,\n} from '@angular/core';\n\nimport { DIALOG_STYLES } from './internal/dialog-styles';\nimport { ModalClose } from './internal/modal-close';\nimport { SheetDrag } from './internal/sheet-drag';\n\n/** Named dialog widths; any other CSS length is accepted as is. */\nexport const DIALOG_WIDTHS = ['md', 'lg'] as const;\nexport type DialogWidth = (typeof DIALOG_WIDTHS)[number];\n\nconst WIDTH_VALUES: Record<DialogWidth, string> = {\n md: '28rem',\n lg: '560px',\n};\n\n/** `confirm` is a short alertdialog: no cross, no hairlines, one 24px padding, 440px wide unless told otherwise. */\nexport const DIALOG_VARIANTS = ['default', 'confirm'] as const;\nexport type DialogVariant = (typeof DIALOG_VARIANTS)[number];\n\n/** `full` makes the sheet under `64rem` fill the screen below the status area; `fit` sizes it to its content. */\nexport const DIALOG_SHEETS = ['fit', 'full'] as const;\nexport type DialogSheet = (typeof DIALOG_SHEETS)[number];\n\ntype VariantClasses = {\n handle: string;\n header: string;\n body: string;\n footer: string;\n};\n\nconst VARIANTS: Record<DialogVariant, VariantClasses> = {\n default: {\n handle: 'pt-[7.5px]',\n header: 'pb-3 lg:py-4 lg:pl-6',\n body: 'py-4 lg:py-5',\n footer: 'pt-3 empty:hidden lg:py-4',\n },\n confirm: {\n handle: 'pt-[11.5px]',\n header: 'pt-3 lg:pt-6 lg:pl-6',\n body: 'pt-3',\n footer: 'not-empty:pt-5 lg:pb-6 lg:not-empty:pt-6',\n },\n};\n\nconst DEFAULT_WIDTHS: Record<DialogVariant, string> = {\n default: 'lg',\n confirm: '440px',\n};\n\n/** What started a close, as `closed` reports it. */\nexport const DIALOG_CLOSE_REASONS = ['escape', 'backdrop', 'cross', 'drag', 'programmatic'] as const;\nexport type DialogCloseReason = (typeof DIALOG_CLOSE_REASONS)[number];\n\nconst nextId = (() => {\n let count = 0;\n\n return () => `ui-dialog-${++count}`;\n})();\n\n/**\n * Modal dialog on the native <dialog>: a bottom sheet under `64rem`, centered above it.\n *\n * `busy` keeps it open while its action runs: Escape, a backdrop click and a sheet drag do nothing\n * and the cross is disabled. The owner disables its own buttons and still closes it through `open`.\n *\n * @example\n * <ui-dialog heading=\"Buy\" description=\"Ferrari\" closeLabel=\"Close\" width=\"528px\" [open]=\"buying()\" (dismissed)=\"buying.set(false)\">\n * <p>Body</p>\n * <button dialogActions ui-button variant=\"outline\" class=\"max-lg:hidden\" (click)=\"buying.set(false)\">Cancel</button>\n * <button dialogActions ui-button>Buy 40 shares</button>\n * </ui-dialog>\n */\n@Component({\n selector: 'ui-dialog',\n host: {\n '(pointerdown)': 'modal.onPointerDown($event)',\n '(click)': 'modal.onClick($event)',\n '(keydown)': 'modal.onKeydown($event)',\n },\n template: `\n <dialog\n #dlg\n [attr.aria-busy]=\"busy() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-labelledby]=\"headingId\"\n [attr.role]=\"variant() === 'confirm' ? 'alertdialog' : 'dialog'\"\n [class]=\"classes()\"\n [style.--dialog-width]=\"widthValue()\"\n (cancel)=\"modal.onCancel($event)\"\n (close)=\"modal.onNativeClose()\"\n >\n <div\n aria-hidden=\"true\"\n class=\"flex h-5 touch-none justify-center lg:hidden\"\n data-dialog-handle\n [class]=\"spec().handle\"\n >\n <span class=\"rounded-pill h-[5px] w-9 bg-(--border)\"></span>\n </div>\n\n <div data-dialog-header [class]=\"headerClasses()\">\n <div class=\"flex min-w-0 flex-1 flex-col\" [class.max-lg:pt-1]=\"description()\">\n <h2 class=\"text-title font-semibold\" [id]=\"headingId\">{{ heading() }}</h2>\n\n @if (description()) {\n <p\n class=\"text-label text-(--muted-foreground)\"\n [class.truncate]=\"truncateDescription()\"\n [id]=\"descriptionId\"\n >\n {{ description() }}\n </p>\n }\n </div>\n\n @if (closeLabel()) {\n <button\n type=\"button\"\n [attr.aria-label]=\"closeLabel()\"\n [class]=\"crossClasses\"\n [disabled]=\"busy()\"\n (click)=\"modal.close('cross')\"\n >\n <svg\n aria-hidden=\"true\"\n class=\"block size-[22px] fill-none stroke-current stroke-[1.75]\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n </div>\n\n <div\n class=\"min-h-0 flex-1 overflow-y-auto px-4 outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring) lg:px-6\"\n data-dialog-body\n [attr.aria-labelledby]=\"scrollable() ? headingId : null\"\n [attr.role]=\"scrollable() ? 'region' : null\"\n [attr.tabindex]=\"scrollable() ? 0 : null\"\n [id]=\"bodyId\"\n >\n <div class=\"flex flex-col gap-4 empty:hidden\" [class]=\"spec().body\">\n <ng-content />\n </div>\n </div>\n\n <div data-dialog-footer [class]=\"footerClasses()\">\n <ng-content select=\"[dialogActions]\" />\n </div>\n </dialog>\n `,\n styles: DIALOG_STYLES,\n})\nexport class UiDialog {\n readonly heading = input.required<string>();\n readonly description = input<string>();\n readonly closeLabel = input<string>();\n readonly variant = input<DialogVariant>('default');\n readonly sheet = input<DialogSheet>('fit');\n readonly truncateDescription = input(false, { transform: booleanAttribute });\n readonly width = input<DialogWidth | (string & {})>();\n readonly open = input(false, { transform: booleanAttribute });\n readonly busy = input(false, { transform: booleanAttribute });\n readonly dismissed = output<void>();\n readonly closed = output<DialogCloseReason>();\n\n protected readonly headingId = nextId();\n protected readonly descriptionId = `${this.headingId}-description`;\n protected readonly bodyId = `${this.headingId}-body`;\n protected readonly scrollable = signal(false);\n protected readonly stacked = signal(false);\n\n protected readonly describedBy = computed(() => {\n if (this.description()) {\n return this.descriptionId;\n }\n\n return this.variant() === 'confirm' ? this.bodyId : null;\n });\n\n protected readonly widthValue = computed(() => {\n const width = this.width() ?? DEFAULT_WIDTHS[this.variant()];\n\n return Object.hasOwn(WIDTH_VALUES, width) ? WIDTH_VALUES[width as DialogWidth] : width;\n });\n\n protected readonly spec = computed(() => VARIANTS[this.variant()]);\n\n protected readonly hairlines = computed(() => Boolean(this.closeLabel()) && this.variant() === 'default');\n\n protected readonly headerClasses = computed(\n () =>\n `flex ${this.description() ? 'items-start' : 'items-center'} gap-2 pl-4 max-lg:touch-none lg:gap-3 ${this.spec().header} ${this.closeLabel() ? 'pr-2 lg:pr-4' : 'pr-4 lg:pr-6'} ${this.hairlines() ? 'shadow-[inset_0_-1px_0_var(--hairline)]' : ''}`,\n );\n\n protected readonly crossClasses =\n 'rounded-pill lg:rounded-control grid size-11 flex-none cursor-pointer place-items-center text-(--muted-foreground) transition-[scale,background-color,color] [transition-duration:var(--duration-press),var(--duration-fast),var(--duration-fast)] ease-out outline-none focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-(--ring) active:scale-(--press-scale) active:bg-(--soft) disabled:pointer-events-none disabled:opacity-40 lg:size-9 lg:hover:bg-(--glow) lg:hover:text-(--foreground)';\n\n protected readonly footerClasses = computed(\n () =>\n `max-lg:[&>[ui-button]]:text-body flex justify-end gap-2 px-4 pb-[calc(8px+env(safe-area-inset-bottom))] max-lg:flex-col-reverse max-lg:[&>[ui-button]]:h-[50px] max-lg:[&>[ui-button]]:w-full lg:px-6 ${this.spec().footer} ${this.hairlines() ? 'shadow-[inset_0_1px_0_var(--hairline)]' : ''}`,\n );\n\n protected readonly classes = computed(\n () =>\n `open:flex w-full flex-col overflow-hidden m-auto mt-auto max-lg:max-h-[calc(100dvh-2rem)] lg:max-h-[calc(100dvh-96px)] max-lg:mb-0 max-lg:max-w-none max-lg:rounded-b-none lg:max-w-(--dialog-width) rounded-container bg-(--card) text-(--foreground) outline-none shadow-[0_0_0_1px_var(--border),0_24px_64px_rgb(0_0_0/0.24)] max-lg:shadow-[0_-1px_0_var(--border),0_-12px_32px_rgb(0_0_0/0.16)] ${this.stacked() ? 'backdrop:bg-black/[0.24]' : 'backdrop:bg-black/[0.36]'} ${this.sheet() === 'full' ? 'max-lg:h-[calc(100dvh-58px)]' : ''}`,\n );\n\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n #drag: SheetDrag | null = null;\n\n protected readonly modal: ModalClose<DialogCloseReason>;\n\n constructor() {\n const destroyRef = inject(DestroyRef);\n\n afterNextRender(() => {\n const host = this.#host.nativeElement;\n const dialog = host.querySelector('dialog') as HTMLDialogElement;\n const grips = ['[data-dialog-handle]', '[data-dialog-header]'].map(\n (grip) => host.querySelector(grip) as HTMLElement,\n );\n\n this.#drag = new SheetDrag(\n dialog,\n grips,\n () => this.modal.close('drag'),\n () => !this.busy(),\n );\n destroyRef.onDestroy(() => this.#drag?.destroy());\n\n if (typeof ResizeObserver === 'undefined') {\n return;\n }\n\n const body = host.querySelector('[data-dialog-body]') as HTMLElement;\n const observer = new ResizeObserver(() => this.scrollable.set(body.scrollHeight > body.clientHeight));\n\n observer.observe(body);\n observer.observe(body.firstElementChild as Element);\n destroyRef.onDestroy(() => observer.disconnect());\n });\n\n this.modal = new ModalClose<DialogCloseReason>({\n open: this.open,\n busy: this.busy,\n dismissed: this.dismissed,\n closed: this.closed,\n reset: () => this.#drag?.reset(),\n opening: () => this.stacked.set(this.#overAnotherModal()),\n });\n }\n\n #overAnotherModal(): boolean {\n const open = this.#host.nativeElement.ownerDocument.querySelectorAll('dialog[open]');\n\n return Array.from(open).some((dialog) => dialog.matches(':modal'));\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"joanroucoux-cairn-ui-dialog.mjs","names":[],"sources":["../../../projects/ui/dialog/internal/dialog-styles.ts","../../../projects/ui/dialog/internal/dialog-exit.ts","../../../projects/ui/dialog/internal/modal-close.ts","../../../projects/ui/dialog/internal/sheet-drag.ts","../../../projects/ui/dialog/dialog.ts"],"sourcesContent":["export const DIALOG_STYLES = `\ndialog {\n --dialog-ease: var(--ease-sheet);\n transition:\n transform var(--duration-base) var(--dialog-ease),\n overlay var(--duration-base) allow-discrete,\n display var(--duration-base) allow-discrete;\n transform: translateY(var(--drag-y, 0px));\n}\n\ndialog:not([open]) {\n transform: translateY(100%);\n transition-duration: var(--duration-exit);\n}\n\n@starting-style {\n dialog[open] {\n transform: translateY(100%);\n }\n}\n\ndialog[data-dragging] {\n transition: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n dialog {\n transition:\n opacity var(--duration-base) var(--dialog-ease),\n overlay var(--duration-base) allow-discrete,\n display var(--duration-base) allow-discrete;\n opacity: 1;\n }\n\n dialog:not([open]) {\n opacity: 0;\n transform: translateY(var(--drag-y, 0px));\n }\n\n @starting-style {\n dialog[open] {\n opacity: 0;\n transform: translateY(0);\n }\n }\n}\n\n@media (min-width: 64rem) {\n dialog {\n --dialog-ease: var(--ease-out);\n transition:\n opacity var(--duration-base) var(--dialog-ease),\n transform var(--duration-base) var(--dialog-ease),\n overlay var(--duration-base) allow-discrete,\n display var(--duration-base) allow-discrete;\n opacity: 1;\n transform: scale(1);\n }\n\n dialog:not([open]) {\n opacity: 0;\n transform: scale(var(--enter-scale));\n }\n\n @starting-style {\n dialog[open] {\n opacity: 0;\n transform: scale(var(--enter-scale));\n }\n }\n}\n\ndialog::backdrop {\n transition: opacity var(--duration-base) var(--ease-out);\n opacity: 1;\n}\n\ndialog:not([open])::backdrop {\n opacity: 0;\n transition-duration: var(--duration-exit);\n}\n\n@starting-style {\n dialog[open]::backdrop {\n opacity: 0;\n }\n}\n`;\n","const GRACE = 50;\n\nexport function afterExit(element: HTMLElement, done: () => void): () => void {\n const style = getComputedStyle(element);\n const wait = longest(style.transitionDuration) + longest(style.transitionDelay);\n\n if (wait === 0) {\n done();\n return () => undefined;\n }\n\n const onEnd = (event: TransitionEvent): void => {\n if (event.target === element && !event.pseudoElement) {\n cancel();\n done();\n }\n };\n const timer = setTimeout(() => {\n cancel();\n done();\n }, wait + GRACE);\n const cancel = (): void => {\n clearTimeout(timer);\n element.removeEventListener('transitionend', onEnd);\n };\n\n element.addEventListener('transitionend', onEnd);\n\n return cancel;\n}\n\nfunction longest(list: string): number {\n return Math.max(0, ...list.split(',').map(toMs));\n}\n\nfunction toMs(value: string): number {\n const amount = Number.parseFloat(value);\n\n if (Number.isNaN(amount)) {\n return 0;\n }\n\n return value.trim().endsWith('ms') ? amount : amount * 1000;\n}\n","import { DestroyRef, ElementRef, type OutputEmitterRef, type Signal, afterRenderEffect, inject } from '@angular/core';\n\nimport { afterExit } from './dialog-exit';\n\ntype SharedReason = 'escape' | 'backdrop' | 'cross' | 'programmatic';\n\n/** @internal */\nexport type ModalCloseConfig<R extends string> = {\n readonly open: Signal<boolean>;\n readonly busy: Signal<boolean>;\n readonly dismissed: OutputEmitterRef<void>;\n readonly closed: OutputEmitterRef<R | SharedReason>;\n readonly reset?: () => void;\n readonly opening?: () => void;\n};\n\n/**\n * The close protocol of a modal on the native <dialog> under the host: `open` drives `showModal()` and `close()`,\n * Escape and a press-and-release on the veil close it unless `busy`, `dismissed` reports a close the user asked for\n * and `closed` its reason once the exit transition has played. `opening` runs just before `showModal()`. Must be\n * created in an injection context.\n *\n * @internal Shared by `ui-dialog` and `ui-drawer`, not meant for applications.\n */\nexport class ModalClose<R extends string> {\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef);\n readonly #config: ModalCloseConfig<R>;\n\n #pressedOnBackdrop = false;\n #reason: R | SharedReason | null = null;\n #cancelExit: () => void = () => undefined;\n\n constructor(config: ModalCloseConfig<R>) {\n this.#config = config;\n inject(DestroyRef).onDestroy(() => this.#cancelExit());\n\n afterRenderEffect(() => {\n const dialog = this.#dialog;\n\n if (config.open() && !dialog.open) {\n this.#cancelExit();\n config.reset?.();\n config.opening?.();\n dialog.showModal();\n } else if (!config.open() && dialog.open) {\n this.close('programmatic');\n }\n });\n }\n\n close(reason: R | SharedReason): void {\n if (this.#dialog.open) {\n this.#reason = reason;\n this.#dialog.close();\n }\n }\n\n onCancel(event: Event): void {\n if (event.target !== event.currentTarget) {\n return;\n }\n event.preventDefault();\n if (!this.#config.busy()) {\n this.close('escape');\n }\n }\n\n onKeydown(event: KeyboardEvent): void {\n if (event.key === 'Escape' && this.#config.busy() && (event.target as Element).closest('dialog') === this.#dialog) {\n event.preventDefault();\n }\n }\n\n onPointerDown(event: MouseEvent): void {\n this.#pressedOnBackdrop = event.target === this.#dialog && this.#outside(event);\n }\n\n onClick(event: MouseEvent): void {\n if (this.#pressedOnBackdrop && !this.#config.busy() && event.target === this.#dialog && this.#outside(event)) {\n this.close('backdrop');\n }\n this.#pressedOnBackdrop = false;\n }\n\n onNativeClose(): void {\n const asked = this.#reason;\n\n this.#reason = null;\n if (this.#dialog.open) {\n return;\n }\n if (asked === null && this.#config.busy()) {\n this.#dialog.showModal();\n return;\n }\n const reason = asked ?? 'escape';\n if (reason !== 'programmatic') {\n this.#config.dismissed.emit();\n }\n this.#cancelExit();\n this.#cancelExit = afterExit(this.#dialog, () => {\n this.#cancelExit = () => undefined;\n this.#config.reset?.();\n this.#config.closed.emit(reason);\n });\n }\n\n get #dialog(): HTMLDialogElement {\n return this.#host.nativeElement.querySelector('dialog') as HTMLDialogElement;\n }\n\n #outside(event: MouseEvent): boolean {\n const rect = this.#dialog.getBoundingClientRect();\n\n return (\n event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom\n );\n }\n}\n","const DISMISS_RATIO = 0.3;\nconst DISMISS_SPEED = 0.5;\nconst SPEED_WINDOW = 100;\nconst SLOP = 6;\nconst CONTROLS = 'button, a, input, select, textarea, [role=\"button\"]';\n\ntype Sample = { y: number; at: number };\n\ntype Gesture = {\n id: number;\n startX: number;\n startY: number;\n dragging: boolean;\n height: number;\n offset: number;\n samples: Sample[];\n};\n\nexport class SheetDrag {\n readonly #panel: HTMLElement;\n readonly #grips: readonly HTMLElement[];\n readonly #dismiss: () => void;\n readonly #enabled: () => boolean;\n #gesture: Gesture | null = null;\n\n constructor(panel: HTMLElement, grips: readonly HTMLElement[], dismiss: () => void, enabled: () => boolean) {\n this.#panel = panel;\n this.#grips = grips;\n this.#dismiss = dismiss;\n this.#enabled = enabled;\n\n for (const grip of grips) {\n grip.addEventListener('pointerdown', this.#onDown);\n grip.addEventListener('pointermove', this.#onMove);\n grip.addEventListener('pointerup', this.#onUp);\n grip.addEventListener('pointercancel', this.#onCancel);\n }\n }\n\n reset(): void {\n this.#gesture = null;\n this.#panel.removeAttribute('data-dragging');\n this.#panel.style.removeProperty('--drag-y');\n }\n\n destroy(): void {\n for (const grip of this.#grips) {\n grip.removeEventListener('pointerdown', this.#onDown);\n grip.removeEventListener('pointermove', this.#onMove);\n grip.removeEventListener('pointerup', this.#onUp);\n grip.removeEventListener('pointercancel', this.#onCancel);\n }\n }\n\n readonly #onDown = (event: PointerEvent): void => {\n const grip = event.currentTarget as HTMLElement;\n\n if (event.button !== 0 || !this.#enabled() || !isSheet() || (event.target as Element).closest(CONTROLS)) {\n return;\n }\n\n grip.setPointerCapture(event.pointerId);\n this.#gesture = {\n id: event.pointerId,\n startX: event.clientX,\n startY: event.clientY,\n dragging: false,\n height: 0,\n offset: 0,\n samples: [{ y: event.clientY, at: event.timeStamp }],\n };\n };\n\n readonly #onMove = (event: PointerEvent): void => {\n const gesture = this.#current(event);\n\n if (!gesture) {\n return;\n }\n\n const dx = event.clientX - gesture.startX;\n const dy = event.clientY - gesture.startY;\n\n if (!gesture.dragging) {\n if (Math.max(Math.abs(dx), Math.abs(dy)) < SLOP) {\n return;\n }\n if (Math.abs(dx) > Math.abs(dy)) {\n this.#gesture = null;\n return;\n }\n gesture.dragging = true;\n gesture.height = this.#panel.getBoundingClientRect().height;\n this.#panel.setAttribute('data-dragging', '');\n }\n\n gesture.offset = Math.max(0, dy);\n gesture.samples.push({ y: event.clientY, at: event.timeStamp });\n this.#panel.style.setProperty('--drag-y', `${gesture.offset}px`);\n };\n\n readonly #onUp = (event: PointerEvent): void => {\n const gesture = this.#current(event);\n\n if (!gesture) {\n return;\n }\n\n this.#gesture = null;\n\n if (!gesture.dragging) {\n return;\n }\n\n this.#panel.removeAttribute('data-dragging');\n\n const release = { y: event.clientY, at: event.timeStamp };\n\n if (gesture.offset > gesture.height * DISMISS_RATIO || releaseSpeed(gesture.samples, release) > DISMISS_SPEED) {\n this.#dismiss();\n } else {\n this.reset();\n }\n };\n\n readonly #onCancel = (event: PointerEvent): void => {\n if (this.#current(event)) {\n this.#gesture = null;\n this.#panel.removeAttribute('data-dragging');\n this.reset();\n }\n };\n\n #current(event: PointerEvent): Gesture | null {\n return this.#gesture?.id === event.pointerId ? this.#gesture : null;\n }\n}\n\nfunction isSheet(): boolean {\n return typeof matchMedia === 'function' && !matchMedia('(min-width: 64rem)').matches;\n}\n\nfunction releaseSpeed(samples: readonly Sample[], last: Sample): number {\n const first = samples.find((sample) => sample.at >= last.at - SPEED_WINDOW) ?? last;\n const elapsed = last.at - first.at;\n\n return elapsed > 0 ? (last.y - first.y) / elapsed : 0;\n}\n","import {\n Component,\n DestroyRef,\n ElementRef,\n afterNextRender,\n booleanAttribute,\n computed,\n inject,\n input,\n output,\n signal,\n} from '@angular/core';\n\nimport { DIALOG_STYLES } from './internal/dialog-styles';\nimport { ModalClose } from './internal/modal-close';\nimport { SheetDrag } from './internal/sheet-drag';\n\n/** Named dialog widths; any other CSS length is accepted as is. */\nexport const DIALOG_WIDTHS = ['md', 'lg'] as const;\nexport type DialogWidth = (typeof DIALOG_WIDTHS)[number];\n\nconst WIDTH_VALUES: Record<DialogWidth, string> = {\n md: '28rem',\n lg: '560px',\n};\n\n/** `confirm` is a short alertdialog: no cross, no hairlines, one 24px padding, 440px wide unless told otherwise. */\nexport const DIALOG_VARIANTS = ['default', 'confirm'] as const;\nexport type DialogVariant = (typeof DIALOG_VARIANTS)[number];\n\n/** `full` makes the sheet under `64rem` fill the screen below the status area; `fit` sizes it to its content. */\nexport const DIALOG_SHEETS = ['fit', 'full'] as const;\nexport type DialogSheet = (typeof DIALOG_SHEETS)[number];\n\ntype VariantClasses = {\n handle: string;\n header: string;\n body: string;\n footer: string;\n};\n\nconst VARIANTS: Record<DialogVariant, VariantClasses> = {\n default: {\n handle: 'pt-[7.5px]',\n header: 'pb-3 lg:py-4 lg:pl-6',\n body: 'py-4 lg:py-5',\n footer: 'pt-3 empty:hidden lg:py-4',\n },\n confirm: {\n handle: 'pt-[11.5px]',\n header: 'pt-3 lg:pt-6 lg:pl-6',\n body: 'pt-3',\n footer: 'not-empty:pt-5 lg:pb-6 lg:not-empty:pt-6',\n },\n};\n\nconst DEFAULT_WIDTHS: Record<DialogVariant, string> = {\n default: 'lg',\n confirm: '440px',\n};\n\n/** What started a close, as `closed` reports it. */\nexport const DIALOG_CLOSE_REASONS = ['escape', 'backdrop', 'cross', 'drag', 'programmatic'] as const;\nexport type DialogCloseReason = (typeof DIALOG_CLOSE_REASONS)[number];\n\nconst nextId = (() => {\n let count = 0;\n\n return () => `ui-dialog-${++count}`;\n})();\n\n/**\n * Modal dialog on the native <dialog>: a bottom sheet under `64rem`, centered above it.\n *\n * `busy` keeps it open while its action runs: Escape, a backdrop click and a sheet drag do nothing\n * and the cross is disabled. The owner disables its own buttons and still closes it through `open`.\n *\n * @example\n * <ui-dialog heading=\"Buy\" description=\"Ferrari\" closeLabel=\"Close\" width=\"528px\" [open]=\"buying()\" (dismissed)=\"buying.set(false)\">\n * <p>Body</p>\n * <button dialogActions ui-button variant=\"outline\" class=\"max-lg:hidden\" (click)=\"buying.set(false)\">Cancel</button>\n * <button dialogActions ui-button>Buy 40 shares</button>\n * </ui-dialog>\n */\n@Component({\n selector: 'ui-dialog',\n host: {\n '(pointerdown)': 'modal.onPointerDown($event)',\n '(click)': 'modal.onClick($event)',\n '(keydown)': 'modal.onKeydown($event)',\n },\n template: `\n <dialog\n #dlg\n [attr.aria-busy]=\"busy() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-labelledby]=\"headingId\"\n [attr.role]=\"variant() === 'confirm' ? 'alertdialog' : 'dialog'\"\n [class]=\"classes()\"\n [style.--dialog-width]=\"widthValue()\"\n (cancel)=\"modal.onCancel($event)\"\n (close)=\"modal.onNativeClose()\"\n >\n <div\n aria-hidden=\"true\"\n class=\"flex h-5 touch-none justify-center lg:hidden\"\n data-dialog-handle\n [class]=\"spec().handle\"\n >\n <span class=\"rounded-pill h-[5px] w-9 bg-(--border)\"></span>\n </div>\n\n <div data-dialog-header [class]=\"headerClasses()\">\n <div class=\"flex min-w-0 flex-1 flex-col\" [class.max-lg:pt-1]=\"description()\">\n <h2 class=\"text-title font-semibold\" [id]=\"headingId\">{{ heading() }}</h2>\n\n @if (description()) {\n <p\n class=\"text-label text-(--muted-foreground)\"\n [class.truncate]=\"truncateDescription()\"\n [id]=\"descriptionId\"\n >\n {{ description() }}\n </p>\n }\n </div>\n\n @if (closeLabel()) {\n <button\n type=\"button\"\n [attr.aria-label]=\"closeLabel()\"\n [class]=\"crossClasses\"\n [disabled]=\"busy()\"\n (click)=\"modal.close('cross')\"\n >\n <svg\n aria-hidden=\"true\"\n class=\"block size-[22px] fill-none stroke-current stroke-[1.75]\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n </div>\n\n <div\n class=\"min-h-0 flex-1 overflow-y-auto px-4 outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring) lg:px-6\"\n data-dialog-body\n [attr.aria-labelledby]=\"scrollable() ? headingId : null\"\n [attr.role]=\"scrollable() ? 'region' : null\"\n [attr.tabindex]=\"scrollable() ? 0 : null\"\n [id]=\"bodyId\"\n >\n <div class=\"flex flex-col gap-4 empty:hidden\" [class]=\"spec().body\">\n <ng-content />\n </div>\n </div>\n\n <div data-dialog-footer [class]=\"footerClasses()\">\n <ng-content select=\"[dialogActions]\" />\n </div>\n </dialog>\n `,\n styles: DIALOG_STYLES,\n})\nexport class UiDialog {\n readonly heading = input.required<string>();\n readonly description = input<string>();\n readonly closeLabel = input<string>();\n readonly variant = input<DialogVariant>('default');\n readonly sheet = input<DialogSheet>('fit');\n readonly truncateDescription = input(false, { transform: booleanAttribute });\n readonly width = input<DialogWidth | (string & {})>();\n readonly open = input(false, { transform: booleanAttribute });\n readonly busy = input(false, { transform: booleanAttribute });\n readonly dismissed = output<void>();\n readonly closed = output<DialogCloseReason>();\n\n protected readonly headingId = nextId();\n protected readonly descriptionId = `${this.headingId}-description`;\n protected readonly bodyId = `${this.headingId}-body`;\n protected readonly scrollable = signal(false);\n protected readonly stacked = signal(false);\n\n protected readonly describedBy = computed(() => {\n if (this.description()) {\n return this.descriptionId;\n }\n\n return this.variant() === 'confirm' ? this.bodyId : null;\n });\n\n protected readonly widthValue = computed(() => {\n const width = this.width() ?? DEFAULT_WIDTHS[this.variant()];\n\n return Object.hasOwn(WIDTH_VALUES, width) ? WIDTH_VALUES[width as DialogWidth] : width;\n });\n\n protected readonly spec = computed(() => VARIANTS[this.variant()]);\n\n protected readonly hairlines = computed(() => Boolean(this.closeLabel()) && this.variant() === 'default');\n\n protected readonly headerClasses = computed(\n () =>\n `flex ${this.description() ? 'items-start' : 'items-center'} gap-2 pl-4 max-lg:touch-none lg:gap-3 ${this.spec().header} ${this.closeLabel() ? 'pr-2 lg:pr-4' : 'pr-4 lg:pr-6'} ${this.hairlines() ? 'shadow-[inset_0_-1px_0_var(--hairline)]' : ''}`,\n );\n\n protected readonly crossClasses =\n 'rounded-pill lg:rounded-control grid size-11 flex-none cursor-pointer place-items-center text-(--muted-foreground) transition-[scale,background-color,color] [transition-duration:var(--duration-press),var(--duration-fast),var(--duration-fast)] ease-out outline-none focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-(--ring) active:scale-(--press-scale) active:bg-(--soft) disabled:pointer-events-none disabled:opacity-40 lg:size-9 lg:hover:bg-(--glow) lg:hover:text-(--foreground)';\n\n protected readonly footerClasses = computed(\n () =>\n `max-lg:[&>[ui-button]]:text-body flex justify-end gap-2 px-4 pb-[calc(8px+env(safe-area-inset-bottom))] max-lg:flex-col-reverse max-lg:[&>[ui-button]]:h-[50px] max-lg:[&>[ui-button]]:w-full lg:px-6 ${this.spec().footer} ${this.hairlines() ? 'shadow-[inset_0_1px_0_var(--hairline)]' : ''}`,\n );\n\n protected readonly classes = computed(\n () =>\n `open:flex w-full flex-col overflow-hidden m-auto mt-auto max-lg:max-h-[calc(100dvh-2rem)] lg:max-h-[calc(100dvh-96px)] max-lg:mb-0 max-lg:max-w-none max-lg:rounded-b-none lg:max-w-(--dialog-width) rounded-container bg-(--card) text-(--foreground) outline-none shadow-[0_0_0_1px_var(--border),0_24px_64px_rgb(0_0_0/0.24)] max-lg:shadow-[0_-1px_0_var(--border),0_-12px_32px_rgb(0_0_0/0.16)] ${this.stacked() ? 'backdrop:bg-black/[0.24]' : 'backdrop:bg-black/[0.36]'} ${this.sheet() === 'full' ? 'max-lg:h-[calc(100dvh-58px)]' : ''}`,\n );\n\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n #drag: SheetDrag | null = null;\n\n protected readonly modal: ModalClose<DialogCloseReason>;\n\n constructor() {\n const destroyRef = inject(DestroyRef);\n\n afterNextRender(() => {\n const host = this.#host.nativeElement;\n const dialog = host.querySelector('dialog') as HTMLDialogElement;\n const grips = ['[data-dialog-handle]', '[data-dialog-header]'].map(\n (grip) => host.querySelector(grip) as HTMLElement,\n );\n\n this.#drag = new SheetDrag(\n dialog,\n grips,\n () => this.modal.close('drag'),\n () => !this.busy(),\n );\n destroyRef.onDestroy(() => this.#drag?.destroy());\n\n if (typeof ResizeObserver === 'undefined') {\n return;\n }\n\n const body = host.querySelector('[data-dialog-body]') as HTMLElement;\n const observer = new ResizeObserver(() => this.scrollable.set(body.scrollHeight > body.clientHeight));\n\n observer.observe(body);\n observer.observe(body.firstElementChild as Element);\n destroyRef.onDestroy(() => observer.disconnect());\n });\n\n this.modal = new ModalClose<DialogCloseReason>({\n open: this.open,\n busy: this.busy,\n dismissed: this.dismissed,\n closed: this.closed,\n reset: () => this.#drag?.reset(),\n opening: () => this.stacked.set(this.#overAnotherModal()),\n });\n }\n\n #overAnotherModal(): boolean {\n const open = this.#host.nativeElement.ownerDocument.querySelectorAll('dialog[open]');\n\n return Array.from(open).some((dialog) => dialog.matches(':modal'));\n }\n}\n"],"mappings":"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"}
@@ -4,16 +4,16 @@ import { Component, computed, input, output, signal } from "@angular/core";
4
4
  const CIRCUMFERENCE = 2 * Math.PI * 78;
5
5
  const GAP = 2;
6
6
  function positionSlices(slices) {
7
- let travelled = 0;
7
+ let traveled = 0;
8
8
  return slices.map((slice) => {
9
9
  const length = slice.share * CIRCUMFERENCE;
10
10
  const drawn = Math.max(0, length - GAP);
11
11
  const positioned = {
12
12
  ...slice,
13
13
  dash: `${drawn.toFixed(2)} ${(CIRCUMFERENCE - drawn).toFixed(2)}`,
14
- offset: (-travelled).toFixed(2)
14
+ offset: (-traveled).toFixed(2)
15
15
  };
16
- travelled += length;
16
+ traveled += length;
17
17
  return positioned;
18
18
  });
19
19
  }
@@ -76,7 +76,7 @@ var UiDonut = class UiDonut {
76
76
  }, ...ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []);
77
77
  ariaLabel = computed(() => {
78
78
  const detail = shareSlices(this.slices()).map((slice) => `${slice.label} ${this.shareFormat()(slice.share)}`).join(", ");
79
- return detail ? `${this.label()} : ${detail}` : this.label();
79
+ return detail ? `${this.label()}: ${detail}` : this.label();
80
80
  }, ...ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []);
81
81
  radius = 78;
82
82
  ringWidth = 30;
@@ -1 +1 @@
1
- {"version":3,"file":"joanroucoux-cairn-ui-donut.mjs","names":[],"sources":["../../../projects/ui/donut/internal/donut-geometry.ts","../../../projects/ui/donut/internal/donut-slices.ts","../../../projects/ui/donut/donut.ts"],"sourcesContent":["import type { RankedSlice } from './donut-slices';\n\nexport type PositionedSlice = RankedSlice & { dash: string; offset: string };\n\nexport const RADIUS = 78;\nexport const RING_WIDTH = 30;\nexport const CIRCUMFERENCE = 2 * Math.PI * RADIUS;\n\nconst GAP = 2;\n\nexport function positionSlices(slices: RankedSlice[]): PositionedSlice[] {\n let travelled = 0;\n\n return slices.map((slice) => {\n const length = slice.share * CIRCUMFERENCE;\n const drawn = Math.max(0, length - GAP);\n const positioned = {\n ...slice,\n dash: `${drawn.toFixed(2)} ${(CIRCUMFERENCE - drawn).toFixed(2)}`,\n offset: (-travelled).toFixed(2),\n };\n travelled += length;\n\n return positioned;\n });\n}\n","export type DonutSlice = {\n id: string;\n label: string;\n value: number;\n sublabel?: string;\n};\n\nexport type Ramp = 1 | 2 | 3 | 4 | 5 | 6;\n\nexport type SharedSlice = DonutSlice & { share: number };\n\nexport type RankedSlice = DonutSlice & { share: number; ramp: Ramp; members: string[] };\n\nconst MAX_RAMP = 6;\nconst OTHERS_ID = 'others';\n\nexport function shareSlices(slices: DonutSlice[]): SharedSlice[] {\n const positive = slices.filter((slice) => slice.value > 0).sort((a, b) => b.value - a.value);\n const total = positive.reduce((sum, slice) => sum + slice.value, 0);\n\n return positive.map((slice) => ({ ...slice, share: slice.value / total }));\n}\n\nexport function rankSlices(slices: DonutSlice[], othersLabel: string): RankedSlice[] {\n const shared = shareSlices(slices);\n\n const toRanked = (slice: SharedSlice, ramp: Ramp, members: string[] = []): RankedSlice => ({\n ...slice,\n ramp,\n members,\n });\n\n if (shared.length <= MAX_RAMP) {\n return shared.map((slice, index) => toRanked(slice, (index + 1) as Ramp));\n }\n\n const kept = shared.slice(0, MAX_RAMP - 1);\n const rest = shared.slice(MAX_RAMP - 1);\n const othersValue = rest.reduce((sum, slice) => sum + slice.value, 0);\n const othersShare = rest.reduce((sum, slice) => sum + slice.share, 0);\n\n return [\n ...kept.map((slice, index) => toRanked(slice, (index + 1) as Ramp)),\n toRanked(\n { id: OTHERS_ID, label: othersLabel, value: othersValue, share: othersShare },\n MAX_RAMP,\n rest.map((slice) => slice.label),\n ),\n ];\n}\n","import { NgTemplateOutlet } from '@angular/common';\nimport { Component, computed, input, output, signal } from '@angular/core';\n\nimport { RADIUS, RING_WIDTH, positionSlices } from './internal/donut-geometry';\nimport { type DonutSlice, type Ramp, type RankedSlice, rankSlices, shareSlices } from './internal/donut-slices';\n\nexport type { DonutSlice };\n\nconst identityValue = (value: number): string => `${value}`;\nconst identityShare = (share: number): string => `${Math.round(share * 100)}%`;\n\nconst ROW_CLASSES =\n 'flex w-full items-center gap-3 min-h-14 px-2 py-1 [font-variant-numeric:var(--numeric)] rounded-control text-left transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out hover:bg-(--glow) active:bg-(--soft) active:scale-(--press-scale) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring)';\n\nconst SLICE_CLASSES =\n 'cursor-pointer origin-center [transform-box:view-box] transition-[scale,opacity] duration-(--duration-fast) ease-out';\n\nconst RAMP_DOT_CLASSES: Record<Ramp, string> = {\n 1: 'bg-(--ramp-1)',\n 2: 'bg-(--ramp-2)',\n 3: 'bg-(--ramp-3)',\n 4: 'bg-(--ramp-4)',\n 5: 'bg-(--ramp-5)',\n 6: 'bg-(--ramp-6)',\n};\n\n/**\n * A ring chart with a legend: slices rank from largest to smallest, and hovering, focusing or\n * activating a legend row highlights its slice.\n *\n * @example\n * <ui-donut [slices]=\"byAssetClass()\" label=\"Par classe d'actif\" othersLabel=\"Autres\" [valueFormat]=\"formatEur\" [shareFormat]=\"formatShare\" (sliceSelect)=\"goToHoldings($event)\" />\n */\n@Component({\n selector: 'ui-donut',\n template: `\n <div class=\"relative size-[232px] flex-none sm:size-60\">\n <svg class=\"block h-full w-full -rotate-90\" role=\"img\" viewBox=\"0 0 200 200\" [attr.aria-label]=\"ariaLabel()\">\n <circle\n class=\"stroke-(--muted)\"\n cx=\"100\"\n cy=\"100\"\n fill=\"none\"\n [attr.r]=\"radius\"\n [attr.stroke-width]=\"ringWidth\"\n />\n @for (slice of positioned(); track slice.id) {\n <circle\n cx=\"100\"\n cy=\"100\"\n data-slice\n fill=\"none\"\n [attr.r]=\"radius\"\n [attr.stroke-dasharray]=\"slice.dash\"\n [attr.stroke-dashoffset]=\"slice.offset\"\n [attr.stroke-width]=\"ringWidth\"\n [class]=\"sliceClasses(slice)\"\n [style.stroke]=\"strokeColor(slice)\"\n (click)=\"select(slice)\"\n (pointerenter)=\"highlight(slice.id)\"\n (pointerleave)=\"clearHighlight(slice.id)\"\n />\n }\n </svg>\n\n @if (active(); as slice) {\n <div\n class=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center px-12 text-center\"\n data-testid=\"donut-centre\"\n >\n <span class=\"text-label max-w-full truncate font-medium whitespace-nowrap text-(--muted-foreground)\">{{\n slice.label\n }}</span>\n <span class=\"text-heading font-semibold tracking-(--tracking-display)\">{{ shareFormat()(slice.share) }}</span>\n <span class=\"text-caption tracking-normal text-(--muted-foreground) [font-variant-numeric:var(--numeric)]\">{{\n valueFormat()(slice.value)\n }}</span>\n </div>\n }\n </div>\n\n <div class=\"flex w-full min-w-0 flex-1 flex-col sm:w-auto\" data-testid=\"donut-legend\">\n @for (slice of ranked(); track slice.id) {\n @if (hrefFor(slice.id); as href) {\n <a\n [attr.href]=\"href\"\n [class]=\"rowClasses(slice)\"\n (blur)=\"clearHighlight(slice.id)\"\n (click)=\"follow($event, slice)\"\n (focus)=\"highlight(slice.id)\"\n (pointerenter)=\"highlight(slice.id)\"\n (pointerleave)=\"clearHighlight(slice.id)\"\n >\n <ng-container *ngTemplateOutlet=\"rowContent; context: { $implicit: slice }\" />\n </a>\n } @else {\n <button\n type=\"button\"\n [class]=\"rowClasses(slice)\"\n (blur)=\"clearHighlight(slice.id)\"\n (click)=\"select(slice)\"\n (focus)=\"highlight(slice.id)\"\n (pointerenter)=\"highlight(slice.id)\"\n (pointerleave)=\"clearHighlight(slice.id)\"\n >\n <ng-container *ngTemplateOutlet=\"rowContent; context: { $implicit: slice }\" />\n </button>\n }\n }\n </div>\n\n <ng-template #rowContent let-slice>\n <span [class]=\"dotClasses(slice)\"></span>\n <span class=\"flex min-w-0 flex-1 flex-col\">\n <span class=\"text-body truncate font-medium\">{{ slice.label }}</span>\n <span class=\"text-caption truncate tracking-(--tracking-caption) text-(--subtle-foreground)\">{{\n subLabel(slice)\n }}</span>\n </span>\n <span class=\"flex flex-none flex-col items-end whitespace-nowrap\">\n <span class=\"text-body font-medium\">{{ shareFormat()(slice.share) }}</span>\n <span class=\"text-caption tracking-normal text-(--muted-foreground)\">{{ valueFormat()(slice.value) }}</span>\n </span>\n </ng-template>\n `,\n imports: [NgTemplateOutlet],\n host: {\n class: 'flex flex-col items-center gap-3 sm:flex-row sm:items-center sm:gap-8',\n },\n})\nexport class UiDonut {\n readonly slices = input.required<DonutSlice[]>();\n readonly label = input.required<string>();\n readonly othersLabel = input.required<string>();\n readonly valueFormat = input<(value: number) => string>(identityValue);\n readonly shareFormat = input<(share: number) => string>(identityShare);\n\n readonly legendHref = input<((id: string) => string | null) | undefined>();\n\n readonly sliceSelect = output<DonutSlice['id']>();\n\n protected readonly activeId = signal<string | null>(null);\n\n protected readonly ranked = computed(() => rankSlices(this.slices(), this.othersLabel()));\n protected readonly positioned = computed(() => positionSlices(this.ranked()));\n\n protected readonly active = computed<RankedSlice | null>(() => {\n const ranked = this.ranked();\n const id = this.activeId();\n\n return ranked.find((slice) => slice.id === id) ?? ranked[0] ?? null;\n });\n\n protected readonly ariaLabel = computed(() => {\n const detail = shareSlices(this.slices())\n .map((slice) => `${slice.label} ${this.shareFormat()(slice.share)}`)\n .join(', ');\n\n return detail ? `${this.label()} : ${detail}` : this.label();\n });\n\n protected readonly radius = RADIUS;\n protected readonly ringWidth = RING_WIDTH;\n\n protected strokeColor(slice: RankedSlice): string {\n return `var(--ramp-${slice.ramp})`;\n }\n\n protected sliceClasses(slice: RankedSlice): string {\n const id = this.activeId();\n if (id === null) {\n return SLICE_CLASSES;\n }\n\n return `${SLICE_CLASSES} ${id === slice.id ? 'scale-104 motion-reduce:scale-100' : 'opacity-50'}`;\n }\n\n protected dotClasses(slice: RankedSlice): string {\n return `size-3 flex-none rounded-pill shadow-[inset_0_0_0_1px_var(--border)] ${RAMP_DOT_CLASSES[slice.ramp]}`;\n }\n\n protected rowClasses(slice: RankedSlice): string {\n return `${ROW_CLASSES}${this.isActive(slice) ? ' bg-(--soft)' : ''}`;\n }\n\n protected subLabel(slice: RankedSlice): string {\n return slice.members.length > 0 ? slice.members.join(', ') : (slice.sublabel ?? '');\n }\n\n protected isActive(slice: RankedSlice): boolean {\n return this.active()?.id === slice.id;\n }\n\n protected highlight(id: string): void {\n this.activeId.set(id);\n }\n\n protected clearHighlight(id: string): void {\n if (this.activeId() === id) {\n this.activeId.set(null);\n }\n }\n\n protected hrefFor(id: string): string | null {\n return this.legendHref()?.(id) ?? null;\n }\n\n protected follow(event: MouseEvent, slice: RankedSlice): void {\n if (event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) {\n return;\n }\n event.preventDefault();\n this.select(slice);\n }\n\n protected select(slice: RankedSlice): void {\n this.activeId.set(slice.id);\n this.sliceSelect.emit(slice.id);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"joanroucoux-cairn-ui-donut.mjs","names":[],"sources":["../../../projects/ui/donut/internal/donut-geometry.ts","../../../projects/ui/donut/internal/donut-slices.ts","../../../projects/ui/donut/donut.ts"],"sourcesContent":["import type { RankedSlice } from './donut-slices';\n\nexport type PositionedSlice = RankedSlice & { dash: string; offset: string };\n\nexport const RADIUS = 78;\nexport const RING_WIDTH = 30;\nexport const CIRCUMFERENCE = 2 * Math.PI * RADIUS;\n\nconst GAP = 2;\n\nexport function positionSlices(slices: RankedSlice[]): PositionedSlice[] {\n let traveled = 0;\n\n return slices.map((slice) => {\n const length = slice.share * CIRCUMFERENCE;\n const drawn = Math.max(0, length - GAP);\n const positioned = {\n ...slice,\n dash: `${drawn.toFixed(2)} ${(CIRCUMFERENCE - drawn).toFixed(2)}`,\n offset: (-traveled).toFixed(2),\n };\n traveled += length;\n\n return positioned;\n });\n}\n","export type DonutSlice = {\n id: string;\n label: string;\n value: number;\n sublabel?: string;\n};\n\nexport type Ramp = 1 | 2 | 3 | 4 | 5 | 6;\n\nexport type SharedSlice = DonutSlice & { share: number };\n\nexport type RankedSlice = DonutSlice & { share: number; ramp: Ramp; members: string[] };\n\nconst MAX_RAMP = 6;\nconst OTHERS_ID = 'others';\n\nexport function shareSlices(slices: DonutSlice[]): SharedSlice[] {\n const positive = slices.filter((slice) => slice.value > 0).sort((a, b) => b.value - a.value);\n const total = positive.reduce((sum, slice) => sum + slice.value, 0);\n\n return positive.map((slice) => ({ ...slice, share: slice.value / total }));\n}\n\nexport function rankSlices(slices: DonutSlice[], othersLabel: string): RankedSlice[] {\n const shared = shareSlices(slices);\n\n const toRanked = (slice: SharedSlice, ramp: Ramp, members: string[] = []): RankedSlice => ({\n ...slice,\n ramp,\n members,\n });\n\n if (shared.length <= MAX_RAMP) {\n return shared.map((slice, index) => toRanked(slice, (index + 1) as Ramp));\n }\n\n const kept = shared.slice(0, MAX_RAMP - 1);\n const rest = shared.slice(MAX_RAMP - 1);\n const othersValue = rest.reduce((sum, slice) => sum + slice.value, 0);\n const othersShare = rest.reduce((sum, slice) => sum + slice.share, 0);\n\n return [\n ...kept.map((slice, index) => toRanked(slice, (index + 1) as Ramp)),\n toRanked(\n { id: OTHERS_ID, label: othersLabel, value: othersValue, share: othersShare },\n MAX_RAMP,\n rest.map((slice) => slice.label),\n ),\n ];\n}\n","import { NgTemplateOutlet } from '@angular/common';\nimport { Component, computed, input, output, signal } from '@angular/core';\n\nimport { RADIUS, RING_WIDTH, positionSlices } from './internal/donut-geometry';\nimport { type DonutSlice, type Ramp, type RankedSlice, rankSlices, shareSlices } from './internal/donut-slices';\n\nexport type { DonutSlice };\n\nconst identityValue = (value: number): string => `${value}`;\nconst identityShare = (share: number): string => `${Math.round(share * 100)}%`;\n\nconst ROW_CLASSES =\n 'flex w-full items-center gap-3 min-h-14 px-2 py-1 [font-variant-numeric:var(--numeric)] rounded-control text-left transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out hover:bg-(--glow) active:bg-(--soft) active:scale-(--press-scale) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring)';\n\nconst SLICE_CLASSES =\n 'cursor-pointer origin-center [transform-box:view-box] transition-[scale,opacity] duration-(--duration-fast) ease-out';\n\nconst RAMP_DOT_CLASSES: Record<Ramp, string> = {\n 1: 'bg-(--ramp-1)',\n 2: 'bg-(--ramp-2)',\n 3: 'bg-(--ramp-3)',\n 4: 'bg-(--ramp-4)',\n 5: 'bg-(--ramp-5)',\n 6: 'bg-(--ramp-6)',\n};\n\n/**\n * A ring chart with a legend: slices rank from largest to smallest, and hovering, focusing or\n * activating a legend row highlights its slice.\n *\n * @example\n * <ui-donut [slices]=\"byAssetClass()\" label=\"By asset class\" othersLabel=\"Other\" [valueFormat]=\"formatEur\" [shareFormat]=\"formatShare\" (sliceSelect)=\"goToHoldings($event)\" />\n */\n@Component({\n selector: 'ui-donut',\n template: `\n <div class=\"relative size-[232px] flex-none sm:size-60\">\n <svg class=\"block h-full w-full -rotate-90\" role=\"img\" viewBox=\"0 0 200 200\" [attr.aria-label]=\"ariaLabel()\">\n <circle\n class=\"stroke-(--muted)\"\n cx=\"100\"\n cy=\"100\"\n fill=\"none\"\n [attr.r]=\"radius\"\n [attr.stroke-width]=\"ringWidth\"\n />\n @for (slice of positioned(); track slice.id) {\n <circle\n cx=\"100\"\n cy=\"100\"\n data-slice\n fill=\"none\"\n [attr.r]=\"radius\"\n [attr.stroke-dasharray]=\"slice.dash\"\n [attr.stroke-dashoffset]=\"slice.offset\"\n [attr.stroke-width]=\"ringWidth\"\n [class]=\"sliceClasses(slice)\"\n [style.stroke]=\"strokeColor(slice)\"\n (click)=\"select(slice)\"\n (pointerenter)=\"highlight(slice.id)\"\n (pointerleave)=\"clearHighlight(slice.id)\"\n />\n }\n </svg>\n\n @if (active(); as slice) {\n <div\n class=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center px-12 text-center\"\n data-testid=\"donut-centre\"\n >\n <span class=\"text-label max-w-full truncate font-medium whitespace-nowrap text-(--muted-foreground)\">{{\n slice.label\n }}</span>\n <span class=\"text-heading font-semibold tracking-(--tracking-display)\">{{ shareFormat()(slice.share) }}</span>\n <span class=\"text-caption tracking-normal text-(--muted-foreground) [font-variant-numeric:var(--numeric)]\">{{\n valueFormat()(slice.value)\n }}</span>\n </div>\n }\n </div>\n\n <div class=\"flex w-full min-w-0 flex-1 flex-col sm:w-auto\" data-testid=\"donut-legend\">\n @for (slice of ranked(); track slice.id) {\n @if (hrefFor(slice.id); as href) {\n <a\n [attr.href]=\"href\"\n [class]=\"rowClasses(slice)\"\n (blur)=\"clearHighlight(slice.id)\"\n (click)=\"follow($event, slice)\"\n (focus)=\"highlight(slice.id)\"\n (pointerenter)=\"highlight(slice.id)\"\n (pointerleave)=\"clearHighlight(slice.id)\"\n >\n <ng-container *ngTemplateOutlet=\"rowContent; context: { $implicit: slice }\" />\n </a>\n } @else {\n <button\n type=\"button\"\n [class]=\"rowClasses(slice)\"\n (blur)=\"clearHighlight(slice.id)\"\n (click)=\"select(slice)\"\n (focus)=\"highlight(slice.id)\"\n (pointerenter)=\"highlight(slice.id)\"\n (pointerleave)=\"clearHighlight(slice.id)\"\n >\n <ng-container *ngTemplateOutlet=\"rowContent; context: { $implicit: slice }\" />\n </button>\n }\n }\n </div>\n\n <ng-template #rowContent let-slice>\n <span [class]=\"dotClasses(slice)\"></span>\n <span class=\"flex min-w-0 flex-1 flex-col\">\n <span class=\"text-body truncate font-medium\">{{ slice.label }}</span>\n <span class=\"text-caption truncate tracking-(--tracking-caption) text-(--subtle-foreground)\">{{\n subLabel(slice)\n }}</span>\n </span>\n <span class=\"flex flex-none flex-col items-end whitespace-nowrap\">\n <span class=\"text-body font-medium\">{{ shareFormat()(slice.share) }}</span>\n <span class=\"text-caption tracking-normal text-(--muted-foreground)\">{{ valueFormat()(slice.value) }}</span>\n </span>\n </ng-template>\n `,\n imports: [NgTemplateOutlet],\n host: {\n class: 'flex flex-col items-center gap-3 sm:flex-row sm:items-center sm:gap-8',\n },\n})\nexport class UiDonut {\n readonly slices = input.required<DonutSlice[]>();\n readonly label = input.required<string>();\n readonly othersLabel = input.required<string>();\n readonly valueFormat = input<(value: number) => string>(identityValue);\n readonly shareFormat = input<(share: number) => string>(identityShare);\n\n readonly legendHref = input<((id: string) => string | null) | undefined>();\n\n readonly sliceSelect = output<DonutSlice['id']>();\n\n protected readonly activeId = signal<string | null>(null);\n\n protected readonly ranked = computed(() => rankSlices(this.slices(), this.othersLabel()));\n protected readonly positioned = computed(() => positionSlices(this.ranked()));\n\n protected readonly active = computed<RankedSlice | null>(() => {\n const ranked = this.ranked();\n const id = this.activeId();\n\n return ranked.find((slice) => slice.id === id) ?? ranked[0] ?? null;\n });\n\n protected readonly ariaLabel = computed(() => {\n const detail = shareSlices(this.slices())\n .map((slice) => `${slice.label} ${this.shareFormat()(slice.share)}`)\n .join(', ');\n\n return detail ? `${this.label()}: ${detail}` : this.label();\n });\n\n protected readonly radius = RADIUS;\n protected readonly ringWidth = RING_WIDTH;\n\n protected strokeColor(slice: RankedSlice): string {\n return `var(--ramp-${slice.ramp})`;\n }\n\n protected sliceClasses(slice: RankedSlice): string {\n const id = this.activeId();\n if (id === null) {\n return SLICE_CLASSES;\n }\n\n return `${SLICE_CLASSES} ${id === slice.id ? 'scale-104 motion-reduce:scale-100' : 'opacity-50'}`;\n }\n\n protected dotClasses(slice: RankedSlice): string {\n return `size-3 flex-none rounded-pill shadow-[inset_0_0_0_1px_var(--border)] ${RAMP_DOT_CLASSES[slice.ramp]}`;\n }\n\n protected rowClasses(slice: RankedSlice): string {\n return `${ROW_CLASSES}${this.isActive(slice) ? ' bg-(--soft)' : ''}`;\n }\n\n protected subLabel(slice: RankedSlice): string {\n return slice.members.length > 0 ? slice.members.join(', ') : (slice.sublabel ?? '');\n }\n\n protected isActive(slice: RankedSlice): boolean {\n return this.active()?.id === slice.id;\n }\n\n protected highlight(id: string): void {\n this.activeId.set(id);\n }\n\n protected clearHighlight(id: string): void {\n if (this.activeId() === id) {\n this.activeId.set(null);\n }\n }\n\n protected hrefFor(id: string): string | null {\n return this.legendHref()?.(id) ?? null;\n }\n\n protected follow(event: MouseEvent, slice: RankedSlice): void {\n if (event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) {\n return;\n }\n event.preventDefault();\n this.select(slice);\n }\n\n protected select(slice: RankedSlice): void {\n this.activeId.set(slice.id);\n this.sliceSelect.emit(slice.id);\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"joanroucoux-cairn-ui-drawer.mjs","names":[],"sources":["../../../projects/ui/drawer/internal/drawer-styles.ts","../../../projects/ui/drawer/drawer.ts"],"sourcesContent":["export const DRAWER_STYLES = `\ndialog {\n transition:\n opacity var(--duration-base) var(--ease-out),\n transform var(--duration-base) var(--ease-out),\n overlay var(--duration-base) allow-discrete,\n display var(--duration-base) allow-discrete;\n opacity: 1;\n transform: none;\n}\n\ndialog:not([open]) {\n opacity: 0;\n transform: translateX(24px);\n transition-duration: var(--duration-exit);\n}\n\n@starting-style {\n dialog[open] {\n opacity: 0;\n transform: translateX(24px);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n dialog:not([open]) {\n transform: none;\n }\n\n @starting-style {\n dialog[open] {\n transform: none;\n }\n }\n}\n\ndialog::backdrop {\n transition: opacity var(--duration-base) var(--ease-out);\n opacity: 1;\n}\n\ndialog:not([open])::backdrop {\n opacity: 0;\n transition-duration: var(--duration-exit);\n}\n\n@starting-style {\n dialog[open]::backdrop {\n opacity: 0;\n }\n}\n`;\n","import { Component, booleanAttribute, computed, input, output } from '@angular/core';\n\nimport { ModalClose } from '@joanroucoux/cairn-ui/dialog';\n\nimport { DRAWER_STYLES } from './internal/drawer-styles';\n\n/** What started a close, as `closed` reports it. */\nexport const DRAWER_CLOSE_REASONS = ['escape', 'backdrop', 'cross', 'programmatic'] as const;\nexport type DrawerCloseReason = (typeof DRAWER_CLOSE_REASONS)[number];\n\nconst nextId = (() => {\n let count = 0;\n\n return () => `ui-drawer-${++count}`;\n})();\n\n/**\n * Modal side panel on the native <dialog>, against the right edge at full height, for a detail opened from a list.\n *\n * Without `heading` it draws no header: the content brings its own title and close button, and `label` names it.\n * With both, `label` names the panel and the heading stays its visible title.\n *\n * @example\n * <ui-drawer heading=\"Northwind Monde\" closeLabel=\"Fermer le détail\" [open]=\"open()\" (dismissed)=\"open.set(false)\">\n * <p>Body</p>\n * </ui-drawer>\n */\n@Component({\n selector: 'ui-drawer',\n host: {\n '(pointerdown)': 'modal.onPointerDown($event)',\n '(click)': 'modal.onClick($event)',\n '(keydown)': 'modal.onKeydown($event)',\n },\n template: `\n <dialog\n class=\"fixed inset-y-0 right-0 left-auto m-0 h-dvh max-h-none w-(--drawer-width) max-w-full [scrollbar-width:none] flex-col gap-6 overflow-y-auto overscroll-contain bg-(--card) p-6 text-(--foreground) shadow-[-1px_0_0_var(--border),-24px_0_48px_rgb(0_0_0/0.12)] outline-none backdrop:bg-black/[0.36] open:flex\"\n [attr.aria-busy]=\"busy() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-labelledby]=\"labelledBy()\"\n [style.--drawer-width]=\"width()\"\n (cancel)=\"modal.onCancel($event)\"\n (close)=\"modal.onNativeClose()\"\n >\n @if (heading()) {\n <div class=\"flex items-start justify-between gap-3\">\n <div class=\"flex min-w-0 flex-col gap-0.5\">\n <h2 class=\"text-title font-semibold text-pretty\" [id]=\"headingId\">{{ heading() }}</h2>\n\n @if (description()) {\n <p class=\"text-label text-(--muted-foreground)\" [id]=\"descriptionId\">{{ description() }}</p>\n }\n </div>\n\n @if (closeLabel()) {\n <button\n class=\"rounded-control -mt-1 -mr-2 grid size-9 flex-none cursor-pointer place-items-center text-(--muted-foreground) transition-[scale] duration-(--duration-press) ease-out outline-none hover:bg-(--glow) hover:text-(--foreground) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring) active:scale-(--press-scale) disabled:pointer-events-none disabled:opacity-40\"\n type=\"button\"\n [attr.aria-label]=\"closeLabel()\"\n [disabled]=\"busy()\"\n (click)=\"modal.close('cross')\"\n >\n <svg\n aria-hidden=\"true\"\n class=\"block size-5 fill-none stroke-current stroke-[1.75]\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n </div>\n }\n\n <ng-content />\n </dialog>\n `,\n styles: DRAWER_STYLES,\n})\nexport class UiDrawer {\n readonly heading = input<string>();\n readonly description = input<string>();\n readonly closeLabel = input<string>();\n readonly label = input<string>();\n readonly width = input('440px');\n readonly open = input(false, { transform: booleanAttribute });\n readonly busy = input(false, { transform: booleanAttribute });\n readonly dismissed = output<void>();\n readonly closed = output<DrawerCloseReason>();\n\n protected readonly headingId = nextId();\n protected readonly descriptionId = `${this.headingId}-description`;\n\n protected readonly describedBy = computed(() => (this.heading() && this.description() ? this.descriptionId : null));\n\n protected readonly ariaLabel = computed(() => {\n if (this.label()) {\n return this.label();\n }\n if (!this.heading()) {\n throw new Error('ui-drawer needs a heading or a label: without either it has no accessible name.');\n }\n return null;\n });\n\n protected readonly labelledBy = computed(() => (this.heading() && !this.label() ? this.headingId : null));\n\n protected readonly modal = new ModalClose<DrawerCloseReason>({\n open: this.open,\n busy: this.busy,\n dismissed: this.dismissed,\n closed: this.closed,\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"joanroucoux-cairn-ui-drawer.mjs","names":[],"sources":["../../../projects/ui/drawer/internal/drawer-styles.ts","../../../projects/ui/drawer/drawer.ts"],"sourcesContent":["export const DRAWER_STYLES = `\ndialog {\n transition:\n opacity var(--duration-base) var(--ease-out),\n transform var(--duration-base) var(--ease-out),\n overlay var(--duration-base) allow-discrete,\n display var(--duration-base) allow-discrete;\n opacity: 1;\n transform: none;\n}\n\ndialog:not([open]) {\n opacity: 0;\n transform: translateX(24px);\n transition-duration: var(--duration-exit);\n}\n\n@starting-style {\n dialog[open] {\n opacity: 0;\n transform: translateX(24px);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n dialog:not([open]) {\n transform: none;\n }\n\n @starting-style {\n dialog[open] {\n transform: none;\n }\n }\n}\n\ndialog::backdrop {\n transition: opacity var(--duration-base) var(--ease-out);\n opacity: 1;\n}\n\ndialog:not([open])::backdrop {\n opacity: 0;\n transition-duration: var(--duration-exit);\n}\n\n@starting-style {\n dialog[open]::backdrop {\n opacity: 0;\n }\n}\n`;\n","import { Component, booleanAttribute, computed, input, output } from '@angular/core';\n\nimport { ModalClose } from '@joanroucoux/cairn-ui/dialog';\n\nimport { DRAWER_STYLES } from './internal/drawer-styles';\n\n/** What started a close, as `closed` reports it. */\nexport const DRAWER_CLOSE_REASONS = ['escape', 'backdrop', 'cross', 'programmatic'] as const;\nexport type DrawerCloseReason = (typeof DRAWER_CLOSE_REASONS)[number];\n\nconst nextId = (() => {\n let count = 0;\n\n return () => `ui-drawer-${++count}`;\n})();\n\n/**\n * Modal side panel on the native <dialog>, against the right edge at full height, for a detail opened from a list.\n *\n * Without `heading` it draws no header: the content brings its own title and close button, and `label` names it.\n * With both, `label` names the panel and the heading stays its visible title.\n *\n * @example\n * <ui-drawer heading=\"Northwind World\" closeLabel=\"Close the detail\" [open]=\"open()\" (dismissed)=\"open.set(false)\">\n * <p>Body</p>\n * </ui-drawer>\n */\n@Component({\n selector: 'ui-drawer',\n host: {\n '(pointerdown)': 'modal.onPointerDown($event)',\n '(click)': 'modal.onClick($event)',\n '(keydown)': 'modal.onKeydown($event)',\n },\n template: `\n <dialog\n class=\"fixed inset-y-0 right-0 left-auto m-0 h-dvh max-h-none w-(--drawer-width) max-w-full [scrollbar-width:none] flex-col gap-6 overflow-y-auto overscroll-contain bg-(--card) p-6 text-(--foreground) shadow-[-1px_0_0_var(--border),-24px_0_48px_rgb(0_0_0/0.12)] outline-none backdrop:bg-black/[0.36] open:flex\"\n [attr.aria-busy]=\"busy() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-labelledby]=\"labelledBy()\"\n [style.--drawer-width]=\"width()\"\n (cancel)=\"modal.onCancel($event)\"\n (close)=\"modal.onNativeClose()\"\n >\n @if (heading()) {\n <div class=\"flex items-start justify-between gap-3\">\n <div class=\"flex min-w-0 flex-col gap-0.5\">\n <h2 class=\"text-title font-semibold text-pretty\" [id]=\"headingId\">{{ heading() }}</h2>\n\n @if (description()) {\n <p class=\"text-label text-(--muted-foreground)\" [id]=\"descriptionId\">{{ description() }}</p>\n }\n </div>\n\n @if (closeLabel()) {\n <button\n class=\"rounded-control -mt-1 -mr-2 grid size-9 flex-none cursor-pointer place-items-center text-(--muted-foreground) transition-[scale] duration-(--duration-press) ease-out outline-none hover:bg-(--glow) hover:text-(--foreground) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring) active:scale-(--press-scale) disabled:pointer-events-none disabled:opacity-40\"\n type=\"button\"\n [attr.aria-label]=\"closeLabel()\"\n [disabled]=\"busy()\"\n (click)=\"modal.close('cross')\"\n >\n <svg\n aria-hidden=\"true\"\n class=\"block size-5 fill-none stroke-current stroke-[1.75]\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n </div>\n }\n\n <ng-content />\n </dialog>\n `,\n styles: DRAWER_STYLES,\n})\nexport class UiDrawer {\n readonly heading = input<string>();\n readonly description = input<string>();\n readonly closeLabel = input<string>();\n readonly label = input<string>();\n readonly width = input('440px');\n readonly open = input(false, { transform: booleanAttribute });\n readonly busy = input(false, { transform: booleanAttribute });\n readonly dismissed = output<void>();\n readonly closed = output<DrawerCloseReason>();\n\n protected readonly headingId = nextId();\n protected readonly descriptionId = `${this.headingId}-description`;\n\n protected readonly describedBy = computed(() => (this.heading() && this.description() ? this.descriptionId : null));\n\n protected readonly ariaLabel = computed(() => {\n if (this.label()) {\n return this.label();\n }\n if (!this.heading()) {\n throw new Error('ui-drawer needs a heading or a label: without either it has no accessible name.');\n }\n return null;\n });\n\n protected readonly labelledBy = computed(() => (this.heading() && !this.label() ? this.headingId : null));\n\n protected readonly modal = new ModalClose<DrawerCloseReason>({\n open: this.open,\n busy: this.busy,\n dismissed: this.dismissed,\n closed: this.closed,\n });\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"joanroucoux-cairn-ui-empty.mjs","names":[],"sources":["../../../projects/ui/empty/empty.ts"],"sourcesContent":["import { Component, input } from '@angular/core';\n\n/**\n * Centred empty state: a title, a hint and an action slot, such as a search that matches nothing.\n *\n * @example\n * <ui-empty heading=\"Aucune ligne ne correspond à « zzz »\" hint=\"La recherche porte sur le nom et l'ISIN.\">\n * <button ui-button variant=\"outline\" size=\"md\" type=\"button\">Ajouter une ligne</button>\n * </ui-empty>\n */\n@Component({\n selector: 'ui-empty',\n template: `\n <span class=\"text-body font-medium text-pretty\">{{ heading() }}</span>\n @if (hint()) {\n <span class=\"text-label text-(--muted-foreground)\">{{ hint() }}</span>\n }\n <div class=\"mt-2 empty:hidden\"><ng-content /></div>\n `,\n host: { class: 'flex flex-col items-center gap-1.5 px-2 py-6 text-center lg:py-12' },\n})\nexport class UiEmpty {\n readonly heading = input.required<string>();\n readonly hint = input('');\n}\n"],"mappings":";;AAqBA,IAAa,UAAb,MAAa,QAAO;CACT,UAAU,MAAM,SAAkB,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAClC,OAAO,MAAM,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAFd,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAO,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAP,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAO,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,MAAA,EAAA,gBAAA,oEAAA;EAAA,UAAA;EAAA,UATR;;;;;;;EAMT,UAAA;CAAA,CAAA;;AAGU,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAO,YAAA,CAAA;EAXnB,MAAA;EAAU,MAAA,CAAA;GACT,UAAU;GACV,UAAU;;;;;;;GAOV,MAAM,EAAE,OAAO,oEAAmE;EACnF,CAAA"}
1
+ {"version":3,"file":"joanroucoux-cairn-ui-empty.mjs","names":[],"sources":["../../../projects/ui/empty/empty.ts"],"sourcesContent":["import { Component, input } from '@angular/core';\n\n/**\n * Centered empty state: a title, a hint and an action slot, such as a search that matches nothing.\n *\n * @example\n * <ui-empty heading=\"No holding matches “zzz”\" hint=\"Search covers the name and the ISIN.\">\n * <button ui-button variant=\"outline\" size=\"md\" type=\"button\">Add a holding</button>\n * </ui-empty>\n */\n@Component({\n selector: 'ui-empty',\n template: `\n <span class=\"text-body font-medium text-pretty\">{{ heading() }}</span>\n @if (hint()) {\n <span class=\"text-label text-(--muted-foreground)\">{{ hint() }}</span>\n }\n <div class=\"mt-2 empty:hidden\"><ng-content /></div>\n `,\n host: { class: 'flex flex-col items-center gap-1.5 px-2 py-6 text-center lg:py-12' },\n})\nexport class UiEmpty {\n readonly heading = input.required<string>();\n readonly hint = input('');\n}\n"],"mappings":";;AAqBA,IAAa,UAAb,MAAa,QAAO;CACT,UAAU,MAAM,SAAkB,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAClC,OAAO,MAAM,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAFd,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAO,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAP,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAO,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,MAAA,EAAA,gBAAA,oEAAA;EAAA,UAAA;EAAA,UATR;;;;;;;EAMT,UAAA;CAAA,CAAA;;AAGU,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAO,YAAA,CAAA;EAXnB,MAAA;EAAU,MAAA,CAAA;GACT,UAAU;GACV,UAAU;;;;;;;GAOV,MAAM,EAAE,OAAO,oEAAmE;EACnF,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"joanroucoux-cairn-ui-fact.mjs","names":[],"sources":["../../../projects/ui/fact/fact.ts"],"sourcesContent":["import { Component, Directive, booleanAttribute, computed, input } from '@angular/core';\n\n/** Available fact row sizes. */\nexport const FACT_SIZES = ['md', 'sm', 'auto', 'recap'] as const;\nexport type FactSize = (typeof FACT_SIZES)[number];\n\n/** Colour of the line under a fact value. `stale` flags an out-of-date figure. */\nexport const FACT_SUB_TONES = ['subtle', 'stale'] as const;\nexport type FactSubTone = (typeof FACT_SUB_TONES)[number];\n\nconst SIZE_CLASSES: Record<FactSize, { row: string; value: string }> = {\n md: { row: 'gap-4 py-3 [&:not(:first-child)]:shadow-[inset_0_1px_0_var(--hairline)]', value: 'text-body' },\n sm: { row: 'gap-4 py-2.5', value: 'text-label' },\n auto: {\n row: 'gap-4 py-3 lg:py-2.5 max-lg:[&:not(:first-child)]:shadow-[inset_0_1px_0_var(--hairline)]',\n value: 'text-body lg:text-label',\n },\n recap: { row: 'gap-3 py-[9px] [&:not(:first-child)]:shadow-[inset_0_1px_0_var(--hairline)]', value: 'text-label' },\n};\n\nconst SUB_TONE_CLASSES: Record<FactSubTone, string> = {\n subtle: 'text-(--subtle-foreground)',\n stale: 'text-(--stale)',\n};\n\n/**\n * The list that holds `div[ui-fact]` rows, as a native description list.\n *\n * @example\n * <dl uiFacts>\n * <div ui-fact label=\"Quantity\">500</div>\n * <div ui-fact label=\"Price\" sub=\"24/09 · Yahoo\">28,64 €</div>\n * </dl>\n */\n@Directive({\n selector: 'dl[uiFacts]',\n host: { '[class]': 'classes()' },\n})\nexport class UiFacts {\n readonly ruled = input(false, { transform: booleanAttribute });\n\n protected readonly classes = computed(() =>\n this.ruled() ? 'm-0 flex flex-col shadow-[inset_0_1px_0_var(--hairline)]' : 'm-0 flex flex-col',\n );\n}\n\n/**\n * One fact of a `dl[uiFacts]`: a label, its value (the projected content) and an optional sub-line\n * under the value.\n *\n * @example\n * <div ui-fact label=\"Price\" sub=\"24/09 · Yahoo\" subTone=\"stale\">28,64 €</div>\n */\n@Component({\n selector: 'div[ui-fact]',\n template: `\n <dt class=\"text-label text-(--muted-foreground)\">{{ label() }}</dt>\n <dd class=\"m-0 flex flex-col items-end text-right\">\n <span [class]=\"valueClasses()\"><ng-content /></span>\n @if (sub()) {\n <span [class]=\"subClasses()\">{{ sub() }}</span>\n }\n </dd>\n `,\n host: { '[class]': 'classes()' },\n})\nexport class UiFact {\n readonly label = input.required<string>();\n readonly sub = input<string>();\n readonly subTone = input<FactSubTone>('subtle');\n readonly size = input<FactSize>('md');\n\n protected readonly classes = computed(() => `flex items-baseline justify-between ${SIZE_CLASSES[this.size()].row}`);\n protected readonly valueClasses = computed(() => `${SIZE_CLASSES[this.size()].value} tabular-nums`);\n protected readonly subClasses = computed(() => `text-caption ${SUB_TONE_CLASSES[this.subTone()]}`);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"joanroucoux-cairn-ui-fact.mjs","names":[],"sources":["../../../projects/ui/fact/fact.ts"],"sourcesContent":["import { Component, Directive, booleanAttribute, computed, input } from '@angular/core';\n\n/** Available fact row sizes. */\nexport const FACT_SIZES = ['md', 'sm', 'auto', 'recap'] as const;\nexport type FactSize = (typeof FACT_SIZES)[number];\n\n/** Color of the line under a fact value. `stale` flags an out-of-date figure. */\nexport const FACT_SUB_TONES = ['subtle', 'stale'] as const;\nexport type FactSubTone = (typeof FACT_SUB_TONES)[number];\n\nconst SIZE_CLASSES: Record<FactSize, { row: string; value: string }> = {\n md: { row: 'gap-4 py-3 [&:not(:first-child)]:shadow-[inset_0_1px_0_var(--hairline)]', value: 'text-body' },\n sm: { row: 'gap-4 py-2.5', value: 'text-label' },\n auto: {\n row: 'gap-4 py-3 lg:py-2.5 max-lg:[&:not(:first-child)]:shadow-[inset_0_1px_0_var(--hairline)]',\n value: 'text-body lg:text-label',\n },\n recap: { row: 'gap-3 py-[9px] [&:not(:first-child)]:shadow-[inset_0_1px_0_var(--hairline)]', value: 'text-label' },\n};\n\nconst SUB_TONE_CLASSES: Record<FactSubTone, string> = {\n subtle: 'text-(--subtle-foreground)',\n stale: 'text-(--stale)',\n};\n\n/**\n * The list that holds `div[ui-fact]` rows, as a native description list.\n *\n * @example\n * <dl uiFacts>\n * <div ui-fact label=\"Quantity\">500</div>\n * <div ui-fact label=\"Price\" sub=\"24/09 · Yahoo\">28,64 €</div>\n * </dl>\n */\n@Directive({\n selector: 'dl[uiFacts]',\n host: { '[class]': 'classes()' },\n})\nexport class UiFacts {\n readonly ruled = input(false, { transform: booleanAttribute });\n\n protected readonly classes = computed(() =>\n this.ruled() ? 'm-0 flex flex-col shadow-[inset_0_1px_0_var(--hairline)]' : 'm-0 flex flex-col',\n );\n}\n\n/**\n * One fact of a `dl[uiFacts]`: a label, its value (the projected content) and an optional sub-line\n * under the value.\n *\n * @example\n * <div ui-fact label=\"Price\" sub=\"24/09 · Yahoo\" subTone=\"stale\">28,64 €</div>\n */\n@Component({\n selector: 'div[ui-fact]',\n template: `\n <dt class=\"text-label text-(--muted-foreground)\">{{ label() }}</dt>\n <dd class=\"m-0 flex flex-col items-end text-right\">\n <span [class]=\"valueClasses()\"><ng-content /></span>\n @if (sub()) {\n <span [class]=\"subClasses()\">{{ sub() }}</span>\n }\n </dd>\n `,\n host: { '[class]': 'classes()' },\n})\nexport class UiFact {\n readonly label = input.required<string>();\n readonly sub = input<string>();\n readonly subTone = input<FactSubTone>('subtle');\n readonly size = input<FactSize>('md');\n\n protected readonly classes = computed(() => `flex items-baseline justify-between ${SIZE_CLASSES[this.size()].row}`);\n protected readonly valueClasses = computed(() => `${SIZE_CLASSES[this.size()].value} tabular-nums`);\n protected readonly subClasses = computed(() => `text-caption ${SUB_TONE_CLASSES[this.subTone()]}`);\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"joanroucoux-cairn-ui-group-header.mjs","names":[],"sources":["../../../projects/ui/group-header/group-header.ts"],"sourcesContent":["import { NgTemplateOutlet } from '@angular/common';\nimport { Component, booleanAttribute, input, model } from '@angular/core';\n\n/**\n * Header of a card on a phone: the name, its meta underneath, the projected total on the right.\n *\n * With `collapsible` the header is a button inside the heading, with a chevron that turns when the\n * group is folded and a press scale. `expanded` is a model: the page keeps the fold memory and hides\n * the card body itself. `toggleDisabled` keeps the button focusable and announced, but a click\n * changes nothing (a filter holds the group open). `controls` is the id of the body it folds.\n *\n * @example\n * <ui-group-header name=\"Northwind PEA\" meta=\"PEA · 3 lignes\" collapsible [(expanded)]=\"open\">\n * <ui-amount [value]=\"total\" />\n * </ui-group-header>\n */\n@Component({\n selector: 'ui-group-header',\n imports: [NgTemplateOutlet],\n host: { class: 'block' },\n template: `\n <ng-template #total><ng-content /></ng-template>\n @if (collapsible()) {\n <h2 class=\"m-0 font-normal\">\n <button\n class=\"rounded-control flex min-h-11 w-full items-end justify-between gap-3 px-1 pt-3 text-left transition-transform [transition-duration:var(--duration-press)] ease-out not-aria-disabled:cursor-pointer focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring) not-aria-disabled:active:scale-(--press-scale)\"\n type=\"button\"\n [attr.aria-controls]=\"controls() || null\"\n [attr.aria-disabled]=\"toggleDisabled() ? 'true' : null\"\n [attr.aria-expanded]=\"expanded()\"\n (click)=\"toggle()\"\n >\n <span class=\"flex min-w-0 items-center gap-2\">\n <svg\n aria-hidden=\"true\"\n class=\"flex-none stroke-(--muted-foreground) transition-transform duration-(--duration-fast) ease-out\"\n fill=\"none\"\n height=\"20\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n viewBox=\"0 0 24 24\"\n width=\"20\"\n [class.-rotate-90]=\"!expanded()\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n <span class=\"flex min-w-0 flex-col\">\n <span class=\"text-title font-semibold\">{{ name() }}</span>\n @if (meta()) {\n <span class=\"text-label text-(--muted-foreground)\">{{ meta() }}</span>\n }\n </span>\n </span>\n <span class=\"text-title font-semibold whitespace-nowrap tabular-nums\">\n <ng-container [ngTemplateOutlet]=\"total\" />\n </span>\n </button>\n </h2>\n } @else {\n <div class=\"flex min-h-11 w-full items-end justify-between gap-3 px-1 pt-3\">\n <div class=\"flex min-w-0 flex-col\">\n <h2 class=\"text-title m-0 font-semibold\">{{ name() }}</h2>\n @if (meta()) {\n <span class=\"text-label text-(--muted-foreground)\">{{ meta() }}</span>\n }\n </div>\n <span class=\"text-title font-semibold whitespace-nowrap tabular-nums\">\n <ng-container [ngTemplateOutlet]=\"total\" />\n </span>\n </div>\n }\n `,\n})\nexport class UiGroupHeader {\n readonly name = input.required<string>();\n readonly meta = input<string>();\n readonly collapsible = input(false, { transform: booleanAttribute });\n readonly expanded = model(true);\n readonly toggleDisabled = input(false, { transform: booleanAttribute });\n readonly controls = input<string>();\n\n protected toggle(): void {\n if (!this.toggleDisabled()) {\n this.expanded.update((expanded) => !expanded);\n }\n }\n}\n"],"mappings":";;;AA0EA,IAAa,gBAAb,MAAa,cAAa;CACf,OAAO,MAAM,SAAkB,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAC/B,OAAO,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CACtB,cAAc,MAAM,OAAK;EAAA,GAAA,YAAA,EAAA,WAAA,cAAA,+BAAA,CAAA;EAAI,WAAW;CAAgB,CAAA;CACxD,WAAW,MAAM,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CACtB,iBAAiB,MAAM,OAAK;EAAA,GAAA,YAAA,EAAA,WAAA,iBAAA,+BAAA,CAAA;EAAI,WAAW;CAAgB,CAAA;CAC3D,WAAW,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAEzB,SAAM;EACd,IAAI,CAAC,KAAK,eAAc,GACtB,KAAK,SAAS,QAAQ,aAAa,CAAC,QAAQ;CAEhD;CAZW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAa,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAb,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAa,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,SAAA,EAAA,UAAA,iBAAA;EAAA,MAAA,EAAA,gBAAA,QAAA;EAAA,UAAA;EAAA,UAtDd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoDT,UAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MAtDS;GAAgB,UAAA;GAAA,QAAA;IAAA;IAAA;IAAA;GAAA;EAAA,CAAA;CAAA,CAAA;;AAwDf,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAa,YAAA,CAAA;EA1DzB,MAAA;EAAU,MAAA,CAAA;GACT,UAAU;GACV,SAAS,CAAC,gBAAgB;GAC1B,MAAM,EAAE,OAAO,QAAO;GACtB,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqDX,CAAA"}
1
+ {"version":3,"file":"joanroucoux-cairn-ui-group-header.mjs","names":[],"sources":["../../../projects/ui/group-header/group-header.ts"],"sourcesContent":["import { NgTemplateOutlet } from '@angular/common';\nimport { Component, booleanAttribute, input, model } from '@angular/core';\n\n/**\n * Header of a card on a phone: the name, its meta underneath, the projected total on the right.\n *\n * With `collapsible` the header is a button inside the heading, with a chevron that turns when the\n * group is folded and a press scale. `expanded` is a model: the page keeps the fold memory and hides\n * the card body itself. `toggleDisabled` keeps the button focusable and announced, but a click\n * changes nothing (a filter holds the group open). `controls` is the id of the body it folds.\n *\n * @example\n * <ui-group-header name=\"Northwind PEA\" meta=\"PEA · 3 holdings\" collapsible [(expanded)]=\"open\">\n * <ui-amount [value]=\"total\" />\n * </ui-group-header>\n */\n@Component({\n selector: 'ui-group-header',\n imports: [NgTemplateOutlet],\n host: { class: 'block' },\n template: `\n <ng-template #total><ng-content /></ng-template>\n @if (collapsible()) {\n <h2 class=\"m-0 font-normal\">\n <button\n class=\"rounded-control flex min-h-11 w-full items-end justify-between gap-3 px-1 pt-3 text-left transition-transform [transition-duration:var(--duration-press)] ease-out not-aria-disabled:cursor-pointer focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring) not-aria-disabled:active:scale-(--press-scale)\"\n type=\"button\"\n [attr.aria-controls]=\"controls() || null\"\n [attr.aria-disabled]=\"toggleDisabled() ? 'true' : null\"\n [attr.aria-expanded]=\"expanded()\"\n (click)=\"toggle()\"\n >\n <span class=\"flex min-w-0 items-center gap-2\">\n <svg\n aria-hidden=\"true\"\n class=\"flex-none stroke-(--muted-foreground) transition-transform duration-(--duration-fast) ease-out\"\n fill=\"none\"\n height=\"20\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n viewBox=\"0 0 24 24\"\n width=\"20\"\n [class.-rotate-90]=\"!expanded()\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n <span class=\"flex min-w-0 flex-col\">\n <span class=\"text-title font-semibold\">{{ name() }}</span>\n @if (meta()) {\n <span class=\"text-label text-(--muted-foreground)\">{{ meta() }}</span>\n }\n </span>\n </span>\n <span class=\"text-title font-semibold whitespace-nowrap tabular-nums\">\n <ng-container [ngTemplateOutlet]=\"total\" />\n </span>\n </button>\n </h2>\n } @else {\n <div class=\"flex min-h-11 w-full items-end justify-between gap-3 px-1 pt-3\">\n <div class=\"flex min-w-0 flex-col\">\n <h2 class=\"text-title m-0 font-semibold\">{{ name() }}</h2>\n @if (meta()) {\n <span class=\"text-label text-(--muted-foreground)\">{{ meta() }}</span>\n }\n </div>\n <span class=\"text-title font-semibold whitespace-nowrap tabular-nums\">\n <ng-container [ngTemplateOutlet]=\"total\" />\n </span>\n </div>\n }\n `,\n})\nexport class UiGroupHeader {\n readonly name = input.required<string>();\n readonly meta = input<string>();\n readonly collapsible = input(false, { transform: booleanAttribute });\n readonly expanded = model(true);\n readonly toggleDisabled = input(false, { transform: booleanAttribute });\n readonly controls = input<string>();\n\n protected toggle(): void {\n if (!this.toggleDisabled()) {\n this.expanded.update((expanded) => !expanded);\n }\n }\n}\n"],"mappings":";;;AA0EA,IAAa,gBAAb,MAAa,cAAa;CACf,OAAO,MAAM,SAAkB,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAC/B,OAAO,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CACtB,cAAc,MAAM,OAAK;EAAA,GAAA,YAAA,EAAA,WAAA,cAAA,+BAAA,CAAA;EAAI,WAAW;CAAgB,CAAA;CACxD,WAAW,MAAM,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CACtB,iBAAiB,MAAM,OAAK;EAAA,GAAA,YAAA,EAAA,WAAA,iBAAA,+BAAA,CAAA;EAAI,WAAW;CAAgB,CAAA;CAC3D,WAAW,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAEzB,SAAM;EACd,IAAI,CAAC,KAAK,eAAc,GACtB,KAAK,SAAS,QAAQ,aAAa,CAAC,QAAQ;CAEhD;CAZW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAa,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAb,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAa,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,SAAA,EAAA,UAAA,iBAAA;EAAA,MAAA,EAAA,gBAAA,QAAA;EAAA,UAAA;EAAA,UAtDd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoDT,UAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MAtDS;GAAgB,UAAA;GAAA,QAAA;IAAA;IAAA;IAAA;GAAA;EAAA,CAAA;CAAA,CAAA;;AAwDf,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAa,YAAA,CAAA;EA1DzB,MAAA;EAAU,MAAA,CAAA;GACT,UAAU;GACV,SAAS,CAAC,gBAAgB;GAC1B,MAAM,EAAE,OAAO,QAAO;GACtB,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqDX,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"joanroucoux-cairn-ui-input.mjs","names":[],"sources":["../../../projects/ui/input/input.ts"],"sourcesContent":["import { Directive, computed, forwardRef, input } from '@angular/core';\n\nimport { UI_CONTROL, type UiControl, type UiControlError } from '@joanroucoux/cairn-ui/control';\n\n/** Available control sizes. */\nexport const CONTROL_SIZES = ['sm', 'md', 'lg', 'xl'] as const;\nexport type ControlSize = (typeof CONTROL_SIZES)[number];\n\nexport const CONTROL_BASE_CLASSES =\n 'w-full rounded-control px-3 text-body text-(--foreground) shadow-[inset_0_0_0_1px_var(--border)] placeholder:text-(--muted-foreground) focus-visible:shadow-[inset_0_0_0_2px_var(--ring)] aria-invalid:shadow-[inset_0_0_0_2px_var(--negative)] disabled:cursor-not-allowed disabled:opacity-50';\n\nexport const CONTROL_SURFACES = ['background', 'card'] as const;\nexport type ControlSurface = (typeof CONTROL_SURFACES)[number];\n\nexport const CONTROL_SURFACE_CLASSES: Record<ControlSurface, string> = {\n background: 'bg-(--background)',\n card: 'bg-(--card)',\n};\n\nexport const CONTROL_SIZE_CLASSES: Record<ControlSize, string> = {\n sm: 'h-9',\n md: 'min-h-(--row-min)',\n lg: 'min-h-12 lg:min-h-(--row-min)',\n xl: 'h-12 lg:h-11',\n};\n\nconst SEARCH_CLASSES = '[&::-webkit-search-cancel-button]:appearance-none';\n\n/**\n * Styled native text input. The host stays a plain `<input>`, so every forms flavor and native\n * attribute keeps working. Declares the `errors` and `touched` inputs Angular's `[formField]` fills,\n * so a surrounding `ui-field` can show the message.\n *\n * @example\n * <input uiInput type=\"email\" placeholder=\"you@example.com\" />\n */\n@Directive({\n selector: 'input[uiInput]',\n host: {\n '[class]': 'classes()',\n },\n providers: [{ provide: UI_CONTROL, useExisting: forwardRef(() => UiInput) }],\n})\nexport class UiInput implements UiControl {\n readonly size = input<ControlSize>('md');\n readonly surface = input<ControlSurface>('background');\n readonly errors = input<readonly UiControlError[]>([]);\n readonly touched = input(false);\n\n protected readonly classes = computed(\n () =>\n `${CONTROL_BASE_CLASSES} ${SEARCH_CLASSES} ${CONTROL_SURFACE_CLASSES[this.surface()]} ${CONTROL_SIZE_CLASSES[this.size()]}`,\n );\n}\n\n/**\n * Styled native textarea. It takes no size: it grows from a minimum height. Declares the `errors`\n * and `touched` inputs Angular's `[formField]` fills, so a surrounding `ui-field` can show the message.\n *\n * @example\n * <textarea uiTextarea maxlength=\"280\"></textarea>\n */\n@Directive({\n selector: 'textarea[uiTextarea]',\n host: {\n class: `${CONTROL_BASE_CLASSES} bg-(--background) min-h-24 py-2`,\n },\n providers: [{ provide: UI_CONTROL, useExisting: forwardRef(() => UiTextarea) }],\n})\nexport class UiTextarea implements UiControl {\n readonly errors = input<readonly UiControlError[]>([]);\n readonly touched = input(false);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"joanroucoux-cairn-ui-input.mjs","names":[],"sources":["../../../projects/ui/input/input.ts"],"sourcesContent":["import { Directive, computed, forwardRef, input } from '@angular/core';\n\nimport { UI_CONTROL, type UiControl, type UiControlError } from '@joanroucoux/cairn-ui/control';\n\n/** Available control sizes. */\nexport const CONTROL_SIZES = ['sm', 'md', 'lg', 'xl'] as const;\nexport type ControlSize = (typeof CONTROL_SIZES)[number];\n\n/** @internal Shared with `select` and `choice-chips`. */\nexport const CONTROL_BASE_CLASSES =\n 'w-full rounded-control px-3 text-body text-(--foreground) shadow-[inset_0_0_0_1px_var(--border)] placeholder:text-(--muted-foreground) focus-visible:shadow-[inset_0_0_0_2px_var(--ring)] aria-invalid:shadow-[inset_0_0_0_2px_var(--negative)] disabled:cursor-not-allowed disabled:opacity-50';\n\nexport const CONTROL_SURFACES = ['background', 'card'] as const;\nexport type ControlSurface = (typeof CONTROL_SURFACES)[number];\n\n/** @internal Shared with `select` and `choice-chips`. */\nexport const CONTROL_SURFACE_CLASSES: Record<ControlSurface, string> = {\n background: 'bg-(--background)',\n card: 'bg-(--card)',\n};\n\n/** @internal Shared with `select` and `choice-chips`. */\nexport const CONTROL_SIZE_CLASSES: Record<ControlSize, string> = {\n sm: 'h-9',\n md: 'min-h-(--row-min)',\n lg: 'min-h-12 lg:min-h-(--row-min)',\n xl: 'h-12 lg:h-11',\n};\n\nconst SEARCH_CLASSES = '[&::-webkit-search-cancel-button]:appearance-none';\n\n/**\n * Styled native text input. The host stays a plain `<input>`, so every forms flavor and native\n * attribute keeps working. Declares the `errors` and `touched` inputs Angular's `[formField]` fills,\n * so a surrounding `ui-field` can show the message.\n *\n * @example\n * <input uiInput type=\"email\" placeholder=\"you@example.com\" />\n */\n@Directive({\n selector: 'input[uiInput]',\n host: {\n '[class]': 'classes()',\n },\n providers: [{ provide: UI_CONTROL, useExisting: forwardRef(() => UiInput) }],\n})\nexport class UiInput implements UiControl {\n readonly size = input<ControlSize>('md');\n readonly surface = input<ControlSurface>('background');\n readonly errors = input<readonly UiControlError[]>([]);\n readonly touched = input(false);\n\n protected readonly classes = computed(\n () =>\n `${CONTROL_BASE_CLASSES} ${SEARCH_CLASSES} ${CONTROL_SURFACE_CLASSES[this.surface()]} ${CONTROL_SIZE_CLASSES[this.size()]}`,\n );\n}\n\n/**\n * Styled native textarea. It takes no size: it grows from a minimum height. Declares the `errors`\n * and `touched` inputs Angular's `[formField]` fills, so a surrounding `ui-field` can show the message.\n *\n * @example\n * <textarea uiTextarea maxlength=\"280\"></textarea>\n */\n@Directive({\n selector: 'textarea[uiTextarea]',\n host: {\n class: `${CONTROL_BASE_CLASSES} bg-(--background) min-h-24 py-2`,\n },\n providers: [{ provide: UI_CONTROL, useExisting: forwardRef(() => UiTextarea) }],\n})\nexport class UiTextarea implements UiControl {\n readonly errors = input<readonly UiControlError[]>([]);\n readonly touched = input(false);\n}\n"],"mappings":"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"}