@joanroucoux/cairn-ui 0.11.0 → 0.11.2

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.2
4
+
5
+ ### Fixed
6
+
7
+ - A tall `ui-dialog` sheet on mobile stops below the status bar of a home screen web app on iPhone (`env(safe-area-inset-top)`).
8
+
9
+ ## 0.11.1
10
+
11
+ ### Fixed
12
+
13
+ - `ui-menu` keeps its 8px margin from the edge of the page, not from the window, so a classic scrollbar no longer covers part of it near the right edge.
14
+
3
15
  ## 0.11.0
4
16
 
5
17
  ### Changed
@@ -280,7 +280,7 @@ var UiDialog = class UiDialog {
280
280
  headerClasses = computed(() => `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)]" : ""}`, ...ngDevMode ? [{ debugName: "headerClasses" }] : /* istanbul ignore next */ []);
281
281
  crossClasses = "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)";
282
282
  footerClasses = computed(() => `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)]" : ""}`, ...ngDevMode ? [{ debugName: "footerClasses" }] : /* istanbul ignore next */ []);
283
- classes = computed(() => `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)]" : ""}`, ...ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []);
283
+ classes = computed(() => `open:flex w-full flex-col overflow-hidden m-auto mt-auto max-lg:max-h-[calc(100dvh-2rem-env(safe-area-inset-top))] 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)]" : ""}`, ...ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []);
284
284
  #host = inject(ElementRef);
285
285
  #drag = null;
286
286
  modal;
@@ -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\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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"}
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-env(safe-area-inset-top))] 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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"}
@@ -193,16 +193,17 @@ var UiMenu = class UiMenu {
193
193
  }
194
194
  #position(trigger) {
195
195
  const margin = 8;
196
+ const { clientWidth: viewportWidth, clientHeight: viewportHeight } = document.documentElement;
196
197
  const triggerRect = trigger.getBoundingClientRect();
197
198
  const menuRect = {
198
199
  width: this.#layoutWidth(),
199
200
  height: this.#host.offsetHeight
200
201
  };
201
- const fitsBelow = window.innerHeight - triggerRect.bottom >= menuRect.height + margin;
202
+ const fitsBelow = viewportHeight - triggerRect.bottom >= menuRect.height + margin;
202
203
  const top = fitsBelow ? triggerRect.bottom + 4 : Math.max(margin, triggerRect.top - menuRect.height - 4);
203
- const left = Math.min(Math.max(triggerRect.right - menuRect.width, margin), window.innerWidth - menuRect.width - margin);
204
+ const left = Math.min(Math.max(triggerRect.right - menuRect.width, margin), viewportWidth - menuRect.width - margin);
204
205
  this.#host.style.position = "fixed";
205
- this.#host.style.top = `${Math.min(top, window.innerHeight - menuRect.height - margin)}px`;
206
+ this.#host.style.top = `${Math.min(top, viewportHeight - menuRect.height - margin)}px`;
206
207
  this.#host.style.left = `${left}px`;
207
208
  this.#host.style.right = "auto";
208
209
  this.#host.style.bottom = "auto";
