@joanroucoux/cairn-ui 0.7.2 → 0.8.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.
- package/CHANGELOG.md +72 -0
- package/README.md +67 -0
- package/fesm2022/joanroucoux-cairn-ui-action-bar.mjs +54 -0
- package/fesm2022/joanroucoux-cairn-ui-action-bar.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-alert.mjs +129 -0
- package/fesm2022/joanroucoux-cairn-ui-alert.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-amount.mjs +114 -0
- package/fesm2022/joanroucoux-cairn-ui-amount.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-async.mjs +282 -0
- package/fesm2022/joanroucoux-cairn-ui-async.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-avatar.mjs +71 -0
- package/fesm2022/joanroucoux-cairn-ui-avatar.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-back-link.mjs +79 -0
- package/fesm2022/joanroucoux-cairn-ui-back-link.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-badge.mjs +48 -0
- package/fesm2022/joanroucoux-cairn-ui-badge.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-button.mjs +128 -0
- package/fesm2022/joanroucoux-cairn-ui-button.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-card.mjs +146 -0
- package/fesm2022/joanroucoux-cairn-ui-card.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-choice-chips.mjs +172 -0
- package/fesm2022/joanroucoux-cairn-ui-choice-chips.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-control.mjs +18 -0
- package/fesm2022/joanroucoux-cairn-ui-control.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-delta.mjs +103 -0
- package/fesm2022/joanroucoux-cairn-ui-delta.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-dialog.mjs +626 -0
- package/fesm2022/joanroucoux-cairn-ui-dialog.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-donut.mjs +347 -0
- package/fesm2022/joanroucoux-cairn-ui-donut.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-empty.mjs +47 -0
- package/fesm2022/joanroucoux-cairn-ui-empty.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-external-link.mjs +65 -0
- package/fesm2022/joanroucoux-cairn-ui-external-link.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-fact.mjs +99 -0
- package/fesm2022/joanroucoux-cairn-ui-fact.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-field.mjs +232 -0
- package/fesm2022/joanroucoux-cairn-ui-field.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-filter-chips.mjs +89 -0
- package/fesm2022/joanroucoux-cairn-ui-filter-chips.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-input.mjs +90 -0
- package/fesm2022/joanroucoux-cairn-ui-input.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-line-chart.mjs +844 -0
- package/fesm2022/joanroucoux-cairn-ui-line-chart.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-menu.mjs +241 -0
- package/fesm2022/joanroucoux-cairn-ui-menu.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-meter.mjs +86 -0
- package/fesm2022/joanroucoux-cairn-ui-meter.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-motion.mjs +415 -0
- package/fesm2022/joanroucoux-cairn-ui-motion.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-nav-item.mjs +45 -0
- package/fesm2022/joanroucoux-cairn-ui-nav-item.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-row.mjs +164 -0
- package/fesm2022/joanroucoux-cairn-ui-row.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-segmented.mjs +165 -0
- package/fesm2022/joanroucoux-cairn-ui-segmented.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-select.mjs +69 -0
- package/fesm2022/joanroucoux-cairn-ui-select.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-skeleton.mjs +91 -0
- package/fesm2022/joanroucoux-cairn-ui-skeleton.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-stale-link.mjs +43 -0
- package/fesm2022/joanroucoux-cairn-ui-stale-link.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-stat.mjs +96 -0
- package/fesm2022/joanroucoux-cairn-ui-stat.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-switch.mjs +37 -0
- package/fesm2022/joanroucoux-cairn-ui-switch.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-tab-bar.mjs +75 -0
- package/fesm2022/joanroucoux-cairn-ui-tab-bar.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-table.mjs +321 -0
- package/fesm2022/joanroucoux-cairn-ui-table.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-toast.mjs +153 -0
- package/fesm2022/joanroucoux-cairn-ui-toast.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui.mjs +0 -5462
- package/fesm2022/joanroucoux-cairn-ui.mjs.map +1 -1
- package/package.json +141 -1
- package/types/joanroucoux-cairn-ui-action-bar.d.ts +22 -0
- package/types/joanroucoux-cairn-ui-alert.d.ts +23 -0
- package/types/joanroucoux-cairn-ui-amount.d.ts +45 -0
- package/types/joanroucoux-cairn-ui-async.d.ts +58 -0
- package/types/joanroucoux-cairn-ui-avatar.d.ts +35 -0
- package/types/joanroucoux-cairn-ui-back-link.d.ts +22 -0
- package/types/joanroucoux-cairn-ui-badge.d.ts +23 -0
- package/types/joanroucoux-cairn-ui-button.d.ts +29 -0
- package/types/joanroucoux-cairn-ui-card.d.ts +36 -0
- package/types/joanroucoux-cairn-ui-choice-chips.d.ts +46 -0
- package/types/joanroucoux-cairn-ui-control.d.ts +27 -0
- package/types/joanroucoux-cairn-ui-delta.d.ts +38 -0
- package/types/joanroucoux-cairn-ui-dialog.d.ts +72 -0
- package/types/joanroucoux-cairn-ui-donut.d.ts +59 -0
- package/types/joanroucoux-cairn-ui-empty.d.ts +19 -0
- package/types/joanroucoux-cairn-ui-external-link.d.ts +15 -0
- package/types/joanroucoux-cairn-ui-fact.d.ts +44 -0
- package/types/joanroucoux-cairn-ui-field.d.ts +84 -0
- package/types/joanroucoux-cairn-ui-filter-chips.d.ts +28 -0
- package/types/joanroucoux-cairn-ui-input.d.ts +51 -0
- package/types/joanroucoux-cairn-ui-line-chart.d.ts +114 -0
- package/types/joanroucoux-cairn-ui-menu.d.ts +66 -0
- package/types/joanroucoux-cairn-ui-meter.d.ts +30 -0
- package/types/joanroucoux-cairn-ui-motion.d.ts +124 -0
- package/types/joanroucoux-cairn-ui-nav-item.d.ts +21 -0
- package/types/joanroucoux-cairn-ui-row.d.ts +78 -0
- package/types/joanroucoux-cairn-ui-segmented.d.ts +36 -0
- package/types/joanroucoux-cairn-ui-select.d.ts +23 -0
- package/types/joanroucoux-cairn-ui-skeleton.d.ts +26 -0
- package/types/joanroucoux-cairn-ui-stale-link.d.ts +24 -0
- package/types/joanroucoux-cairn-ui-stat.d.ts +32 -0
- package/types/joanroucoux-cairn-ui-switch.d.ts +17 -0
- package/types/joanroucoux-cairn-ui-tab-bar.d.ts +31 -0
- package/types/joanroucoux-cairn-ui-table.d.ts +139 -0
- package/types/joanroucoux-cairn-ui-toast.d.ts +51 -0
- package/types/joanroucoux-cairn-ui.d.ts +1 -1440
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"joanroucoux-cairn-ui-dialog.mjs","sources":["../../../projects/ui/dialog/internal/dialog-exit.ts","../../../projects/ui/dialog/internal/dialog-styles.ts","../../../projects/ui/dialog/internal/sheet-drag.ts","../../../projects/ui/dialog/dialog.ts","../../../projects/ui/dialog/joanroucoux-cairn-ui-dialog.ts"],"sourcesContent":["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","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 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 afterRenderEffect,\n booleanAttribute,\n computed,\n inject,\n input,\n output,\n signal,\n} from '@angular/core';\n\nimport { afterExit } from './internal/dialog-exit';\nimport { DIALOG_STYLES } from './internal/dialog-styles';\nimport { SheetDrag } from './internal/sheet-drag';\n\n/** Named dialog widths; any other CSS length is accepted as is. `DialogWidth` is derived from this tuple. */\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/** Spacing presets. `DialogLayout` is derived from this tuple. */\nexport const DIALOG_LAYOUTS = ['trade', 'form', 'list', 'confirm'] as const;\nexport type DialogLayout = (typeof DIALOG_LAYOUTS)[number];\n\ntype LayoutClasses = {\n dialog: string;\n header: string;\n title: string;\n cross: string;\n icon: string;\n handle: string;\n body: string;\n footer: string;\n};\n\nconst LAYOUTS: Record<DialogLayout, LayoutClasses> = {\n trade: {\n dialog: '',\n header: 'items-start lg:pt-6',\n title: 'pt-1 lg:pt-0',\n cross: 'lg:-mt-1.5',\n icon: 'lg:size-5',\n handle: 'pt-[7.5px]',\n body: 'pt-3 pb-4 lg:pt-5 lg:pb-5',\n footer: 'max-lg:pb-[calc(8px+env(safe-area-inset-bottom))] lg:pb-6',\n },\n form: {\n dialog: '',\n header: 'items-center lg:pt-6',\n title: '',\n cross: '',\n icon: 'lg:size-[22px]',\n handle: 'pt-[7.5px]',\n body: 'pt-2 pb-5 lg:pt-4 lg:pb-5',\n footer: 'max-lg:pb-[calc(12px+env(safe-area-inset-bottom))] lg:pb-5',\n },\n list: {\n dialog: 'max-lg:h-[calc(100dvh-58px)] lg:mt-24 lg:mb-auto lg:max-h-[min(760px,calc(100dvh-96px-32px))]',\n header: 'items-center lg:pt-5',\n title: '',\n cross: '',\n icon: 'lg:size-[22px]',\n handle: 'pt-[7.5px]',\n body: 'pt-2 pb-4 lg:pt-4 lg:pb-5',\n footer: 'pt-3 shadow-[0_-1px_0_var(--hairline)] max-lg:pb-[calc(8px+env(safe-area-inset-bottom))] lg:pt-4 lg:pb-4',\n },\n confirm: {\n dialog: '',\n header: 'items-center pt-3 lg:pt-6',\n title: '',\n cross: '',\n icon: 'lg:size-[22px]',\n handle: 'pt-[11.5px]',\n body: 'pt-3 pb-5 lg:pt-3 lg:pb-6',\n footer: 'max-lg:pb-[calc(8px+env(safe-area-inset-bottom))] lg:pb-6',\n },\n};\n\n/** What started a close, as `closed` reports it. `DialogCloseReason` is derived from this tuple. */\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 click on the backdrop and a drag of the sheet do\n * nothing 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)': 'onPointerDown($event)',\n '(click)': 'onClick($event)',\n '(keydown)': '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]=\"layout() === 'confirm' ? 'alertdialog' : 'dialog'\"\n [class]=\"classes()\"\n [style.--dialog-width]=\"widthValue()\"\n (cancel)=\"onCancel($event)\"\n (close)=\"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]=\"titleClasses()\">\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)=\"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 [class]=\"iconClasses()\"\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 <!-- empty:hidden keeps a body-less dialog from carrying the body's vertical padding. -->\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 layout = input<DialogLayout>('trade');\n readonly truncateDescription = input(false, { transform: booleanAttribute });\n readonly width = input<DialogWidth | (string & {})>('lg');\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\n protected readonly describedBy = computed(() => {\n if (this.description()) {\n return this.descriptionId;\n }\n\n return this.layout() === 'confirm' ? this.bodyId : null;\n });\n\n protected readonly widthValue = computed(() => {\n const width = this.width();\n\n return Object.hasOwn(WIDTH_VALUES, width) ? WIDTH_VALUES[width as DialogWidth] : width;\n });\n\n protected readonly spec = computed(() => LAYOUTS[this.layout()]);\n\n protected readonly headerClasses = computed(\n () =>\n `flex gap-2 pl-4 max-lg:touch-none lg:gap-3 lg:pl-6 ${this.spec().header} ${this.closeLabel() ? 'pr-2 lg:pr-4' : 'pr-4 lg:pr-6'}`,\n );\n\n protected readonly titleClasses = computed(() => this.spec().title);\n\n protected readonly crossClasses = computed(\n () =>\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) ${this.spec().cross}`,\n );\n\n protected readonly iconClasses = computed(() => this.spec().icon);\n\n protected readonly footerClasses = computed(\n () =>\n `max-lg:[&>[ui-button]]:text-body flex justify-end gap-2 px-4 empty:hidden max-lg:flex-col-reverse max-lg:[&>[ui-button]]:h-[50px] max-lg:[&>[ui-button]]:w-full lg:px-6 ${this.spec().footer}`,\n );\n\n protected readonly classes = computed(\n () =>\n `open:flex w-full flex-col overflow-hidden m-auto mt-auto max-h-[calc(100dvh-2rem)] 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) 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)] backdrop:bg-black/[0.36] ${this.spec().dialog}`,\n );\n\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n constructor() {\n const destroyRef = inject(DestroyRef);\n\n afterNextRender(() => {\n const host = this.#host.nativeElement;\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 this.#dialog,\n grips,\n () => this.close('drag'),\n () => !this.busy(),\n );\n destroyRef.onDestroy(() => {\n this.#drag?.destroy();\n this.#cancelExit();\n });\n\n if (typeof ResizeObserver === 'undefined') {\n return;\n }\n\n const body = this.#host.nativeElement.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 afterRenderEffect(() => {\n // viewChild.required can't target a #private field (NG1053); safe because the template has exactly one <dialog>.\n const dialog = this.#host.nativeElement.querySelector('dialog') as HTMLDialogElement;\n\n if (this.open() && !dialog.open) {\n this.#cancelExit();\n this.#drag?.reset();\n dialog.showModal();\n } else if (!this.open() && dialog.open) {\n this.close('programmatic');\n }\n });\n }\n\n #pressedOnBackdrop = false;\n #drag: SheetDrag | null = null;\n #reason: DialogCloseReason | null = null;\n #cancelExit: () => void = () => undefined;\n\n protected close(reason: DialogCloseReason): void {\n if (this.#dialog.open) {\n this.#reason = reason;\n this.#dialog.close();\n }\n }\n\n protected onCancel(event: Event): void {\n if (event.target !== event.currentTarget) {\n return;\n }\n event.preventDefault();\n if (!this.busy()) {\n this.close('escape');\n }\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n if (event.key === 'Escape' && this.busy()) {\n event.preventDefault();\n }\n }\n\n protected onPointerDown(event: MouseEvent): void {\n this.#pressedOnBackdrop = event.target === this.#dialog && this.#outside(event);\n }\n\n protected onClick(event: MouseEvent): void {\n if (this.#pressedOnBackdrop && !this.busy() && event.target === this.#dialog && this.#outside(event)) {\n this.close('backdrop');\n }\n this.#pressedOnBackdrop = false;\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 protected 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.busy()) {\n this.#dialog.showModal();\n return;\n }\n const reason = asked ?? 'escape';\n if (reason !== 'programmatic') {\n this.dismissed.emit();\n }\n this.#awaitExit(reason);\n }\n\n #awaitExit(reason: DialogCloseReason): void {\n this.#cancelExit();\n this.#cancelExit = afterExit(this.#dialog, () => {\n this.#cancelExit = () => undefined;\n this.#drag?.reset();\n this.closed.emit(reason);\n });\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { NgTemplateOutlet } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { input, output, signal, computed, Component } from '@angular/core';
|
|
4
|
+
|
|
5
|
+
const RADIUS = 78;
|
|
6
|
+
const RING_WIDTH = 30;
|
|
7
|
+
const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
|
|
8
|
+
const GAP = 2;
|
|
9
|
+
function positionSlices(slices) {
|
|
10
|
+
let travelled = 0;
|
|
11
|
+
return slices.map((slice) => {
|
|
12
|
+
const length = slice.share * CIRCUMFERENCE;
|
|
13
|
+
const drawn = Math.max(0, length - GAP);
|
|
14
|
+
const positioned = {
|
|
15
|
+
...slice,
|
|
16
|
+
dash: `${drawn.toFixed(2)} ${(CIRCUMFERENCE - drawn).toFixed(2)}`,
|
|
17
|
+
offset: (-travelled).toFixed(2),
|
|
18
|
+
};
|
|
19
|
+
travelled += length;
|
|
20
|
+
return positioned;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const MAX_RAMP = 6;
|
|
25
|
+
const OTHERS_ID = 'others';
|
|
26
|
+
function shareSlices(slices) {
|
|
27
|
+
const positive = slices.filter((slice) => slice.value > 0).sort((a, b) => b.value - a.value);
|
|
28
|
+
const total = positive.reduce((sum, slice) => sum + slice.value, 0);
|
|
29
|
+
return positive.map((slice) => ({ ...slice, share: slice.value / total }));
|
|
30
|
+
}
|
|
31
|
+
function rankSlices(slices, othersLabel) {
|
|
32
|
+
const shared = shareSlices(slices);
|
|
33
|
+
const toRanked = (slice, ramp, members = []) => ({
|
|
34
|
+
...slice,
|
|
35
|
+
ramp,
|
|
36
|
+
members,
|
|
37
|
+
});
|
|
38
|
+
if (shared.length <= MAX_RAMP) {
|
|
39
|
+
return shared.map((slice, index) => toRanked(slice, (index + 1)));
|
|
40
|
+
}
|
|
41
|
+
const kept = shared.slice(0, MAX_RAMP - 1);
|
|
42
|
+
const rest = shared.slice(MAX_RAMP - 1);
|
|
43
|
+
const othersValue = rest.reduce((sum, slice) => sum + slice.value, 0);
|
|
44
|
+
const othersShare = rest.reduce((sum, slice) => sum + slice.share, 0);
|
|
45
|
+
return [
|
|
46
|
+
...kept.map((slice, index) => toRanked(slice, (index + 1))),
|
|
47
|
+
toRanked({ id: OTHERS_ID, label: othersLabel, value: othersValue, share: othersShare }, MAX_RAMP, rest.map((slice) => slice.label)),
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const identityValue = (value) => `${value}`;
|
|
52
|
+
const identityShare = (share) => `${Math.round(share * 100)}%`;
|
|
53
|
+
const ROW_CLASSES = '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)';
|
|
54
|
+
const SLICE_CLASSES = 'cursor-pointer origin-center [transform-box:view-box] transition-[scale,opacity] duration-(--duration-fast) ease-out';
|
|
55
|
+
const RAMP_DOT_CLASSES = {
|
|
56
|
+
1: 'bg-(--ramp-1)',
|
|
57
|
+
2: 'bg-(--ramp-2)',
|
|
58
|
+
3: 'bg-(--ramp-3)',
|
|
59
|
+
4: 'bg-(--ramp-4)',
|
|
60
|
+
5: 'bg-(--ramp-5)',
|
|
61
|
+
6: 'bg-(--ramp-6)',
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* A ring chart with a legend: slices rank from largest to smallest on a grey ramp, and hovering,
|
|
65
|
+
* focusing or activating a legend row highlights its slice and centres it in the ring.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* <ui-donut [slices]="byAssetClass()" label="Par classe d'actif" othersLabel="Autres" [valueFormat]="formatEur" [shareFormat]="formatShare" (sliceSelect)="goToHoldings($event)" />
|
|
69
|
+
*/
|
|
70
|
+
class UiDonut {
|
|
71
|
+
slices = input.required(/* @ts-ignore */
|
|
72
|
+
...(ngDevMode ? [{ debugName: "slices" }] : /* istanbul ignore next */ []));
|
|
73
|
+
label = input.required(/* @ts-ignore */
|
|
74
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
75
|
+
othersLabel = input.required(/* @ts-ignore */
|
|
76
|
+
...(ngDevMode ? [{ debugName: "othersLabel" }] : /* istanbul ignore next */ []));
|
|
77
|
+
valueFormat = input(identityValue, /* @ts-ignore */
|
|
78
|
+
...(ngDevMode ? [{ debugName: "valueFormat" }] : /* istanbul ignore next */ []));
|
|
79
|
+
shareFormat = input(identityShare, /* @ts-ignore */
|
|
80
|
+
...(ngDevMode ? [{ debugName: "shareFormat" }] : /* istanbul ignore next */ []));
|
|
81
|
+
legendHref = input(/* @ts-ignore */
|
|
82
|
+
...(ngDevMode ? [undefined, { debugName: "legendHref" }] : /* istanbul ignore next */ []));
|
|
83
|
+
sliceSelect = output();
|
|
84
|
+
activeId = signal(null, /* @ts-ignore */
|
|
85
|
+
...(ngDevMode ? [{ debugName: "activeId" }] : /* istanbul ignore next */ []));
|
|
86
|
+
ranked = computed(() => rankSlices(this.slices(), this.othersLabel()), /* @ts-ignore */
|
|
87
|
+
...(ngDevMode ? [{ debugName: "ranked" }] : /* istanbul ignore next */ []));
|
|
88
|
+
positioned = computed(() => positionSlices(this.ranked()), /* @ts-ignore */
|
|
89
|
+
...(ngDevMode ? [{ debugName: "positioned" }] : /* istanbul ignore next */ []));
|
|
90
|
+
active = computed(() => {
|
|
91
|
+
const ranked = this.ranked();
|
|
92
|
+
const id = this.activeId();
|
|
93
|
+
return ranked.find((slice) => slice.id === id) ?? ranked[0] ?? null;
|
|
94
|
+
}, /* @ts-ignore */
|
|
95
|
+
...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
|
|
96
|
+
ariaLabel = computed(() => {
|
|
97
|
+
const detail = shareSlices(this.slices())
|
|
98
|
+
.map((slice) => `${slice.label} ${this.shareFormat()(slice.share)}`)
|
|
99
|
+
.join(', ');
|
|
100
|
+
return detail ? `${this.label()} : ${detail}` : this.label();
|
|
101
|
+
}, /* @ts-ignore */
|
|
102
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
103
|
+
radius = RADIUS;
|
|
104
|
+
ringWidth = RING_WIDTH;
|
|
105
|
+
strokeColor(slice) {
|
|
106
|
+
return `var(--ramp-${slice.ramp})`;
|
|
107
|
+
}
|
|
108
|
+
sliceClasses(slice) {
|
|
109
|
+
const id = this.activeId();
|
|
110
|
+
if (id === null) {
|
|
111
|
+
return SLICE_CLASSES;
|
|
112
|
+
}
|
|
113
|
+
return `${SLICE_CLASSES} ${id === slice.id ? 'scale-104 motion-reduce:scale-100' : 'opacity-50'}`;
|
|
114
|
+
}
|
|
115
|
+
dotClasses(slice) {
|
|
116
|
+
return `size-3 flex-none rounded-pill shadow-[inset_0_0_0_1px_var(--border)] ${RAMP_DOT_CLASSES[slice.ramp]}`;
|
|
117
|
+
}
|
|
118
|
+
rowClasses(slice) {
|
|
119
|
+
return `${ROW_CLASSES}${this.isActive(slice) ? ' bg-(--soft)' : ''}`;
|
|
120
|
+
}
|
|
121
|
+
subLabel(slice) {
|
|
122
|
+
return slice.members.length > 0 ? slice.members.join(', ') : (slice.sublabel ?? '');
|
|
123
|
+
}
|
|
124
|
+
isActive(slice) {
|
|
125
|
+
return this.active()?.id === slice.id;
|
|
126
|
+
}
|
|
127
|
+
highlight(id) {
|
|
128
|
+
this.activeId.set(id);
|
|
129
|
+
}
|
|
130
|
+
clearHighlight(id) {
|
|
131
|
+
if (this.activeId() === id) {
|
|
132
|
+
this.activeId.set(null);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
hrefFor(id) {
|
|
136
|
+
return this.legendHref()?.(id) ?? null;
|
|
137
|
+
}
|
|
138
|
+
follow(event, slice) {
|
|
139
|
+
if (event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
event.preventDefault();
|
|
143
|
+
this.select(slice);
|
|
144
|
+
}
|
|
145
|
+
select(slice) {
|
|
146
|
+
this.activeId.set(slice.id);
|
|
147
|
+
this.sliceSelect.emit(slice.id);
|
|
148
|
+
}
|
|
149
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiDonut, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
150
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiDonut, isStandalone: true, selector: "ui-donut", inputs: { slices: { classPropertyName: "slices", publicName: "slices", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, othersLabel: { classPropertyName: "othersLabel", publicName: "othersLabel", isSignal: true, isRequired: true, transformFunction: null }, valueFormat: { classPropertyName: "valueFormat", publicName: "valueFormat", isSignal: true, isRequired: false, transformFunction: null }, shareFormat: { classPropertyName: "shareFormat", publicName: "shareFormat", isSignal: true, isRequired: false, transformFunction: null }, legendHref: { classPropertyName: "legendHref", publicName: "legendHref", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sliceSelect: "sliceSelect" }, host: { classAttribute: "flex flex-col items-center gap-3 sm:flex-row sm:items-center sm:gap-8" }, ngImport: i0, template: `
|
|
151
|
+
<div class="relative size-[232px] flex-none sm:size-60">
|
|
152
|
+
<svg class="block h-full w-full -rotate-90" role="img" viewBox="0 0 200 200" [attr.aria-label]="ariaLabel()">
|
|
153
|
+
<circle
|
|
154
|
+
class="stroke-(--muted)"
|
|
155
|
+
cx="100"
|
|
156
|
+
cy="100"
|
|
157
|
+
fill="none"
|
|
158
|
+
[attr.r]="radius"
|
|
159
|
+
[attr.stroke-width]="ringWidth"
|
|
160
|
+
/>
|
|
161
|
+
@for (slice of positioned(); track slice.id) {
|
|
162
|
+
<circle
|
|
163
|
+
cx="100"
|
|
164
|
+
cy="100"
|
|
165
|
+
data-slice
|
|
166
|
+
fill="none"
|
|
167
|
+
[attr.r]="radius"
|
|
168
|
+
[attr.stroke-dasharray]="slice.dash"
|
|
169
|
+
[attr.stroke-dashoffset]="slice.offset"
|
|
170
|
+
[attr.stroke-width]="ringWidth"
|
|
171
|
+
[class]="sliceClasses(slice)"
|
|
172
|
+
[style.stroke]="strokeColor(slice)"
|
|
173
|
+
(click)="select(slice)"
|
|
174
|
+
(pointerenter)="highlight(slice.id)"
|
|
175
|
+
(pointerleave)="clearHighlight(slice.id)"
|
|
176
|
+
/>
|
|
177
|
+
}
|
|
178
|
+
</svg>
|
|
179
|
+
|
|
180
|
+
@if (active(); as slice) {
|
|
181
|
+
<div
|
|
182
|
+
class="pointer-events-none absolute inset-0 flex flex-col items-center justify-center px-12 text-center"
|
|
183
|
+
data-testid="donut-centre"
|
|
184
|
+
>
|
|
185
|
+
<span class="text-label max-w-full truncate font-medium whitespace-nowrap text-(--muted-foreground)">{{
|
|
186
|
+
slice.label
|
|
187
|
+
}}</span>
|
|
188
|
+
<span class="text-heading font-semibold tracking-(--tracking-display)">{{ shareFormat()(slice.share) }}</span>
|
|
189
|
+
<span class="text-caption tracking-normal text-(--muted-foreground) [font-variant-numeric:var(--numeric)]">{{
|
|
190
|
+
valueFormat()(slice.value)
|
|
191
|
+
}}</span>
|
|
192
|
+
</div>
|
|
193
|
+
}
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<div class="flex w-full min-w-0 flex-1 flex-col sm:w-auto" data-testid="donut-legend">
|
|
197
|
+
@for (slice of ranked(); track slice.id) {
|
|
198
|
+
@if (hrefFor(slice.id); as href) {
|
|
199
|
+
<a
|
|
200
|
+
[attr.href]="href"
|
|
201
|
+
[class]="rowClasses(slice)"
|
|
202
|
+
(blur)="clearHighlight(slice.id)"
|
|
203
|
+
(click)="follow($event, slice)"
|
|
204
|
+
(focus)="highlight(slice.id)"
|
|
205
|
+
(pointerenter)="highlight(slice.id)"
|
|
206
|
+
(pointerleave)="clearHighlight(slice.id)"
|
|
207
|
+
>
|
|
208
|
+
<ng-container *ngTemplateOutlet="rowContent; context: { $implicit: slice }" />
|
|
209
|
+
</a>
|
|
210
|
+
} @else {
|
|
211
|
+
<button
|
|
212
|
+
type="button"
|
|
213
|
+
[class]="rowClasses(slice)"
|
|
214
|
+
(blur)="clearHighlight(slice.id)"
|
|
215
|
+
(click)="select(slice)"
|
|
216
|
+
(focus)="highlight(slice.id)"
|
|
217
|
+
(pointerenter)="highlight(slice.id)"
|
|
218
|
+
(pointerleave)="clearHighlight(slice.id)"
|
|
219
|
+
>
|
|
220
|
+
<ng-container *ngTemplateOutlet="rowContent; context: { $implicit: slice }" />
|
|
221
|
+
</button>
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<ng-template #rowContent let-slice>
|
|
227
|
+
<span [class]="dotClasses(slice)"></span>
|
|
228
|
+
<span class="flex min-w-0 flex-1 flex-col">
|
|
229
|
+
<span class="text-body truncate font-medium">{{ slice.label }}</span>
|
|
230
|
+
<span class="text-caption truncate tracking-(--tracking-caption) text-(--subtle-foreground)">{{
|
|
231
|
+
subLabel(slice)
|
|
232
|
+
}}</span>
|
|
233
|
+
</span>
|
|
234
|
+
<span class="flex flex-none flex-col items-end whitespace-nowrap">
|
|
235
|
+
<span class="text-body font-medium">{{ shareFormat()(slice.share) }}</span>
|
|
236
|
+
<span class="text-caption tracking-normal text-(--muted-foreground)">{{ valueFormat()(slice.value) }}</span>
|
|
237
|
+
</span>
|
|
238
|
+
</ng-template>
|
|
239
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
|
|
240
|
+
}
|
|
241
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiDonut, decorators: [{
|
|
242
|
+
type: Component,
|
|
243
|
+
args: [{
|
|
244
|
+
selector: 'ui-donut',
|
|
245
|
+
template: `
|
|
246
|
+
<div class="relative size-[232px] flex-none sm:size-60">
|
|
247
|
+
<svg class="block h-full w-full -rotate-90" role="img" viewBox="0 0 200 200" [attr.aria-label]="ariaLabel()">
|
|
248
|
+
<circle
|
|
249
|
+
class="stroke-(--muted)"
|
|
250
|
+
cx="100"
|
|
251
|
+
cy="100"
|
|
252
|
+
fill="none"
|
|
253
|
+
[attr.r]="radius"
|
|
254
|
+
[attr.stroke-width]="ringWidth"
|
|
255
|
+
/>
|
|
256
|
+
@for (slice of positioned(); track slice.id) {
|
|
257
|
+
<circle
|
|
258
|
+
cx="100"
|
|
259
|
+
cy="100"
|
|
260
|
+
data-slice
|
|
261
|
+
fill="none"
|
|
262
|
+
[attr.r]="radius"
|
|
263
|
+
[attr.stroke-dasharray]="slice.dash"
|
|
264
|
+
[attr.stroke-dashoffset]="slice.offset"
|
|
265
|
+
[attr.stroke-width]="ringWidth"
|
|
266
|
+
[class]="sliceClasses(slice)"
|
|
267
|
+
[style.stroke]="strokeColor(slice)"
|
|
268
|
+
(click)="select(slice)"
|
|
269
|
+
(pointerenter)="highlight(slice.id)"
|
|
270
|
+
(pointerleave)="clearHighlight(slice.id)"
|
|
271
|
+
/>
|
|
272
|
+
}
|
|
273
|
+
</svg>
|
|
274
|
+
|
|
275
|
+
@if (active(); as slice) {
|
|
276
|
+
<div
|
|
277
|
+
class="pointer-events-none absolute inset-0 flex flex-col items-center justify-center px-12 text-center"
|
|
278
|
+
data-testid="donut-centre"
|
|
279
|
+
>
|
|
280
|
+
<span class="text-label max-w-full truncate font-medium whitespace-nowrap text-(--muted-foreground)">{{
|
|
281
|
+
slice.label
|
|
282
|
+
}}</span>
|
|
283
|
+
<span class="text-heading font-semibold tracking-(--tracking-display)">{{ shareFormat()(slice.share) }}</span>
|
|
284
|
+
<span class="text-caption tracking-normal text-(--muted-foreground) [font-variant-numeric:var(--numeric)]">{{
|
|
285
|
+
valueFormat()(slice.value)
|
|
286
|
+
}}</span>
|
|
287
|
+
</div>
|
|
288
|
+
}
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
<div class="flex w-full min-w-0 flex-1 flex-col sm:w-auto" data-testid="donut-legend">
|
|
292
|
+
@for (slice of ranked(); track slice.id) {
|
|
293
|
+
@if (hrefFor(slice.id); as href) {
|
|
294
|
+
<a
|
|
295
|
+
[attr.href]="href"
|
|
296
|
+
[class]="rowClasses(slice)"
|
|
297
|
+
(blur)="clearHighlight(slice.id)"
|
|
298
|
+
(click)="follow($event, slice)"
|
|
299
|
+
(focus)="highlight(slice.id)"
|
|
300
|
+
(pointerenter)="highlight(slice.id)"
|
|
301
|
+
(pointerleave)="clearHighlight(slice.id)"
|
|
302
|
+
>
|
|
303
|
+
<ng-container *ngTemplateOutlet="rowContent; context: { $implicit: slice }" />
|
|
304
|
+
</a>
|
|
305
|
+
} @else {
|
|
306
|
+
<button
|
|
307
|
+
type="button"
|
|
308
|
+
[class]="rowClasses(slice)"
|
|
309
|
+
(blur)="clearHighlight(slice.id)"
|
|
310
|
+
(click)="select(slice)"
|
|
311
|
+
(focus)="highlight(slice.id)"
|
|
312
|
+
(pointerenter)="highlight(slice.id)"
|
|
313
|
+
(pointerleave)="clearHighlight(slice.id)"
|
|
314
|
+
>
|
|
315
|
+
<ng-container *ngTemplateOutlet="rowContent; context: { $implicit: slice }" />
|
|
316
|
+
</button>
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
</div>
|
|
320
|
+
|
|
321
|
+
<ng-template #rowContent let-slice>
|
|
322
|
+
<span [class]="dotClasses(slice)"></span>
|
|
323
|
+
<span class="flex min-w-0 flex-1 flex-col">
|
|
324
|
+
<span class="text-body truncate font-medium">{{ slice.label }}</span>
|
|
325
|
+
<span class="text-caption truncate tracking-(--tracking-caption) text-(--subtle-foreground)">{{
|
|
326
|
+
subLabel(slice)
|
|
327
|
+
}}</span>
|
|
328
|
+
</span>
|
|
329
|
+
<span class="flex flex-none flex-col items-end whitespace-nowrap">
|
|
330
|
+
<span class="text-body font-medium">{{ shareFormat()(slice.share) }}</span>
|
|
331
|
+
<span class="text-caption tracking-normal text-(--muted-foreground)">{{ valueFormat()(slice.value) }}</span>
|
|
332
|
+
</span>
|
|
333
|
+
</ng-template>
|
|
334
|
+
`,
|
|
335
|
+
imports: [NgTemplateOutlet],
|
|
336
|
+
host: {
|
|
337
|
+
class: 'flex flex-col items-center gap-3 sm:flex-row sm:items-center sm:gap-8',
|
|
338
|
+
},
|
|
339
|
+
}]
|
|
340
|
+
}], propDecorators: { slices: [{ type: i0.Input, args: [{ isSignal: true, alias: "slices", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], othersLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "othersLabel", required: true }] }], valueFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueFormat", required: false }] }], shareFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "shareFormat", required: false }] }], legendHref: [{ type: i0.Input, args: [{ isSignal: true, alias: "legendHref", required: false }] }], sliceSelect: [{ type: i0.Output, args: ["sliceSelect"] }] } });
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Generated bundle index. Do not edit.
|
|
344
|
+
*/
|
|
345
|
+
|
|
346
|
+
export { UiDonut };
|
|
347
|
+
//# sourceMappingURL=joanroucoux-cairn-ui-donut.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"joanroucoux-cairn-ui-donut.mjs","sources":["../../../projects/ui/donut/internal/donut-geometry.ts","../../../projects/ui/donut/internal/donut-slices.ts","../../../projects/ui/donut/donut.ts","../../../projects/ui/donut/joanroucoux-cairn-ui-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 on a grey ramp, and hovering,\n * focusing or activating a legend row highlights its slice and centres it in the ring.\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","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, Component } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Centred empty state: a title, a hint and an action slot, such as a catalogue search that matches
|
|
6
|
+
* nothing. The padding is 24px, 48px from 64rem.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* <ui-empty heading="Aucun instrument ne correspond à « zzz »" hint="La recherche porte sur le nom et l'ISIN.">
|
|
10
|
+
* <button ui-button variant="outline" size="md" type="button">Ajouter une ligne</button>
|
|
11
|
+
* </ui-empty>
|
|
12
|
+
*/
|
|
13
|
+
class UiEmpty {
|
|
14
|
+
heading = input.required(/* @ts-ignore */
|
|
15
|
+
...(ngDevMode ? [{ debugName: "heading" }] : /* istanbul ignore next */ []));
|
|
16
|
+
hint = input('', /* @ts-ignore */
|
|
17
|
+
...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
|
|
18
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiEmpty, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
19
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiEmpty, isStandalone: true, selector: "ui-empty", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: true, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "flex flex-col items-center gap-1.5 px-2 py-6 text-center lg:py-12" }, ngImport: i0, template: `
|
|
20
|
+
<span class="text-body font-medium text-pretty">{{ heading() }}</span>
|
|
21
|
+
@if (hint()) {
|
|
22
|
+
<span class="text-label text-(--muted-foreground)">{{ hint() }}</span>
|
|
23
|
+
}
|
|
24
|
+
<div class="mt-2 empty:hidden"><ng-content /></div>
|
|
25
|
+
`, isInline: true });
|
|
26
|
+
}
|
|
27
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiEmpty, decorators: [{
|
|
28
|
+
type: Component,
|
|
29
|
+
args: [{
|
|
30
|
+
selector: 'ui-empty',
|
|
31
|
+
template: `
|
|
32
|
+
<span class="text-body font-medium text-pretty">{{ heading() }}</span>
|
|
33
|
+
@if (hint()) {
|
|
34
|
+
<span class="text-label text-(--muted-foreground)">{{ hint() }}</span>
|
|
35
|
+
}
|
|
36
|
+
<div class="mt-2 empty:hidden"><ng-content /></div>
|
|
37
|
+
`,
|
|
38
|
+
host: { class: 'flex flex-col items-center gap-1.5 px-2 py-6 text-center lg:py-12' },
|
|
39
|
+
}]
|
|
40
|
+
}], propDecorators: { heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: true }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }] } });
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Generated bundle index. Do not edit.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
export { UiEmpty };
|
|
47
|
+
//# sourceMappingURL=joanroucoux-cairn-ui-empty.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"joanroucoux-cairn-ui-empty.mjs","sources":["../../../projects/ui/empty/empty.ts","../../../projects/ui/empty/joanroucoux-cairn-ui-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 catalogue search that matches\n * nothing. The padding is 24px, 48px from 64rem.\n *\n * @example\n * <ui-empty heading=\"Aucun instrument 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","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAEA;;;;;;;;AAQG;MAYU,OAAO,CAAA;IACT,OAAO,GAAG,KAAK,CAAC,QAAQ;gFAAU;IAClC,IAAI,GAAG,KAAK,CAAC,EAAE;6EAAC;uGAFd,OAAO,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAP,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,OAAO,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,cAAA,EAAA,mEAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EATR;;;;;;AAMT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA;;2FAGU,OAAO,EAAA,UAAA,EAAA,CAAA;kBAXnB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,UAAU;AACpB,oBAAA,QAAQ,EAAE;;;;;;AAMT,EAAA,CAAA;AACD,oBAAA,IAAI,EAAE,EAAE,KAAK,EAAE,mEAAmE,EAAE;AACrF,iBAAA;;;ACrBD;;AAEG;;;;"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Component } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Link to another site: the label followed by an external-link icon. A 44px target on touch,
|
|
6
|
+
* an underline on hover with a mouse.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* <a ui-external-link href="https://example.com" target="_blank" rel="noopener">Page on example.com</a>
|
|
10
|
+
*/
|
|
11
|
+
class UiExternalLink {
|
|
12
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiExternalLink, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: UiExternalLink, isStandalone: true, selector: "a[ui-external-link]", host: { classAttribute: "inline-flex min-h-11 items-center gap-1.5 rounded-[4px] text-label leading-[17px] font-medium lg:min-h-0 hover:underline underline-offset-3 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring) [&>svg]:flex-none" }, ngImport: i0, template: `
|
|
14
|
+
<ng-content />
|
|
15
|
+
<svg
|
|
16
|
+
aria-hidden="true"
|
|
17
|
+
fill="none"
|
|
18
|
+
height="14"
|
|
19
|
+
stroke="currentColor"
|
|
20
|
+
stroke-linecap="round"
|
|
21
|
+
stroke-linejoin="round"
|
|
22
|
+
stroke-width="1.75"
|
|
23
|
+
viewBox="0 0 24 24"
|
|
24
|
+
width="14"
|
|
25
|
+
>
|
|
26
|
+
<path d="M15 3h6v6" />
|
|
27
|
+
<path d="M10 14 21 3" />
|
|
28
|
+
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
|
29
|
+
</svg>
|
|
30
|
+
`, isInline: true });
|
|
31
|
+
}
|
|
32
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiExternalLink, decorators: [{
|
|
33
|
+
type: Component,
|
|
34
|
+
args: [{
|
|
35
|
+
selector: 'a[ui-external-link]',
|
|
36
|
+
template: `
|
|
37
|
+
<ng-content />
|
|
38
|
+
<svg
|
|
39
|
+
aria-hidden="true"
|
|
40
|
+
fill="none"
|
|
41
|
+
height="14"
|
|
42
|
+
stroke="currentColor"
|
|
43
|
+
stroke-linecap="round"
|
|
44
|
+
stroke-linejoin="round"
|
|
45
|
+
stroke-width="1.75"
|
|
46
|
+
viewBox="0 0 24 24"
|
|
47
|
+
width="14"
|
|
48
|
+
>
|
|
49
|
+
<path d="M15 3h6v6" />
|
|
50
|
+
<path d="M10 14 21 3" />
|
|
51
|
+
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
|
52
|
+
</svg>
|
|
53
|
+
`,
|
|
54
|
+
host: {
|
|
55
|
+
class: 'inline-flex min-h-11 items-center gap-1.5 rounded-[4px] text-label leading-[17px] font-medium lg:min-h-0 hover:underline underline-offset-3 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring) [&>svg]:flex-none',
|
|
56
|
+
},
|
|
57
|
+
}]
|
|
58
|
+
}] });
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Generated bundle index. Do not edit.
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
export { UiExternalLink };
|
|
65
|
+
//# sourceMappingURL=joanroucoux-cairn-ui-external-link.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"joanroucoux-cairn-ui-external-link.mjs","sources":["../../../projects/ui/external-link/external-link.ts","../../../projects/ui/external-link/joanroucoux-cairn-ui-external-link.ts"],"sourcesContent":["import { Component } from '@angular/core';\n\n/**\n * Link to another site: the label followed by an external-link icon. A 44px target on touch,\n * an underline on hover with a mouse.\n *\n * @example\n * <a ui-external-link href=\"https://example.com\" target=\"_blank\" rel=\"noopener\">Page on example.com</a>\n */\n@Component({\n selector: 'a[ui-external-link]',\n template: `\n <ng-content />\n <svg\n aria-hidden=\"true\"\n fill=\"none\"\n height=\"14\"\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n viewBox=\"0 0 24 24\"\n width=\"14\"\n >\n <path d=\"M15 3h6v6\" />\n <path d=\"M10 14 21 3\" />\n <path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\" />\n </svg>\n `,\n host: {\n class:\n 'inline-flex min-h-11 items-center gap-1.5 rounded-[4px] text-label leading-[17px] font-medium lg:min-h-0 hover:underline underline-offset-3 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring) [&>svg]:flex-none',\n },\n})\nexport class UiExternalLink {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAEA;;;;;;AAMG;MA0BU,cAAc,CAAA;uGAAd,cAAc,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAd,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,cAAc,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,qBAAA,EAAA,IAAA,EAAA,EAAA,cAAA,EAAA,qPAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAvBf;;;;;;;;;;;;;;;;;AAiBT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA;;2FAMU,cAAc,EAAA,UAAA,EAAA,CAAA;kBAzB1B,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,qBAAqB;AAC/B,oBAAA,QAAQ,EAAE;;;;;;;;;;;;;;;;;AAiBT,EAAA,CAAA;AACD,oBAAA,IAAI,EAAE;AACJ,wBAAA,KAAK,EACH,qPAAqP;AACxP,qBAAA;AACF,iBAAA;;;ACjCD;;AAEG;;;;"}
|