@@ -1 +1 @@
1
- {"version":3,"file":"joanroucoux-cairn-ui-menu.mjs","names":[],"sources":["../../../projects/ui/menu/menu.ts"],"sourcesContent":["import {\n Component,\n Directive,\n ElementRef,\n booleanAttribute,\n computed,\n contentChildren,\n inject,\n input,\n numberAttribute,\n signal,\n} from '@angular/core';\n\nconst MENU_CLASSES =\n 'm-0 min-w-44 p-1 rounded-container bg-(--elevated) shadow-[0_8px_24px_rgb(0_0_0/0.16),inset_0_0_0_1px_var(--border)] text-(--foreground) transition-[opacity,scale,overlay,display] transition-discrete duration-(--duration-fast) not-open:duration-[120ms] ease-out starting:open:opacity-0 starting:open:scale-(--enter-scale) opacity-0 scale-(--enter-scale) open:opacity-100 open:scale-100';\n\nconst SHEET_CLASSES =\n 'max-lg:fixed max-lg:inset-x-4 max-lg:top-auto max-lg:w-auto max-lg:bottom-[calc(76px+env(safe-area-inset-bottom))] max-lg:min-w-0 max-lg:p-1.5 max-lg:bg-(--card) max-lg:shadow-[0_12px_32px_rgb(0_0_0/0.2),inset_0_0_0_1px_var(--border)] max-lg:backdrop:bg-[rgb(0_0_0/0.36)] max-lg:transition-[opacity,translate,overlay,display] max-lg:scale-100 max-lg:translate-y-4 max-lg:open:translate-y-0 max-lg:starting:open:scale-100 max-lg:starting:open:translate-y-4 max-lg:motion-reduce:translate-y-0 max-lg:motion-reduce:starting:open:translate-y-0';\n\nconst SHEET_ITEM_CLASSES =\n 'flex w-full items-center gap-3 lg:pointer-fine:gap-2.5 min-h-12 lg:min-h-11 lg:pointer-fine:min-h-9 px-3 lg:pointer-fine:px-2.5 rounded-[calc(var(--radius-container)-6px)] lg:rounded-[calc(var(--radius-container)-4px)] text-body lg:pointer-fine:text-label text-left whitespace-nowrap hover:bg-(--glow) focus-visible:bg-(--glow) active:bg-(--soft) transition-[background-color] duration-(--duration-fast) ease-out outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring) [:where(&>svg)]:size-5 [:where(&>svg)]:flex-none lg:[:where(&>svg)]:size-[18px] lg:pointer-fine:[:where(&>svg)]:size-4';\n\nconst ITEM_CLASSES =\n 'flex w-full items-center gap-3 pointer-fine:gap-2.5 min-h-11 pointer-fine:min-h-9 px-3 pointer-fine:px-2.5 rounded-[calc(var(--radius-container)-4px)] text-body pointer-fine:text-label text-left whitespace-nowrap hover:bg-(--glow) focus-visible:bg-(--glow) active:bg-(--soft) transition-[background-color] duration-(--duration-fast) ease-out outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring) [:where(&>svg)]:size-[18px] [:where(&>svg)]:flex-none pointer-fine:[:where(&>svg)]:size-4';\n\nconst CHECK_CLASSES =\n \"after:ms-auto after:block after:h-3 after:w-1.5 after:flex-none after:rotate-45 after:border-r-2 after:border-b-2 after:border-current after:content-['']\";\n\nconst nextId = (() => {\n let count = 0;\n\n return () => `ui-menu-${++count}`;\n})();\n\n/**\n * One action of a `ui-menu`; `destructive` paints it in `--negative`. Giving it `checked` makes it a radio\n * choice (`menuitemradio`): the current one carries a check mark and `aria-checked=\"true\"`.\n *\n * @example\n * <button uiMenuItem destructive (click)=\"remove()\">Delete the holding</button>\n * <button uiMenuItem [checked]=\"account() === 'pea'\" (click)=\"account.set('pea')\">Northwind PEA</button>\n */\n@Directive({\n selector: 'button[uiMenuItem]',\n host: {\n '[attr.role]': 'role()',\n '[attr.aria-checked]': 'checked() === undefined ? null : checked()',\n tabindex: '-1',\n '[class]': 'classes()',\n '(click)': 'onClick()',\n },\n})\nexport class UiMenuItem {\n readonly destructive = input(false, { transform: booleanAttribute });\n readonly checked = input<boolean>();\n\n readonly #host = inject<ElementRef<HTMLButtonElement>>(ElementRef).nativeElement;\n readonly #menu = inject(UiMenu);\n\n protected readonly role = computed(() => (this.checked() === undefined ? 'menuitem' : 'menuitemradio'));\n\n protected readonly classes = computed(\n () =>\n `${this.#menu.sheet() ? SHEET_ITEM_CLASSES : ITEM_CLASSES}${this.destructive() ? ' text-(--negative)' : ''}${this.checked() ? ` ${CHECK_CLASSES}` : ''}`,\n );\n\n focus(): void {\n this.#host.focus();\n }\n\n hasFocus(): boolean {\n return this.#host === document.activeElement;\n }\n\n protected onClick(): void {\n this.#menu.close();\n }\n}\n\n/**\n * Secondary actions menu, opened from its trigger through the native Popover API. With `sheet`, below\n * 64rem it opens as a bottom action sheet instead of beside its trigger, and `heading` names what\n * the actions apply to. `width` fixes the popover width in pixels; without it the menu fits its content.\n *\n * @example\n * <button ui-button size=\"icon\" aria-label=\"More\" [uiMenuTrigger]=\"menu\">...</button>\n * <ui-menu #menu label=\"Holding actions\">\n * <button uiMenuItem (click)=\"edit()\">Edit</button>\n * <button uiMenuItem destructive (click)=\"remove()\">Delete the holding</button>\n * </ui-menu>\n */\n@Component({\n selector: 'ui-menu',\n template: `\n @if (sheet() && heading()) {\n <div aria-hidden=\"true\" class=\"text-label px-3 pt-2 pb-1.5 leading-[17px] text-(--muted-foreground) lg:hidden\">\n {{ heading() }}\n </div>\n }\n <ng-content />\n `,\n host: {\n role: 'menu',\n popover: 'auto',\n '[id]': 'menuId',\n '[style.--ui-menu-width]': 'widthVar()',\n '[class]': 'classes()',\n '[attr.aria-label]': 'label()',\n '(toggle)': 'onToggle($event)',\n '(keydown)': 'onKeydown($event)',\n },\n})\nexport class UiMenu {\n readonly label = input.required<string>();\n readonly sheet = input(false, { transform: booleanAttribute });\n readonly heading = input<string>();\n readonly width = input<number | undefined, unknown>(undefined, { transform: numberAttribute });\n\n readonly menuId = nextId();\n readonly isOpen = signal(false);\n\n protected readonly hasWidth = computed(() => Number.isFinite(this.width()));\n protected readonly widthVar = computed(() => (this.hasWidth() ? `${this.width()}px` : null));\n protected readonly classes = computed(() => {\n const width = !this.hasWidth() ? '' : this.sheet() ? ' lg:w-(--ui-menu-width)' : ' w-(--ui-menu-width)';\n return `${MENU_CLASSES}${this.sheet() ? ` ${SHEET_CLASSES}` : ''}${width}`;\n });\n protected readonly items = contentChildren(UiMenuItem);\n\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n #trigger: HTMLElement | null = null;\n\n open(trigger: HTMLElement): void {\n this.#trigger = trigger;\n this.isOpen.set(true);\n if (typeof this.#host.showPopover === 'function') {\n this.#host.showPopover();\n }\n if (this.#isSheet()) {\n this.#host.style.position = '';\n this.#host.style.top = '';\n this.#host.style.left = '';\n this.#host.style.right = '';\n this.#host.style.bottom = '';\n } else {\n this.#position(trigger);\n }\n (this.items().find((item) => item.checked()) ?? this.items()[0])?.focus();\n }\n\n close(): void {\n const wasOpen = this.isOpen();\n\n this.#applyClosed();\n\n if (wasOpen && typeof this.#host.hidePopover === 'function') {\n this.#host.hidePopover();\n }\n }\n\n #applyClosed(): void {\n this.isOpen.set(false);\n this.#trigger?.focus();\n this.#trigger = null;\n }\n\n protected onToggle(event: Event): void {\n if ((event as ToggleEvent).newState === 'closed') {\n this.#applyClosed();\n }\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n if (event.key === 'Tab' || event.key === 'Escape') {\n event.preventDefault();\n this.close();\n return;\n }\n\n const items = this.items();\n const currentIndex = items.findIndex((item) => item.hasFocus());\n const lastIndex = items.length - 1;\n\n let nextIndex: number | undefined;\n switch (event.key) {\n case 'ArrowDown':\n nextIndex = currentIndex >= lastIndex ? 0 : currentIndex + 1;\n break;\n case 'ArrowUp':\n nextIndex = currentIndex <= 0 ? lastIndex : currentIndex - 1;\n break;\n case 'Home':\n nextIndex = 0;\n break;\n case 'End':\n nextIndex = lastIndex;\n break;\n }\n\n if (nextIndex === undefined) {\n return;\n }\n event.preventDefault();\n items[nextIndex]?.focus();\n }\n\n #isSheet(): boolean {\n return this.sheet() && typeof window.matchMedia === 'function' && !window.matchMedia('(min-width: 64rem)').matches;\n }\n\n #layoutWidth(): number {\n const style = getComputedStyle(this.#host);\n const width = parseFloat(style.width);\n\n if (Number.isNaN(width)) {\n return this.#host.offsetWidth;\n }\n\n if (style.boxSizing === 'border-box') {\n return width;\n }\n\n return (\n width +\n parseFloat(style.paddingLeft) +\n parseFloat(style.paddingRight) +\n parseFloat(style.borderLeftWidth) +\n parseFloat(style.borderRightWidth)\n );\n }\n\n #position(trigger: HTMLElement): void {\n const margin = 8;\n const triggerRect = trigger.getBoundingClientRect();\n const menuRect = { width: this.#layoutWidth(), height: this.#host.offsetHeight };\n const fitsBelow = window.innerHeight - triggerRect.bottom >= menuRect.height + margin;\n\n const top = fitsBelow ? triggerRect.bottom + 4 : Math.max(margin, triggerRect.top - menuRect.height - 4);\n const left = Math.min(\n Math.max(triggerRect.right - menuRect.width, margin),\n window.innerWidth - menuRect.width - margin,\n );\n\n this.#host.style.position = 'fixed';\n this.#host.style.top = `${Math.min(top, window.innerHeight - menuRect.height - margin)}px`;\n this.#host.style.left = `${left}px`;\n this.#host.style.right = 'auto';\n this.#host.style.bottom = 'auto';\n this.#host.style.transformOrigin = fitsBelow ? 'top right' : 'bottom right';\n }\n}\n\n/**\n * Opens a `ui-menu` from a trigger element and wires its ARIA attributes.\n *\n * @example\n * <button ui-button size=\"icon\" aria-label=\"More\" [uiMenuTrigger]=\"menu\">...</button>\n */\n@Directive({\n selector: '[uiMenuTrigger]',\n host: {\n 'aria-haspopup': 'menu',\n '[attr.aria-expanded]': 'uiMenuTrigger().isOpen()',\n '[attr.aria-controls]': 'uiMenuTrigger().menuId',\n '(click)': 'toggle()',\n },\n})\nexport class UiMenuTrigger {\n readonly uiMenuTrigger = input.required<UiMenu>();\n\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n\n protected toggle(): void {\n const menu = this.uiMenuTrigger();\n\n if (menu.isOpen()) {\n menu.close();\n } else {\n menu.open(this.#host);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"joanroucoux-cairn-ui-menu.mjs","names":[],"sources":["../../../projects/ui/menu/menu.ts"],"sourcesContent":["import {\n Component,\n Directive,\n ElementRef,\n booleanAttribute,\n computed,\n contentChildren,\n inject,\n input,\n numberAttribute,\n signal,\n} from '@angular/core';\n\nconst MENU_CLASSES =\n 'm-0 min-w-44 p-1 rounded-container bg-(--elevated) shadow-[0_8px_24px_rgb(0_0_0/0.16),inset_0_0_0_1px_var(--border)] text-(--foreground) transition-[opacity,scale,overlay,display] transition-discrete duration-(--duration-fast) not-open:duration-[120ms] ease-out starting:open:opacity-0 starting:open:scale-(--enter-scale) opacity-0 scale-(--enter-scale) open:opacity-100 open:scale-100';\n\nconst SHEET_CLASSES =\n 'max-lg:fixed max-lg:inset-x-4 max-lg:top-auto max-lg:w-auto max-lg:bottom-[calc(76px+env(safe-area-inset-bottom))] max-lg:min-w-0 max-lg:p-1.5 max-lg:bg-(--card) max-lg:shadow-[0_12px_32px_rgb(0_0_0/0.2),inset_0_0_0_1px_var(--border)] max-lg:backdrop:bg-[rgb(0_0_0/0.36)] max-lg:transition-[opacity,translate,overlay,display] max-lg:scale-100 max-lg:translate-y-4 max-lg:open:translate-y-0 max-lg:starting:open:scale-100 max-lg:starting:open:translate-y-4 max-lg:motion-reduce:translate-y-0 max-lg:motion-reduce:starting:open:translate-y-0';\n\nconst SHEET_ITEM_CLASSES =\n 'flex w-full items-center gap-3 lg:pointer-fine:gap-2.5 min-h-12 lg:min-h-11 lg:pointer-fine:min-h-9 px-3 lg:pointer-fine:px-2.5 rounded-[calc(var(--radius-container)-6px)] lg:rounded-[calc(var(--radius-container)-4px)] text-body lg:pointer-fine:text-label text-left whitespace-nowrap hover:bg-(--glow) focus-visible:bg-(--glow) active:bg-(--soft) transition-[background-color] duration-(--duration-fast) ease-out outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring) [:where(&>svg)]:size-5 [:where(&>svg)]:flex-none lg:[:where(&>svg)]:size-[18px] lg:pointer-fine:[:where(&>svg)]:size-4';\n\nconst ITEM_CLASSES =\n 'flex w-full items-center gap-3 pointer-fine:gap-2.5 min-h-11 pointer-fine:min-h-9 px-3 pointer-fine:px-2.5 rounded-[calc(var(--radius-container)-4px)] text-body pointer-fine:text-label text-left whitespace-nowrap hover:bg-(--glow) focus-visible:bg-(--glow) active:bg-(--soft) transition-[background-color] duration-(--duration-fast) ease-out outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring) [:where(&>svg)]:size-[18px] [:where(&>svg)]:flex-none pointer-fine:[:where(&>svg)]:size-4';\n\nconst CHECK_CLASSES =\n \"after:ms-auto after:block after:h-3 after:w-1.5 after:flex-none after:rotate-45 after:border-r-2 after:border-b-2 after:border-current after:content-['']\";\n\nconst nextId = (() => {\n let count = 0;\n\n return () => `ui-menu-${++count}`;\n})();\n\n/**\n * One action of a `ui-menu`; `destructive` paints it in `--negative`. Giving it `checked` makes it a radio\n * choice (`menuitemradio`): the current one carries a check mark and `aria-checked=\"true\"`.\n *\n * @example\n * <button uiMenuItem destructive (click)=\"remove()\">Delete the holding</button>\n * <button uiMenuItem [checked]=\"account() === 'pea'\" (click)=\"account.set('pea')\">Northwind PEA</button>\n */\n@Directive({\n selector: 'button[uiMenuItem]',\n host: {\n '[attr.role]': 'role()',\n '[attr.aria-checked]': 'checked() === undefined ? null : checked()',\n tabindex: '-1',\n '[class]': 'classes()',\n '(click)': 'onClick()',\n },\n})\nexport class UiMenuItem {\n readonly destructive = input(false, { transform: booleanAttribute });\n readonly checked = input<boolean>();\n\n readonly #host = inject<ElementRef<HTMLButtonElement>>(ElementRef).nativeElement;\n readonly #menu = inject(UiMenu);\n\n protected readonly role = computed(() => (this.checked() === undefined ? 'menuitem' : 'menuitemradio'));\n\n protected readonly classes = computed(\n () =>\n `${this.#menu.sheet() ? SHEET_ITEM_CLASSES : ITEM_CLASSES}${this.destructive() ? ' text-(--negative)' : ''}${this.checked() ? ` ${CHECK_CLASSES}` : ''}`,\n );\n\n focus(): void {\n this.#host.focus();\n }\n\n hasFocus(): boolean {\n return this.#host === document.activeElement;\n }\n\n protected onClick(): void {\n this.#menu.close();\n }\n}\n\n/**\n * Secondary actions menu, opened from its trigger through the native Popover API. With `sheet`, below\n * 64rem it opens as a bottom action sheet instead of beside its trigger, and `heading` names what\n * the actions apply to. `width` fixes the popover width in pixels; without it the menu fits its content.\n *\n * @example\n * <button ui-button size=\"icon\" aria-label=\"More\" [uiMenuTrigger]=\"menu\">...</button>\n * <ui-menu #menu label=\"Holding actions\">\n * <button uiMenuItem (click)=\"edit()\">Edit</button>\n * <button uiMenuItem destructive (click)=\"remove()\">Delete the holding</button>\n * </ui-menu>\n */\n@Component({\n selector: 'ui-menu',\n template: `\n @if (sheet() && heading()) {\n <div aria-hidden=\"true\" class=\"text-label px-3 pt-2 pb-1.5 leading-[17px] text-(--muted-foreground) lg:hidden\">\n {{ heading() }}\n </div>\n }\n <ng-content />\n `,\n host: {\n role: 'menu',\n popover: 'auto',\n '[id]': 'menuId',\n '[style.--ui-menu-width]': 'widthVar()',\n '[class]': 'classes()',\n '[attr.aria-label]': 'label()',\n '(toggle)': 'onToggle($event)',\n '(keydown)': 'onKeydown($event)',\n },\n})\nexport class UiMenu {\n readonly label = input.required<string>();\n readonly sheet = input(false, { transform: booleanAttribute });\n readonly heading = input<string>();\n readonly width = input<number | undefined, unknown>(undefined, { transform: numberAttribute });\n\n readonly menuId = nextId();\n readonly isOpen = signal(false);\n\n protected readonly hasWidth = computed(() => Number.isFinite(this.width()));\n protected readonly widthVar = computed(() => (this.hasWidth() ? `${this.width()}px` : null));\n protected readonly classes = computed(() => {\n const width = !this.hasWidth() ? '' : this.sheet() ? ' lg:w-(--ui-menu-width)' : ' w-(--ui-menu-width)';\n return `${MENU_CLASSES}${this.sheet() ? ` ${SHEET_CLASSES}` : ''}${width}`;\n });\n protected readonly items = contentChildren(UiMenuItem);\n\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n #trigger: HTMLElement | null = null;\n\n open(trigger: HTMLElement): void {\n this.#trigger = trigger;\n this.isOpen.set(true);\n if (typeof this.#host.showPopover === 'function') {\n this.#host.showPopover();\n }\n if (this.#isSheet()) {\n this.#host.style.position = '';\n this.#host.style.top = '';\n this.#host.style.left = '';\n this.#host.style.right = '';\n this.#host.style.bottom = '';\n } else {\n this.#position(trigger);\n }\n (this.items().find((item) => item.checked()) ?? this.items()[0])?.focus();\n }\n\n close(): void {\n const wasOpen = this.isOpen();\n\n this.#applyClosed();\n\n if (wasOpen && typeof this.#host.hidePopover === 'function') {\n this.#host.hidePopover();\n }\n }\n\n #applyClosed(): void {\n this.isOpen.set(false);\n this.#trigger?.focus();\n this.#trigger = null;\n }\n\n protected onToggle(event: Event): void {\n if ((event as ToggleEvent).newState === 'closed') {\n this.#applyClosed();\n }\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n if (event.key === 'Tab' || event.key === 'Escape') {\n event.preventDefault();\n this.close();\n return;\n }\n\n const items = this.items();\n const currentIndex = items.findIndex((item) => item.hasFocus());\n const lastIndex = items.length - 1;\n\n let nextIndex: number | undefined;\n switch (event.key) {\n case 'ArrowDown':\n nextIndex = currentIndex >= lastIndex ? 0 : currentIndex + 1;\n break;\n case 'ArrowUp':\n nextIndex = currentIndex <= 0 ? lastIndex : currentIndex - 1;\n break;\n case 'Home':\n nextIndex = 0;\n break;\n case 'End':\n nextIndex = lastIndex;\n break;\n }\n\n if (nextIndex === undefined) {\n return;\n }\n event.preventDefault();\n items[nextIndex]?.focus();\n }\n\n #isSheet(): boolean {\n return this.sheet() && typeof window.matchMedia === 'function' && !window.matchMedia('(min-width: 64rem)').matches;\n }\n\n #layoutWidth(): number {\n const style = getComputedStyle(this.#host);\n const width = parseFloat(style.width);\n\n if (Number.isNaN(width)) {\n return this.#host.offsetWidth;\n }\n\n if (style.boxSizing === 'border-box') {\n return width;\n }\n\n return (\n width +\n parseFloat(style.paddingLeft) +\n parseFloat(style.paddingRight) +\n parseFloat(style.borderLeftWidth) +\n parseFloat(style.borderRightWidth)\n );\n }\n\n #position(trigger: HTMLElement): void {\n const margin = 8;\n const { clientWidth: viewportWidth, clientHeight: viewportHeight } = document.documentElement;\n const triggerRect = trigger.getBoundingClientRect();\n const menuRect = { width: this.#layoutWidth(), height: this.#host.offsetHeight };\n const fitsBelow = viewportHeight - triggerRect.bottom >= menuRect.height + margin;\n\n const top = fitsBelow ? triggerRect.bottom + 4 : Math.max(margin, triggerRect.top - menuRect.height - 4);\n const left = Math.min(\n Math.max(triggerRect.right - menuRect.width, margin),\n viewportWidth - menuRect.width - margin,\n );\n\n this.#host.style.position = 'fixed';\n this.#host.style.top = `${Math.min(top, viewportHeight - menuRect.height - margin)}px`;\n this.#host.style.left = `${left}px`;\n this.#host.style.right = 'auto';\n this.#host.style.bottom = 'auto';\n this.#host.style.transformOrigin = fitsBelow ? 'top right' : 'bottom right';\n }\n}\n\n/**\n * Opens a `ui-menu` from a trigger element and wires its ARIA attributes.\n *\n * @example\n * <button ui-button size=\"icon\" aria-label=\"More\" [uiMenuTrigger]=\"menu\">...</button>\n */\n@Directive({\n selector: '[uiMenuTrigger]',\n host: {\n 'aria-haspopup': 'menu',\n '[attr.aria-expanded]': 'uiMenuTrigger().isOpen()',\n '[attr.aria-controls]': 'uiMenuTrigger().menuId',\n '(click)': 'toggle()',\n },\n})\nexport class UiMenuTrigger {\n readonly uiMenuTrigger = input.required<UiMenu>();\n\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n\n protected toggle(): void {\n const menu = this.uiMenuTrigger();\n\n if (menu.isOpen()) {\n menu.close();\n } else {\n menu.open(this.#host);\n }\n }\n}\n"],"mappings":"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"}