@cdevhub/ngx-tw 0.5.0 → 0.6.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/README.md +78 -15
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs +16 -5
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs +26 -15
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-badge.mjs +17 -5
- package/fesm2022/cdevhub-ngx-tw-badge.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs +27 -6
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-button.mjs +23 -11
- package/fesm2022/cdevhub-ngx-tw-button.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs +5 -5
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs +3 -3
- package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs +427 -122
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-card.mjs +21 -11
- package/fesm2022/cdevhub-ngx-tw-card.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs +215 -96
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs +108 -64
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs +18 -6
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs +55 -30
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-combobox-testing.mjs +218 -0
- package/fesm2022/cdevhub-ngx-tw-combobox-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs +236 -211
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-command-palette-testing.mjs +200 -0
- package/fesm2022/cdevhub-ngx-tw-command-palette-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs +34 -7
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-core.mjs +135 -15
- package/fesm2022/cdevhub-ngx-tw-core.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-date-picker-testing.mjs +203 -0
- package/fesm2022/cdevhub-ngx-tw-date-picker-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs +160 -102
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-date-range-picker-testing.mjs +209 -0
- package/fesm2022/cdevhub-ngx-tw-date-range-picker-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs +182 -107
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-dialog-testing.mjs +122 -0
- package/fesm2022/cdevhub-ngx-tw-dialog-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs +11 -2
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs +1 -1
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-file-upload-testing.mjs +149 -0
- package/fesm2022/cdevhub-ngx-tw-file-upload-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-file-upload.mjs +57 -67
- package/fesm2022/cdevhub-ngx-tw-file-upload.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs +42 -24
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs +114 -21
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-icon.mjs +16 -6
- package/fesm2022/cdevhub-ngx-tw-icon.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-input.mjs +52 -61
- package/fesm2022/cdevhub-ngx-tw-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-item.mjs +28 -4
- package/fesm2022/cdevhub-ngx-tw-item.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-menu-testing.mjs +168 -0
- package/fesm2022/cdevhub-ngx-tw-menu-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-menu.mjs +58 -21
- package/fesm2022/cdevhub-ngx-tw-menu.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-number-input.mjs +34 -1
- package/fesm2022/cdevhub-ngx-tw-number-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs +95 -45
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-popover.mjs +81 -32
- package/fesm2022/cdevhub-ngx-tw-popover.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs +1 -1
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-radio.mjs +131 -98
- package/fesm2022/cdevhub-ngx-tw-radio.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs +207 -42
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-select-testing.mjs +165 -0
- package/fesm2022/cdevhub-ngx-tw-select-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-select.mjs +386 -210
- package/fesm2022/cdevhub-ngx-tw-select.mjs.map +1 -1
- package/fesm2022/{cdevhub-ngx-tw-sheet-sheet-renderer-ByyNluUd.mjs → cdevhub-ngx-tw-sheet-sheet-renderer-CqVJwgmp.mjs} +3 -3
- package/fesm2022/cdevhub-ngx-tw-sheet-sheet-renderer-CqVJwgmp.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-sheet-testing.mjs +129 -0
- package/fesm2022/cdevhub-ngx-tw-sheet-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs +23 -7
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-slider.mjs +89 -54
- package/fesm2022/cdevhub-ngx-tw-slider.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-sort.mjs +122 -17
- package/fesm2022/cdevhub-ngx-tw-sort.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-split.mjs +72 -19
- package/fesm2022/cdevhub-ngx-tw-split.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-stat.mjs +63 -25
- package/fesm2022/cdevhub-ngx-tw-stat.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs +35 -8
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-switch.mjs +54 -48
- package/fesm2022/cdevhub-ngx-tw-switch.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs +12 -15
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-table.mjs +74 -13
- package/fesm2022/cdevhub-ngx-tw-table.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs +84 -30
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tags-input-testing.mjs +172 -0
- package/fesm2022/cdevhub-ngx-tw-tags-input-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-tags-input.mjs +107 -71
- package/fesm2022/cdevhub-ngx-tw-tags-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs +41 -9
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-theme.mjs +257 -45
- package/fesm2022/cdevhub-ngx-tw-theme.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-time-picker-testing.mjs +201 -0
- package/fesm2022/cdevhub-ngx-tw-time-picker-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs +184 -77
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs +110 -27
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs.map +1 -1
- package/fesm2022/{cdevhub-ngx-tw-toast-toast-renderer-DSu4YoTy.mjs → cdevhub-ngx-tw-toast-toast-renderer-BlDxemsU.mjs} +107 -19
- package/fesm2022/cdevhub-ngx-tw-toast-toast-renderer-BlDxemsU.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-toast.mjs +21 -13
- package/fesm2022/cdevhub-ngx-tw-toast.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs +102 -15
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-transfer-testing.mjs +219 -0
- package/fesm2022/cdevhub-ngx-tw-transfer-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-transfer.mjs +88 -69
- package/fesm2022/cdevhub-ngx-tw-transfer.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tree.mjs +68 -19
- package/fesm2022/cdevhub-ngx-tw-tree.mjs.map +1 -1
- package/index.json +1 -1
- package/package.json +63 -2
- package/theme/_base.css +13 -0
- package/theme/_dark.css +111 -36
- package/theme/_high-contrast-dark.css +367 -0
- package/theme/_high-contrast.css +13 -2
- package/theme/_light.css +332 -0
- package/theme/_semantic.css +60 -26
- package/theme/_typography.css +17 -0
- package/theme/index.css +86 -7
- package/types/cdevhub-ngx-tw-accordion.d.ts +12 -4
- package/types/cdevhub-ngx-tw-avatar.d.ts +10 -3
- package/types/cdevhub-ngx-tw-button.d.ts +1 -0
- package/types/cdevhub-ngx-tw-calendar-luxon.d.ts +2 -2
- package/types/cdevhub-ngx-tw-calendar-testing.d.ts +1 -1
- package/types/cdevhub-ngx-tw-calendar.d.ts +179 -73
- package/types/cdevhub-ngx-tw-card.d.ts +11 -3
- package/types/cdevhub-ngx-tw-carousel.d.ts +89 -20
- package/types/cdevhub-ngx-tw-checkbox.d.ts +40 -12
- package/types/cdevhub-ngx-tw-code-block.d.ts +11 -2
- package/types/cdevhub-ngx-tw-collapsible.d.ts +32 -5
- package/types/cdevhub-ngx-tw-combobox-testing.d.ts +157 -0
- package/types/cdevhub-ngx-tw-combobox.d.ts +94 -53
- package/types/cdevhub-ngx-tw-command-palette-testing.d.ts +136 -0
- package/types/cdevhub-ngx-tw-command-palette.d.ts +33 -5
- package/types/cdevhub-ngx-tw-core.d.ts +152 -21
- package/types/cdevhub-ngx-tw-date-picker-testing.d.ts +141 -0
- package/types/cdevhub-ngx-tw-date-picker.d.ts +37 -29
- package/types/cdevhub-ngx-tw-date-range-picker-testing.d.ts +148 -0
- package/types/cdevhub-ngx-tw-date-range-picker.d.ts +36 -29
- package/types/cdevhub-ngx-tw-dialog-testing.d.ts +98 -0
- package/types/cdevhub-ngx-tw-dialog.d.ts +12 -3
- package/types/cdevhub-ngx-tw-file-upload-testing.d.ts +120 -0
- package/types/cdevhub-ngx-tw-file-upload.d.ts +8 -10
- package/types/cdevhub-ngx-tw-flip-card.d.ts +18 -11
- package/types/cdevhub-ngx-tw-form-field.d.ts +19 -2
- package/types/cdevhub-ngx-tw-icon.d.ts +3 -3
- package/types/cdevhub-ngx-tw-input.d.ts +5 -8
- package/types/cdevhub-ngx-tw-item.d.ts +2 -0
- package/types/cdevhub-ngx-tw-menu-testing.d.ts +131 -0
- package/types/cdevhub-ngx-tw-menu.d.ts +2 -2
- package/types/cdevhub-ngx-tw-number-input.d.ts +20 -0
- package/types/cdevhub-ngx-tw-paginator.d.ts +58 -24
- package/types/cdevhub-ngx-tw-popover.d.ts +41 -10
- package/types/cdevhub-ngx-tw-progress-bar.d.ts +1 -1
- package/types/cdevhub-ngx-tw-radio.d.ts +68 -25
- package/types/cdevhub-ngx-tw-segmented-control.d.ts +79 -16
- package/types/cdevhub-ngx-tw-select-testing.d.ts +114 -0
- package/types/cdevhub-ngx-tw-select.d.ts +104 -33
- package/types/cdevhub-ngx-tw-sheet-testing.d.ts +104 -0
- package/types/cdevhub-ngx-tw-sheet.d.ts +19 -3
- package/types/cdevhub-ngx-tw-slider.d.ts +79 -25
- package/types/cdevhub-ngx-tw-sort.d.ts +45 -4
- package/types/cdevhub-ngx-tw-split.d.ts +32 -2
- package/types/cdevhub-ngx-tw-stat.d.ts +14 -4
- package/types/cdevhub-ngx-tw-stepper.d.ts +16 -2
- package/types/cdevhub-ngx-tw-switch.d.ts +35 -13
- package/types/cdevhub-ngx-tw-tab-nav.d.ts +7 -6
- package/types/cdevhub-ngx-tw-table.d.ts +53 -10
- package/types/cdevhub-ngx-tw-tabs.d.ts +41 -10
- package/types/cdevhub-ngx-tw-tags-input-testing.d.ts +135 -0
- package/types/cdevhub-ngx-tw-tags-input.d.ts +26 -11
- package/types/cdevhub-ngx-tw-textarea.d.ts +3 -2
- package/types/cdevhub-ngx-tw-theme.d.ts +245 -39
- package/types/cdevhub-ngx-tw-time-picker-testing.d.ts +129 -0
- package/types/cdevhub-ngx-tw-time-picker.d.ts +76 -19
- package/types/cdevhub-ngx-tw-timeline.d.ts +36 -12
- package/types/cdevhub-ngx-tw-toast.d.ts +20 -6
- package/types/cdevhub-ngx-tw-tooltip.d.ts +28 -6
- package/types/cdevhub-ngx-tw-transfer-testing.d.ts +152 -0
- package/types/cdevhub-ngx-tw-transfer.d.ts +21 -8
- package/types/cdevhub-ngx-tw-tree.d.ts +6 -1
- package/fesm2022/cdevhub-ngx-tw-sheet-sheet-renderer-ByyNluUd.mjs.map +0 -1
- package/fesm2022/cdevhub-ngx-tw-toast-toast-renderer-DSu4YoTy.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cdevhub-ngx-tw-progress-bar.mjs","sources":["../../../projects/ngx-tw/progress-bar/progress-bar.ts","../../../projects/ngx-tw/progress-bar/cdevhub-ngx-tw-progress-bar.ts"],"sourcesContent":["/**\n * `tw-progress-bar` — visualises measurable progress.\n *\n * Composition usecases:\n * - Upload progress (determinate linear with `options.formatter` → `\"3.2 MB / 10 MB\"`).\n * - Multi-step wizard (segmented variant, `options.segments` = step count).\n * - Skill / rating display (static determinate linear).\n * - Inline list-row progress (compact `size=\"sm\"`, constrained width via host class).\n * - Card-footer task completion (`options.showValue=true`, `color` switches to `success`/`error`).\n * - Indeterminate loading before first byte (omit `value` — the bar sweeps).\n *\n * For unknown-duration operations with no measurable progress, use `tw-spinner` instead.\n */\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n input,\n untracked,\n} from '@angular/core';\nimport { tv } from 'tailwind-variants';\nimport type { TwColor } from '@cdevhub/ngx-tw/core';\n\n// `ngDevMode` is a globalThis flag set by Angular's build tooling — true in dev,\n// `false` in production builds. The `typeof` guard keeps the warning path safe\n// in environments (SSR, ad-hoc test harnesses) where the global is undeclared.\n// Preferred over `isDevMode()` because the latter is a runtime function call;\n// `ngDevMode` is a build-time constant the bundler can dead-code-eliminate.\ndeclare const ngDevMode: boolean | undefined;\n\n/** Visual style of the progress bar. */\nexport type ProgressBarVariant = 'linear' | 'segmented';\n\n/** Size scale specific to progress bars (bar thickness). */\nexport type ProgressBarSize = 'sm' | 'md' | 'lg';\n\n/** Function signature for formatting the visible / announced progress value. */\nexport type ProgressBarValueFormatter = (value: number, max: number, min: number) => string;\n\n/**\n * Non-visual configuration bundled into a single input to keep the component's public\n * surface small. Every field is optional and falls back to a sensible default.\n */\nexport interface ProgressBarOptions {\n /** Lower bound of the value range. Defaults to `0`. */\n min?: number;\n /** Upper bound of the value range. Defaults to `100`. */\n max?: number;\n /** Number of equal cells when `variant` is `'segmented'`. Ignored for `'linear'`. Defaults to `5`. */\n segments?: number;\n /** When true, renders the formatted progress value next to the label. Defaults to `false`. */\n showValue?: boolean;\n /** Custom formatter for the displayed and announced value. Defaults to an integer percentage, e.g. `'42%'`. */\n formatter?: ProgressBarValueFormatter;\n /** Accessible name when no visible `label` is provided. Mirrored to `aria-label` on the progressbar element. */\n ariaLabel?: string;\n /** ID of an external element that labels the progress bar. Mirrored to `aria-labelledby`. */\n ariaLabelledby?: string;\n}\n\n// ── Static fill-color lookup (Tailwind v4 scans class strings statically) ──\n\nconst FILL_COLORS: Record<TwColor, string> = {\n primary: 'bg-primary-500',\n secondary: 'bg-secondary-500',\n accent: 'bg-accent-500',\n neutral: 'bg-fg',\n info: 'bg-info-500',\n success: 'bg-success-500',\n warning: 'bg-warning-500',\n error: 'bg-error-500',\n};\n\n// ── tv() config ──\n\nconst progressBarVariants = tv({\n slots: {\n root: 'flex flex-col w-full',\n header: 'flex items-center justify-between gap-3',\n label: 'text-xs text-fg-muted',\n valueText: 'text-xs text-fg-muted font-medium tabular-nums',\n rail: 'relative w-full overflow-hidden rounded-full bg-surface-muted',\n fill: 'absolute inset-y-0 left-0 rounded-full',\n segmentList: 'flex w-full gap-1',\n segment: 'flex-1 rounded-full',\n },\n variants: {\n size: {\n sm: { rail: 'h-1', segment: 'h-1' },\n md: { rail: 'h-2', segment: 'h-2' },\n lg: { rail: 'h-3', segment: 'h-3' },\n },\n variant: {\n linear: { segmentList: 'hidden' },\n segmented: { rail: 'hidden' },\n },\n isIndeterminate: {\n true: { fill: 'w-[30%] animate-progress-bar-indeterminate' },\n false: { fill: 'transition-[width] duration-normal motion-reduce:transition-none' },\n },\n hasHeader: {\n true: { root: 'gap-1.5' },\n false: { root: 'gap-0' },\n },\n },\n defaultVariants: {\n size: 'md',\n variant: 'linear',\n isIndeterminate: false,\n hasHeader: false,\n },\n}, {\n twMerge: true,\n});\n\nlet nextId = 0;\n\nconst DEFAULT_FORMATTER: ProgressBarValueFormatter = (value, max, min) => {\n const range = max - min;\n if (range <= 0) return '0%';\n return `${Math.round(((value - min) / range) * 100)}%`;\n};\n\n/**\n * Indicates progress toward the completion of a task.\n *\n * @example\n * ```html\n * <tw-progress-bar [value]=\"40\" />\n * <tw-progress-bar label=\"Fetching records\" />\n * <tw-progress-bar\n * variant=\"segmented\"\n * [value]=\"step() * 25\"\n * [options]=\"{ segments: 4 }\"\n * />\n * ```\n */\n@Component({\n selector: 'tw-progress-bar',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'rootClasses()',\n },\n template: `\n @if (hasHeader()) {\n <div [class]=\"headerClasses()\">\n @if (label(); as lbl) {\n <span [id]=\"labelId\" [class]=\"labelClasses()\">{{ lbl }}</span>\n } @else {\n <span></span>\n }\n @if (resolvedShowValue() && !isIndeterminate()) {\n <span [class]=\"valueTextClasses()\">{{ formattedValue() }}</span>\n }\n </div>\n }\n\n @if (variant() === 'linear') {\n <div\n [class]=\"railClasses()\"\n role=\"progressbar\"\n [attr.aria-valuemin]=\"resolvedMin()\"\n [attr.aria-valuemax]=\"resolvedMax()\"\n [attr.aria-valuenow]=\"isIndeterminate() ? null : clampedValue()\"\n [attr.aria-valuetext]=\"isIndeterminate() ? null : formattedValue()\"\n [attr.aria-busy]=\"isIndeterminate() ? true : null\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-labelledby]=\"resolvedAriaLabelledby()\"\n >\n @if (isIndeterminate()) {\n <span [class]=\"fillClasses()\"></span>\n } @else {\n <span [class]=\"fillClasses()\" [style.width.%]=\"progressRatio() * 100\"></span>\n }\n </div>\n } @else {\n <div\n [class]=\"segmentListClasses()\"\n role=\"progressbar\"\n [attr.aria-valuemin]=\"resolvedMin()\"\n [attr.aria-valuemax]=\"resolvedMax()\"\n [attr.aria-valuenow]=\"isIndeterminate() ? null : clampedValue()\"\n [attr.aria-valuetext]=\"isIndeterminate() ? null : formattedValue()\"\n [attr.aria-busy]=\"isIndeterminate() ? true : null\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-labelledby]=\"resolvedAriaLabelledby()\"\n >\n @for (i of segmentIndices(); track i) {\n <span [class]=\"segmentClassFor(i)\"></span>\n }\n </div>\n }\n `,\n})\nexport class ProgressBarComponent {\n /** Current progress value. When null or undefined, the bar renders indeterminate. Values outside `[options.min, options.max]` are clamped. */\n readonly value = input<number | null | undefined>(null);\n\n /** Visual style of the bar. `'linear'` renders a single fill; `'segmented'` splits the rail into discrete steps. Defaults to `'linear'`. */\n readonly variant = input<ProgressBarVariant>('linear');\n\n /** Semantic color of the filled portion. Defaults to `'primary'`. Use status colors (`success`/`warning`/`error`) to reflect task outcome. */\n readonly color = input<TwColor>('primary');\n\n /** Bar thickness. `'sm'` = h-1, `'md'` = h-2, `'lg'` = h-3. Defaults to `'md'`. */\n readonly size = input<ProgressBarSize>('md');\n\n /** Visible label rendered above the bar. When set, the bar is wired to the label via `aria-labelledby`. */\n readonly label = input<string | undefined>(undefined);\n\n /** Bundles non-visual configuration: value range (`min`/`max`/`segments`), value readout (`showValue`/`formatter`), and accessibility fallbacks (`ariaLabel`/`ariaLabelledby`). Every field is optional. */\n readonly options = input<ProgressBarOptions | undefined>(undefined);\n\n /** @internal */\n readonly labelId = `tw-progress-bar-${nextId++}-label`;\n\n /** @internal */\n readonly resolvedMin = computed(() => this.options()?.min ?? 0);\n\n /** @internal */\n readonly resolvedMax = computed(() => this.options()?.max ?? 100);\n\n /** @internal */\n readonly resolvedSegments = computed(() => this.options()?.segments ?? 5);\n\n /** @internal */\n readonly resolvedShowValue = computed(() => this.options()?.showValue ?? false);\n\n /** @internal */\n readonly isIndeterminate = computed(() => {\n const v = this.value();\n return v === null || v === undefined;\n });\n\n /** @internal */\n readonly clampedValue = computed(() => {\n const v = this.value();\n if (v === null || v === undefined) return this.resolvedMin();\n const lo = this.resolvedMin();\n const hi = this.resolvedMax();\n if (v < lo) return lo;\n if (v > hi) return hi;\n return v;\n });\n\n /** @internal */\n readonly progressRatio = computed(() => {\n if (this.isIndeterminate()) return 0;\n const range = this.resolvedMax() - this.resolvedMin();\n if (range <= 0) return 0;\n return (this.clampedValue() - this.resolvedMin()) / range;\n });\n\n /** @internal */\n readonly formattedValue = computed(() => {\n if (this.isIndeterminate()) return '';\n const fmt = this.options()?.formatter ?? DEFAULT_FORMATTER;\n return fmt(this.clampedValue(), this.resolvedMax(), this.resolvedMin());\n });\n\n /** @internal */\n readonly hasHeader = computed(() => !!this.label() || this.resolvedShowValue());\n\n /** @internal */\n readonly segmentIndices = computed(() => {\n const n = Math.max(1, Math.floor(this.resolvedSegments()));\n return Array.from({ length: n }, (_, i) => i);\n });\n\n /** @internal */\n readonly resolvedAriaLabel = computed(() => {\n if (this.label()) return null;\n if (this.options()?.ariaLabelledby) return null;\n return this.options()?.ariaLabel ?? null;\n });\n\n /** @internal */\n readonly resolvedAriaLabelledby = computed(() => {\n if (this.label()) return this.labelId;\n return this.options()?.ariaLabelledby ?? null;\n });\n\n // ── tv() slot computeds ──\n\n private readonly variantResult = computed(() =>\n progressBarVariants({\n size: this.size(),\n variant: this.variant(),\n isIndeterminate: this.isIndeterminate(),\n hasHeader: this.hasHeader(),\n }),\n );\n\n readonly rootClasses = computed(() => this.variantResult().root());\n readonly headerClasses = computed(() => this.variantResult().header());\n readonly labelClasses = computed(() => this.variantResult().label());\n readonly valueTextClasses = computed(() => this.variantResult().valueText());\n readonly railClasses = computed(() => this.variantResult().rail());\n readonly fillClasses = computed(() => `${this.variantResult().fill()} ${FILL_COLORS[this.color()]}`);\n readonly segmentListClasses = computed(() => this.variantResult().segmentList());\n private readonly segmentBaseClasses = computed(() => this.variantResult().segment());\n\n /** @internal */\n segmentClassFor(index: number): string {\n const base = this.segmentBaseClasses();\n const filled = (index + 1) / this.segmentIndices().length <= this.progressRatio();\n const bg = filled ? FILL_COLORS[this.color()] : 'bg-surface-muted';\n return `${base} ${bg}`;\n }\n\n // ── Dev-mode accessible-name warning (fires once per instance) ──\n //\n // The effect is reactive on purpose: if a consumer mounts the component\n // without a label and later removes the only accessible name they had, the\n // warning still fires on the *first* render where no name is present. Once\n // it has fired, `warned` short-circuits further evaluations. Production\n // builds skip the entire effect setup because `ngDevMode` is statically\n // `false` and the constructor returns early.\n\n constructor() {\n if (typeof ngDevMode !== 'undefined' && !ngDevMode) return;\n let warned = false;\n effect(() => {\n if (warned) return;\n const opts = this.options();\n if (this.label() || opts?.ariaLabel || opts?.ariaLabelledby) return;\n // `untracked` wraps the side-effect so the console.warn / flag mutation\n // can never create a reactive subscription — defensive in case future\n // work adds signal reads inside the warning block.\n untracked(() => {\n warned = true;\n console.warn(\n 'tw-progress-bar: no accessible name provided. Supply one of `label`, `options.ariaLabel`, or `options.ariaLabelledby`.',\n );\n });\n });\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAAA;;;;;;;;;;;;AAYG;AAiDH;AAEA,MAAM,WAAW,GAA4B;AAC3C,IAAA,OAAO,EAAE,gBAAgB;AACzB,IAAA,SAAS,EAAE,kBAAkB;AAC7B,IAAA,MAAM,EAAE,eAAe;AACvB,IAAA,OAAO,EAAE,OAAO;AAChB,IAAA,IAAI,EAAE,aAAa;AACnB,IAAA,OAAO,EAAE,gBAAgB;AACzB,IAAA,OAAO,EAAE,gBAAgB;AACzB,IAAA,KAAK,EAAE,cAAc;CACtB;AAED;AAEA,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAC7B,IAAA,KAAK,EAAE;AACL,QAAA,IAAI,EAAE,sBAAsB;AAC5B,QAAA,MAAM,EAAE,yCAAyC;AACjD,QAAA,KAAK,EAAE,uBAAuB;AAC9B,QAAA,SAAS,EAAE,gDAAgD;AAC3D,QAAA,IAAI,EAAE,+DAA+D;AACrE,QAAA,IAAI,EAAE,wCAAwC;AAC9C,QAAA,WAAW,EAAE,mBAAmB;AAChC,QAAA,OAAO,EAAE,qBAAqB;AAC/B,KAAA;AACD,IAAA,QAAQ,EAAE;AACR,QAAA,IAAI,EAAE;YACJ,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE;YACnC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE;YACnC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE;AACpC,SAAA;AACD,QAAA,OAAO,EAAE;AACP,YAAA,MAAM,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE;AACjC,YAAA,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;AAC9B,SAAA;AACD,QAAA,eAAe,EAAE;AACf,YAAA,IAAI,EAAE,EAAE,IAAI,EAAE,4CAA4C,EAAE;AAC5D,YAAA,KAAK,EAAE,EAAE,IAAI,EAAE,kEAAkE,EAAE;AACpF,SAAA;AACD,QAAA,SAAS,EAAE;AACT,YAAA,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;AACzB,YAAA,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;AACzB,SAAA;AACF,KAAA;AACD,IAAA,eAAe,EAAE;AACf,QAAA,IAAI,EAAE,IAAI;AACV,QAAA,OAAO,EAAE,QAAQ;AACjB,QAAA,eAAe,EAAE,KAAK;AACtB,QAAA,SAAS,EAAE,KAAK;AACjB,KAAA;CACF,EAAE;AACD,IAAA,OAAO,EAAE,IAAI;AACd,CAAA,CAAC;AAEF,IAAI,MAAM,GAAG,CAAC;AAEd,MAAM,iBAAiB,GAA8B,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,KAAI;AACvE,IAAA,MAAM,KAAK,GAAG,GAAG,GAAG,GAAG;IACvB,IAAI,KAAK,IAAI,CAAC;AAAE,QAAA,OAAO,IAAI;AAC3B,IAAA,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,GAAG,IAAI,KAAK,IAAI,GAAG,CAAC,GAAG;AACxD,CAAC;AAED;;;;;;;;;;;;;AAaG;MA0DU,oBAAoB,CAAA;;IAEtB,KAAK,GAAG,KAAK,CAA4B,IAAI;8EAAC;;IAG9C,OAAO,GAAG,KAAK,CAAqB,QAAQ;gFAAC;;IAG7C,KAAK,GAAG,KAAK,CAAU,SAAS;8EAAC;;IAGjC,IAAI,GAAG,KAAK,CAAkB,IAAI;6EAAC;;IAGnC,KAAK,GAAG,KAAK,CAAqB,SAAS;8EAAC;;IAG5C,OAAO,GAAG,KAAK,CAAiC,SAAS;gFAAC;;AAG1D,IAAA,OAAO,GAAG,CAAA,gBAAA,EAAmB,MAAM,EAAE,QAAQ;;AAG7C,IAAA,WAAW,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC;oFAAC;;AAGtD,IAAA,WAAW,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,EAAE,GAAG,IAAI,GAAG;oFAAC;;AAGxD,IAAA,gBAAgB,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,IAAI,CAAC;yFAAC;;AAGhE,IAAA,iBAAiB,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,IAAI,KAAK;0FAAC;;AAGtE,IAAA,eAAe,GAAG,QAAQ,CAAC,MAAK;AACvC,QAAA,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE;AACtB,QAAA,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS;IACtC,CAAC;wFAAC;;AAGO,IAAA,YAAY,GAAG,QAAQ,CAAC,MAAK;AACpC,QAAA,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE;AACtB,QAAA,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS;AAAE,YAAA,OAAO,IAAI,CAAC,WAAW,EAAE;AAC5D,QAAA,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE;AAC7B,QAAA,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE;QAC7B,IAAI,CAAC,GAAG,EAAE;AAAE,YAAA,OAAO,EAAE;QACrB,IAAI,CAAC,GAAG,EAAE;AAAE,YAAA,OAAO,EAAE;AACrB,QAAA,OAAO,CAAC;IACV,CAAC;qFAAC;;AAGO,IAAA,aAAa,GAAG,QAAQ,CAAC,MAAK;QACrC,IAAI,IAAI,CAAC,eAAe,EAAE;AAAE,YAAA,OAAO,CAAC;QACpC,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE;QACrD,IAAI,KAAK,IAAI,CAAC;AAAE,YAAA,OAAO,CAAC;AACxB,QAAA,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE,IAAI,KAAK;IAC3D,CAAC;sFAAC;;AAGO,IAAA,cAAc,GAAG,QAAQ,CAAC,MAAK;QACtC,IAAI,IAAI,CAAC,eAAe,EAAE;AAAE,YAAA,OAAO,EAAE;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,IAAI,iBAAiB;AAC1D,QAAA,OAAO,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC;IACzE,CAAC;uFAAC;;AAGO,IAAA,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,IAAI,CAAC,iBAAiB,EAAE;kFAAC;;AAGtE,IAAA,cAAc,GAAG,QAAQ,CAAC,MAAK;AACtC,QAAA,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC;AAC1D,QAAA,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IAC/C,CAAC;uFAAC;;AAGO,IAAA,iBAAiB,GAAG,QAAQ,CAAC,MAAK;QACzC,IAAI,IAAI,CAAC,KAAK,EAAE;AAAE,YAAA,OAAO,IAAI;AAC7B,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,cAAc;AAAE,YAAA,OAAO,IAAI;QAC/C,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,IAAI,IAAI;IAC1C,CAAC;0FAAC;;AAGO,IAAA,sBAAsB,GAAG,QAAQ,CAAC,MAAK;QAC9C,IAAI,IAAI,CAAC,KAAK,EAAE;YAAE,OAAO,IAAI,CAAC,OAAO;QACrC,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,cAAc,IAAI,IAAI;IAC/C,CAAC;+FAAC;;AAIe,IAAA,aAAa,GAAG,QAAQ,CAAC,MACxC,mBAAmB,CAAC;AAClB,QAAA,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;AACjB,QAAA,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE;AACvB,QAAA,eAAe,EAAE,IAAI,CAAC,eAAe,EAAE;AACvC,QAAA,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE;KAC5B,CAAC;sFACH;AAEQ,IAAA,WAAW,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE;oFAAC;AACzD,IAAA,aAAa,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE;sFAAC;AAC7D,IAAA,YAAY,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE;qFAAC;AAC3D,IAAA,gBAAgB,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC,SAAS,EAAE;yFAAC;AACnE,IAAA,WAAW,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE;oFAAC;IACzD,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAA,EAAG,IAAI,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAA,CAAE;oFAAC;AAC3F,IAAA,kBAAkB,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC,WAAW,EAAE;2FAAC;AAC/D,IAAA,kBAAkB,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE;2FAAC;;AAGpF,IAAA,eAAe,CAAC,KAAa,EAAA;AAC3B,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,kBAAkB,EAAE;AACtC,QAAA,MAAM,MAAM,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC,MAAM,IAAI,IAAI,CAAC,aAAa,EAAE;AACjF,QAAA,MAAM,EAAE,GAAG,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,kBAAkB;AAClE,QAAA,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,EAAE,EAAE;IACxB;;;;;;;;;AAWA,IAAA,WAAA,GAAA;AACE,QAAA,IAAI,OAAO,SAAS,KAAK,WAAW,IAAI,CAAC,SAAS;YAAE;QACpD,IAAI,MAAM,GAAG,KAAK;QAClB,MAAM,CAAC,MAAK;AACV,YAAA,IAAI,MAAM;gBAAE;AACZ,YAAA,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE;YAC3B,IAAI,IAAI,CAAC,KAAK,EAAE,IAAI,IAAI,EAAE,SAAS,IAAI,IAAI,EAAE,cAAc;gBAAE;;;;YAI7D,SAAS,CAAC,MAAK;gBACb,MAAM,GAAG,IAAI;AACb,gBAAA,OAAO,CAAC,IAAI,CACV,wHAAwH,CACzH;AACH,YAAA,CAAC,CAAC;AACJ,QAAA,CAAC,CAAC;IACJ;uGA9IW,oBAAoB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAApB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,oBAAoB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,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,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,OAAA,EAAA,eAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAnDrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAEU,oBAAoB,EAAA,UAAA,EAAA,CAAA;kBAzDhC,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,iBAAiB;oBAC3B,eAAe,EAAE,uBAAuB,CAAC,MAAM;AAC/C,oBAAA,IAAI,EAAE;AACJ,wBAAA,SAAS,EAAE,eAAe;AAC3B,qBAAA;AACD,oBAAA,QAAQ,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDT,EAAA,CAAA;AACF,iBAAA;;;AClMD;;AAEG;;;;"}
|
|
1
|
+
{"version":3,"file":"cdevhub-ngx-tw-progress-bar.mjs","sources":["../../../projects/ngx-tw/progress-bar/progress-bar.ts","../../../projects/ngx-tw/progress-bar/cdevhub-ngx-tw-progress-bar.ts"],"sourcesContent":["/**\n * `tw-progress-bar` — visualises measurable progress.\n *\n * Composition usecases:\n * - Upload progress (determinate linear with `options.formatter` → `\"3.2 MB / 10 MB\"`).\n * - Multi-step wizard (segmented variant, `options.segments` = step count).\n * - Skill / rating display (static determinate linear).\n * - Inline list-row progress (compact `size=\"sm\"`, constrained width via host class).\n * - Card-footer task completion (`options.showValue=true`, `color` switches to `success`/`error`).\n * - Indeterminate loading before first byte (omit `value` — the bar sweeps).\n *\n * For unknown-duration operations with no measurable progress, use `tw-spinner` instead.\n */\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n input,\n untracked,\n} from '@angular/core';\nimport { tv } from 'tailwind-variants';\nimport type { TwColor } from '@cdevhub/ngx-tw/core';\n\n// `ngDevMode` is a globalThis flag set by Angular's build tooling — true in dev,\n// `false` in production builds. The `typeof` guard keeps the warning path safe\n// in environments (SSR, ad-hoc test harnesses) where the global is undeclared.\n// Preferred over `isDevMode()` because the latter is a runtime function call;\n// `ngDevMode` is a build-time constant the bundler can dead-code-eliminate.\ndeclare const ngDevMode: boolean | undefined;\n\n/** Visual style of the progress bar. */\nexport type ProgressBarVariant = 'linear' | 'segmented';\n\n/** Size scale specific to progress bars (bar thickness). */\nexport type ProgressBarSize = 'sm' | 'md' | 'lg';\n\n/** Function signature for formatting the visible / announced progress value. */\nexport type ProgressBarValueFormatter = (value: number, max: number, min: number) => string;\n\n/**\n * Non-visual configuration bundled into a single input to keep the component's public\n * surface small. Every field is optional and falls back to a sensible default.\n */\nexport interface ProgressBarOptions {\n /** Lower bound of the value range. Defaults to `0`. */\n min?: number;\n /** Upper bound of the value range. Defaults to `100`. */\n max?: number;\n /** Number of equal cells when `variant` is `'segmented'`. Ignored for `'linear'`. Defaults to `5`. */\n segments?: number;\n /** When true, renders the formatted progress value next to the label. Defaults to `false`. */\n showValue?: boolean;\n /** Custom formatter for the displayed and announced value. Defaults to an integer percentage, e.g. `'42%'`. */\n formatter?: ProgressBarValueFormatter;\n /** Accessible name when no visible `label` is provided. Mirrored to `aria-label` on the progressbar element. */\n ariaLabel?: string;\n /** ID of an external element that labels the progress bar. Mirrored to `aria-labelledby`. */\n ariaLabelledby?: string;\n}\n\n// ── Static fill-color lookup (Tailwind v4 scans class strings statically) ──\n\nconst FILL_COLORS: Record<TwColor, string> = {\n primary: 'bg-primary-500',\n secondary: 'bg-secondary-500',\n accent: 'bg-accent-500',\n neutral: 'bg-fg',\n info: 'bg-info-500',\n success: 'bg-success-500',\n warning: 'bg-warning-500',\n error: 'bg-error-500',\n};\n\n// ── tv() config ──\n\nconst progressBarVariants = tv({\n slots: {\n root: 'flex flex-col w-full',\n header: 'flex items-center justify-between gap-3',\n label: 'text-xs text-fg-muted',\n valueText: 'text-xs text-fg-muted font-medium tabular-nums',\n rail: 'relative w-full overflow-hidden rounded-full bg-surface-muted',\n fill: 'absolute inset-y-0 left-0 rounded-full',\n segmentList: 'flex w-full gap-1',\n segment: 'flex-1 rounded-full',\n },\n variants: {\n size: {\n sm: { rail: 'h-1', segment: 'h-1' },\n md: { rail: 'h-2', segment: 'h-2' },\n lg: { rail: 'h-3', segment: 'h-3' },\n },\n variant: {\n linear: { segmentList: 'hidden' },\n segmented: { rail: 'hidden' },\n },\n isIndeterminate: {\n true: { fill: 'w-[30%] animate-progress-bar-indeterminate' },\n false: { fill: 'transition-[width] duration-normal motion-reduce:transition-none' },\n },\n hasHeader: {\n true: { root: 'gap-1.5' },\n false: { root: 'gap-0' },\n },\n },\n defaultVariants: {\n size: 'md',\n variant: 'linear',\n isIndeterminate: false,\n hasHeader: false,\n },\n}, {\n twMerge: true,\n});\n\nlet nextId = 0;\n\nconst DEFAULT_FORMATTER: ProgressBarValueFormatter = (value, max, min) => {\n const range = max - min;\n if (range <= 0) return '0%';\n return `${Math.round(((value - min) / range) * 100)}%`;\n};\n\n/**\n * Indicates progress toward the completion of a task.\n *\n * @example\n * ```html\n * <tw-progress-bar [value]=\"40\" />\n * <tw-progress-bar label=\"Fetching records\" />\n * <tw-progress-bar\n * variant=\"segmented\"\n * [value]=\"step() * 25\"\n * [options]=\"{ segments: 4 }\"\n * />\n * ```\n */\n@Component({\n selector: 'tw-progress-bar',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'rootClasses()',\n },\n template: `\n @if (hasHeader()) {\n <div [class]=\"headerClasses()\">\n @if (label(); as lbl) {\n <span [id]=\"labelId\" [class]=\"labelClasses()\">{{ lbl }}</span>\n } @else {\n <span></span>\n }\n @if (resolvedShowValue() && !isIndeterminate()) {\n <span [class]=\"valueTextClasses()\">{{ formattedValue() }}</span>\n }\n </div>\n }\n\n @if (variant() === 'linear') {\n <div\n [class]=\"railClasses()\"\n role=\"progressbar\"\n [attr.aria-valuemin]=\"resolvedMin()\"\n [attr.aria-valuemax]=\"resolvedMax()\"\n [attr.aria-valuenow]=\"isIndeterminate() ? null : clampedValue()\"\n [attr.aria-valuetext]=\"isIndeterminate() ? null : formattedValue()\"\n [attr.aria-busy]=\"isIndeterminate() ? true : null\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-labelledby]=\"resolvedAriaLabelledby()\"\n >\n @if (isIndeterminate()) {\n <span [class]=\"fillClasses()\"></span>\n } @else {\n <span [class]=\"fillClasses()\" [style.width.%]=\"progressRatio() * 100\"></span>\n }\n </div>\n } @else {\n <div\n [class]=\"segmentListClasses()\"\n role=\"progressbar\"\n [attr.aria-valuemin]=\"resolvedMin()\"\n [attr.aria-valuemax]=\"resolvedMax()\"\n [attr.aria-valuenow]=\"isIndeterminate() ? null : clampedValue()\"\n [attr.aria-valuetext]=\"isIndeterminate() ? null : formattedValue()\"\n [attr.aria-busy]=\"isIndeterminate() ? true : null\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-labelledby]=\"resolvedAriaLabelledby()\"\n >\n @for (i of segmentIndices(); track i) {\n <span [class]=\"segmentClassFor(i)\"></span>\n }\n </div>\n }\n `,\n})\nexport class ProgressBarComponent {\n /** Current progress value. When null or undefined, the bar renders indeterminate. Values outside `[options.min, options.max]` are clamped. Defaults to `null`. */\n readonly value = input<number | null | undefined>(null);\n\n /** Visual style of the bar. `'linear'` renders a single fill; `'segmented'` splits the rail into discrete steps. Defaults to `'linear'`. */\n readonly variant = input<ProgressBarVariant>('linear');\n\n /** Semantic color of the filled portion. Defaults to `'primary'`. Use status colors (`success`/`warning`/`error`) to reflect task outcome. */\n readonly color = input<TwColor>('primary');\n\n /** Bar thickness. `'sm'` = h-1, `'md'` = h-2, `'lg'` = h-3. Defaults to `'md'`. */\n readonly size = input<ProgressBarSize>('md');\n\n /** Visible label rendered above the bar. When set, the bar is wired to the label via `aria-labelledby`. */\n readonly label = input<string | undefined>(undefined);\n\n /** Bundles non-visual configuration: value range (`min`/`max`/`segments`), value readout (`showValue`/`formatter`), and accessibility fallbacks (`ariaLabel`/`ariaLabelledby`). Every field is optional. */\n readonly options = input<ProgressBarOptions | undefined>(undefined);\n\n /** @internal */\n readonly labelId = `tw-progress-bar-${nextId++}-label`;\n\n /** @internal */\n readonly resolvedMin = computed(() => this.options()?.min ?? 0);\n\n /** @internal */\n readonly resolvedMax = computed(() => this.options()?.max ?? 100);\n\n /** @internal */\n readonly resolvedSegments = computed(() => this.options()?.segments ?? 5);\n\n /** @internal */\n readonly resolvedShowValue = computed(() => this.options()?.showValue ?? false);\n\n /** @internal */\n readonly isIndeterminate = computed(() => {\n const v = this.value();\n return v === null || v === undefined;\n });\n\n /** @internal */\n readonly clampedValue = computed(() => {\n const v = this.value();\n if (v === null || v === undefined) return this.resolvedMin();\n const lo = this.resolvedMin();\n const hi = this.resolvedMax();\n if (v < lo) return lo;\n if (v > hi) return hi;\n return v;\n });\n\n /** @internal */\n readonly progressRatio = computed(() => {\n if (this.isIndeterminate()) return 0;\n const range = this.resolvedMax() - this.resolvedMin();\n if (range <= 0) return 0;\n return (this.clampedValue() - this.resolvedMin()) / range;\n });\n\n /** @internal */\n readonly formattedValue = computed(() => {\n if (this.isIndeterminate()) return '';\n const fmt = this.options()?.formatter ?? DEFAULT_FORMATTER;\n return fmt(this.clampedValue(), this.resolvedMax(), this.resolvedMin());\n });\n\n /** @internal */\n readonly hasHeader = computed(() => !!this.label() || this.resolvedShowValue());\n\n /** @internal */\n readonly segmentIndices = computed(() => {\n const n = Math.max(1, Math.floor(this.resolvedSegments()));\n return Array.from({ length: n }, (_, i) => i);\n });\n\n /** @internal */\n readonly resolvedAriaLabel = computed(() => {\n if (this.label()) return null;\n if (this.options()?.ariaLabelledby) return null;\n return this.options()?.ariaLabel ?? null;\n });\n\n /** @internal */\n readonly resolvedAriaLabelledby = computed(() => {\n if (this.label()) return this.labelId;\n return this.options()?.ariaLabelledby ?? null;\n });\n\n // ── tv() slot computeds ──\n\n private readonly variantResult = computed(() =>\n progressBarVariants({\n size: this.size(),\n variant: this.variant(),\n isIndeterminate: this.isIndeterminate(),\n hasHeader: this.hasHeader(),\n }),\n );\n\n readonly rootClasses = computed(() => this.variantResult().root());\n readonly headerClasses = computed(() => this.variantResult().header());\n readonly labelClasses = computed(() => this.variantResult().label());\n readonly valueTextClasses = computed(() => this.variantResult().valueText());\n readonly railClasses = computed(() => this.variantResult().rail());\n readonly fillClasses = computed(() => `${this.variantResult().fill()} ${FILL_COLORS[this.color()]}`);\n readonly segmentListClasses = computed(() => this.variantResult().segmentList());\n private readonly segmentBaseClasses = computed(() => this.variantResult().segment());\n\n /** @internal */\n segmentClassFor(index: number): string {\n const base = this.segmentBaseClasses();\n const filled = (index + 1) / this.segmentIndices().length <= this.progressRatio();\n const bg = filled ? FILL_COLORS[this.color()] : 'bg-surface-muted';\n return `${base} ${bg}`;\n }\n\n // ── Dev-mode accessible-name warning (fires once per instance) ──\n //\n // The effect is reactive on purpose: if a consumer mounts the component\n // without a label and later removes the only accessible name they had, the\n // warning still fires on the *first* render where no name is present. Once\n // it has fired, `warned` short-circuits further evaluations. Production\n // builds skip the entire effect setup because `ngDevMode` is statically\n // `false` and the constructor returns early.\n\n constructor() {\n if (typeof ngDevMode !== 'undefined' && !ngDevMode) return;\n let warned = false;\n effect(() => {\n if (warned) return;\n const opts = this.options();\n if (this.label() || opts?.ariaLabel || opts?.ariaLabelledby) return;\n // `untracked` wraps the side-effect so the console.warn / flag mutation\n // can never create a reactive subscription — defensive in case future\n // work adds signal reads inside the warning block.\n untracked(() => {\n warned = true;\n console.warn(\n 'tw-progress-bar: no accessible name provided. Supply one of `label`, `options.ariaLabel`, or `options.ariaLabelledby`.',\n );\n });\n });\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { input, model, output, inject, ElementRef, DestroyRef, forwardRef, signal, linkedSignal, computed, afterNextRender, isDevMode, ChangeDetectionStrategy, Component, contentChildren } from '@angular/core';
|
|
3
|
-
import {
|
|
4
|
-
import { NgControl, NgForm, FormGroupDirective } from '@angular/forms';
|
|
3
|
+
import { NgControl } from '@angular/forms';
|
|
5
4
|
import { FocusMonitor } from '@angular/cdk/a11y';
|
|
6
|
-
import { merge } from 'rxjs';
|
|
7
5
|
import { tv } from 'tailwind-variants';
|
|
8
|
-
import {
|
|
6
|
+
import { wireErrorState } from '@cdevhub/ngx-tw/core';
|
|
9
7
|
|
|
10
8
|
// ── Radio tv() config ────────────────────────────────────────────
|
|
11
9
|
const radioVariants = tv({
|
|
@@ -13,6 +11,25 @@ const radioVariants = tv({
|
|
|
13
11
|
// `items-start` aligns circleWrap with the top of the label container; combined with
|
|
14
12
|
// a `min-h-N` matching the label's first-line line-height on circleWrap, the circle
|
|
15
13
|
// stays centered on the first line whether the label spans one line or many.
|
|
14
|
+
//
|
|
15
|
+
// ── Row-height scale (selection cohort) ──────────────────────────────
|
|
16
|
+
// The rendered row is `max(circleWrap, label first line)`, and the host itself is the
|
|
17
|
+
// click target (`role="radio"` + `tabindex` + `(click)` all sit on the root), so the
|
|
18
|
+
// row IS the interactive target. It runs 16 / 20 / 24 / 28 / 32 px — five distinct
|
|
19
|
+
// steps, every one on the 4px baseline of docs/vertical-rhythm.md, and identical to
|
|
20
|
+
// `checkbox` and `switch`. Selection controls are glyph-scale and deliberately do NOT
|
|
21
|
+
// take the pinned 24/32/36/44/48 form-row scale (vertical-rhythm.md section 4).
|
|
22
|
+
//
|
|
23
|
+
// The row is driven by `circleWrap` min-h, NOT by the circle glyph, which keeps its own
|
|
24
|
+
// 14 / 16 / 20 / 24 / 28 scale. Driving it from circleWrap is what makes the five steps
|
|
25
|
+
// survive when no label is projected.
|
|
26
|
+
//
|
|
27
|
+
// Because centering demands `circleWrap height == label first-line leading`, the label
|
|
28
|
+
// leading carries the same 4 / 5 / 6 / 7 / 8 progression. That is forced, not chosen:
|
|
29
|
+
// the label font scale has only three steps (text-xs, text-sm, text-base), so leading
|
|
30
|
+
// is the only lever that can break the sm/md and lg/xl ties. Before this, sm and md
|
|
31
|
+
// both rendered a 20px row — a dead step. Accepted cost: `text-base leading-8` at xl is
|
|
32
|
+
// a 2.0 line ratio, looser than typographic ideal on a wrapped multi-line label.
|
|
16
33
|
root: 'inline-flex items-start gap-3 cursor-pointer select-none rounded-md focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500',
|
|
17
34
|
circleWrap: 'relative inline-flex items-center justify-center shrink-0',
|
|
18
35
|
circle: 'inline-flex items-center justify-center rounded-full border bg-surface transition-colors duration-normal motion-reduce:transition-none',
|
|
@@ -27,6 +44,12 @@ const radioVariants = tv({
|
|
|
27
44
|
xs: {
|
|
28
45
|
// `size-3.5` half-step: aligns with text-xs/leading-4 row metric.
|
|
29
46
|
circle: 'size-3.5',
|
|
47
|
+
// The dot runs 6 / 8 / 10 / 12 / 14px — it is NOT on the status-dot sub-scale of
|
|
48
|
+
// CLAUDE.md (that one is for standalone presence pips). It is a proportional inner
|
|
49
|
+
// selection mark, held at ~50% of the enclosing circle at every size, so its value
|
|
50
|
+
// is derived from `circle` rather than picked off a table. At xs, 50% of a 14px
|
|
51
|
+
// circle is 7px, which no utility expresses; `size-1.5` (6px) is the nearest step
|
|
52
|
+
// down — rounding up to 8px would leave under 3px of ring on each side.
|
|
30
53
|
dot: 'size-1.5',
|
|
31
54
|
circleWrap: 'min-h-4',
|
|
32
55
|
label: 'text-xs leading-4',
|
|
@@ -42,15 +65,18 @@ const radioVariants = tv({
|
|
|
42
65
|
md: {
|
|
43
66
|
circle: 'size-5',
|
|
44
67
|
dot: 'size-2.5',
|
|
45
|
-
|
|
46
|
-
|
|
68
|
+
// 24px row. `text-sm` is shared with sm by the documented font scale, so the
|
|
69
|
+
// leading breaks the tie: leading-6 lifts the row off sm's 20px.
|
|
70
|
+
circleWrap: 'min-h-6',
|
|
71
|
+
label: 'text-sm leading-6',
|
|
47
72
|
description: 'text-xs leading-4',
|
|
48
73
|
},
|
|
49
74
|
lg: {
|
|
50
75
|
circle: 'size-6',
|
|
51
76
|
dot: 'size-3',
|
|
52
|
-
|
|
53
|
-
|
|
77
|
+
// 28px row — leading-7 keeps the first label line the same height as circleWrap.
|
|
78
|
+
circleWrap: 'min-h-7',
|
|
79
|
+
label: 'text-base leading-7',
|
|
54
80
|
description: 'text-sm leading-5',
|
|
55
81
|
},
|
|
56
82
|
xl: {
|
|
@@ -58,10 +84,10 @@ const radioVariants = tv({
|
|
|
58
84
|
// xl density: dot uses `size-3.5` (14px) as the proportionally-sized
|
|
59
85
|
// selection mark inside the size-7 (28px) circle — exactly 50%.
|
|
60
86
|
dot: 'size-3.5',
|
|
61
|
-
//
|
|
62
|
-
// leading-
|
|
63
|
-
circleWrap: 'min-h-
|
|
64
|
-
label: 'text-base leading-
|
|
87
|
+
// 32px row. `text-base` is shared with lg, so leading-8 breaks that tie the same
|
|
88
|
+
// way leading-6 breaks sm/md. A 2.0 ratio on a wrapped label is the accepted cost.
|
|
89
|
+
circleWrap: 'min-h-8',
|
|
90
|
+
label: 'text-base leading-8',
|
|
65
91
|
description: 'text-sm leading-5',
|
|
66
92
|
},
|
|
67
93
|
},
|
|
@@ -138,7 +164,7 @@ const OUTLINE_DOT = {
|
|
|
138
164
|
let nextRadioId = 0;
|
|
139
165
|
// ── RadioComponent ───────────────────────────────────────────────
|
|
140
166
|
class RadioComponent {
|
|
141
|
-
/** The value this radio contributes when selected inside a `tw-radio-group`. Required when nested in a group; ignored when used standalone. */
|
|
167
|
+
/** The value this radio contributes when selected inside a `tw-radio-group`. Required when nested in a group; ignored when used standalone. Defaults to `undefined`. */
|
|
142
168
|
value = input(undefined, /* @ts-ignore */
|
|
143
169
|
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
144
170
|
/** Overrides the parent group's color for this radio. When undefined, inherits from the group (or defaults to `'primary'` standalone). */
|
|
@@ -165,36 +191,60 @@ class RadioComponent {
|
|
|
165
191
|
/** Optional name attribute for standalone use. Ignored when the parent group provides a name. */
|
|
166
192
|
name = input(undefined, /* @ts-ignore */
|
|
167
193
|
...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
|
|
168
|
-
/** Accessible name when no visible label is provided. Mirrored to `aria-label`. */
|
|
194
|
+
/** Accessible name when no visible label is provided. Mirrored to `aria-label`. Defaults to `undefined`. */
|
|
169
195
|
ariaLabel = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
|
|
170
|
-
/** ID of an external element that labels the radio. Mirrored to `aria-labelledby`. */
|
|
196
|
+
/** ID of an external element that labels the radio. Mirrored to `aria-labelledby`. Defaults to `undefined`. */
|
|
171
197
|
ariaLabelledby = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabelledby" } : /* istanbul ignore next */ {}), alias: 'aria-labelledby' });
|
|
172
|
-
/** ID of an external element that describes the radio. Mirrored to `aria-describedby`. */
|
|
198
|
+
/** ID of an external element that describes the radio. Mirrored to `aria-describedby`. Defaults to `undefined`. */
|
|
173
199
|
ariaDescribedby = input(undefined, { ...(ngDevMode ? { debugName: "ariaDescribedby" } : /* istanbul ignore next */ {}), alias: 'aria-describedby' });
|
|
174
|
-
/**
|
|
200
|
+
/**
|
|
201
|
+
* Two-way bound checked state. Authoritative only in standalone mode.
|
|
202
|
+
* Defaults to `false`.
|
|
203
|
+
*
|
|
204
|
+
* Standalone, the minted `checkedChange` output is the *any-change* channel:
|
|
205
|
+
* it fires both when the user selects this radio and when a bound form writes
|
|
206
|
+
* into it (`FormControl.setValue`, `ngModel`, `writeValue`). `(change)` is the
|
|
207
|
+
* *user-gesture-only* channel — bind that one for analytics or a confirmation
|
|
208
|
+
* prompt, where a programmatic write must not count as a click.
|
|
209
|
+
*
|
|
210
|
+
* Inside a `tw-radio-group` this model is inert: the group owns selection,
|
|
211
|
+
* the rendered state comes from the group's `value`, and the component never
|
|
212
|
+
* writes `checked` — so `(checkedChange)` never fires on a grouped radio.
|
|
213
|
+
* `(change)` still fires when this radio becomes the selected one. Bind the
|
|
214
|
+
* group's `[(value)]` / `(change)` instead.
|
|
215
|
+
*/
|
|
175
216
|
checked = model(false, /* @ts-ignore */
|
|
176
217
|
...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
|
|
177
218
|
/** Per-instance override of the {@link ErrorStateMatcher}. When omitted, the radio uses the `TW_ERROR_STATE_MATCHER` token's value. */
|
|
178
219
|
errorStateMatcher = input(undefined, /* @ts-ignore */
|
|
179
220
|
...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
|
|
180
|
-
/**
|
|
221
|
+
/**
|
|
222
|
+
* Fires after the checked state changes from a user interaction on this
|
|
223
|
+
* radio. In grouped mode only fires when this radio becomes the selected one.
|
|
224
|
+
* Does **not** fire when selection is updated programmatically via
|
|
225
|
+
* `writeValue` / `FormControl.setValue` / `ngModel` — standalone, bind the
|
|
226
|
+
* two-way binding's `(checkedChange)` for that, which fires on any change.
|
|
227
|
+
* (Grouped, `(checkedChange)` never fires at all; use the group's
|
|
228
|
+
* `[(value)]`.)
|
|
229
|
+
*/
|
|
181
230
|
change = output();
|
|
182
231
|
focusMonitor = inject(FocusMonitor);
|
|
183
232
|
elementRef = inject((ElementRef));
|
|
184
233
|
destroyRef = inject(DestroyRef);
|
|
185
234
|
parent = inject(forwardRef(() => RadioGroupComponent), { optional: true });
|
|
186
235
|
ngControl = inject(NgControl, { optional: true, self: true });
|
|
187
|
-
parentForm = inject(NgForm, { optional: true });
|
|
188
|
-
parentFormGroup = inject(FormGroupDirective, { optional: true });
|
|
189
|
-
defaultMatcher = inject(TW_ERROR_STATE_MATCHER);
|
|
190
236
|
onChange = () => { };
|
|
191
237
|
onTouched = () => { };
|
|
192
238
|
cvaDisabled = signal(false, /* @ts-ignore */
|
|
193
239
|
...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
240
|
+
/** @internal Shared `errorState` derivation — see `wireErrorState`. */
|
|
241
|
+
errorWiring = wireErrorState({
|
|
242
|
+
ngControl: () => this.ngControl,
|
|
243
|
+
matcher: () => this.errorStateMatcher(),
|
|
244
|
+
// A radio inside a group inherits the group's error state verbatim,
|
|
245
|
+
// including `false`; standalone it falls through to the matcher.
|
|
246
|
+
errorStateOverride: () => (this.parent ? this.parent.errorState() : undefined),
|
|
247
|
+
});
|
|
198
248
|
uid = nextRadioId++;
|
|
199
249
|
hostId = `tw-radio-${this.uid}`;
|
|
200
250
|
labelId = `${this.hostId}-label`;
|
|
@@ -224,17 +274,7 @@ class RadioComponent {
|
|
|
224
274
|
isDisabled = computed(() => this.disabled() || this.cvaDisabled() || (this.parent?.isDisabled() ?? false), /* @ts-ignore */
|
|
225
275
|
...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
226
276
|
/** Whether the radio is in an error state per the configured `ErrorStateMatcher`. Inherits from the parent group when wrapped; falls back to its own NgControl when standalone. */
|
|
227
|
-
errorState =
|
|
228
|
-
this._ngControlRev();
|
|
229
|
-
this._formSubmitRev();
|
|
230
|
-
if (this.parent)
|
|
231
|
-
return this.parent.errorState();
|
|
232
|
-
const matcher = this.errorStateMatcher() ?? this.defaultMatcher;
|
|
233
|
-
const form = this.parentFormGroup ??
|
|
234
|
-
this.parentForm;
|
|
235
|
-
return matcher.isErrorState(this.ngControl?.control ?? null, form);
|
|
236
|
-
}, /* @ts-ignore */
|
|
237
|
-
...(ngDevMode ? [{ debugName: "errorState" }] : /* istanbul ignore next */ []));
|
|
277
|
+
errorState = this.errorWiring.errorState;
|
|
238
278
|
/** Roving tabindex: in a group, only the selected (or first enabled if none selected) radio is focusable. Standalone radios are always focusable unless disabled. */
|
|
239
279
|
isFocusable = computed(() => {
|
|
240
280
|
if (this.isDisabled())
|
|
@@ -336,7 +376,8 @@ class RadioComponent {
|
|
|
336
376
|
this.internalChecked.set(true);
|
|
337
377
|
this.checked.set(true);
|
|
338
378
|
this.onChange(true);
|
|
339
|
-
|
|
379
|
+
// Deliberately NOT `onTouched()` — that is the blur notification, and
|
|
380
|
+
// `onBlur()` owns it. See the note on `RadioGroupComponent.selectValue`.
|
|
340
381
|
this.change.emit(true);
|
|
341
382
|
}
|
|
342
383
|
}
|
|
@@ -357,7 +398,7 @@ class RadioComponent {
|
|
|
357
398
|
else {
|
|
358
399
|
this.onTouched();
|
|
359
400
|
// Blur flips `touched` on `NgControl`; bump the revision so `errorState` recomputes.
|
|
360
|
-
this.
|
|
401
|
+
this.errorWiring.bump();
|
|
361
402
|
}
|
|
362
403
|
}
|
|
363
404
|
/** Focuses the host element. Required for keyboard navigation from the group. */
|
|
@@ -386,21 +427,7 @@ class RadioComponent {
|
|
|
386
427
|
});
|
|
387
428
|
// Track validator status/value changes on a bound NgControl so `errorState`
|
|
388
429
|
// re-evaluates when status flips. Mirrors `input.ts`'s pattern.
|
|
389
|
-
|
|
390
|
-
if (ctrl) {
|
|
391
|
-
const streams = [ctrl.statusChanges, ctrl.valueChanges].filter((s) => !!s);
|
|
392
|
-
if (streams.length) {
|
|
393
|
-
merge(...streams)
|
|
394
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
395
|
-
.subscribe(() => this._ngControlRev.update((n) => n + 1));
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
const submit = this.parentFormGroup?.ngSubmit ?? this.parentForm?.ngSubmit;
|
|
399
|
-
if (submit) {
|
|
400
|
-
submit
|
|
401
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
402
|
-
.subscribe(() => this._formSubmitRev.update((n) => n + 1));
|
|
403
|
-
}
|
|
430
|
+
this.errorWiring.connect();
|
|
404
431
|
}
|
|
405
432
|
hasAccessibleNameHint() {
|
|
406
433
|
if (this.label() || this.ariaLabel() || this.ariaLabelledby())
|
|
@@ -527,31 +554,61 @@ class RadioGroupComponent {
|
|
|
527
554
|
/** Visual style of the selected indicator. `'solid'` fills the dot with the color against a colored ring; `'outline'` keeps a transparent fill with a colored ring and colored dot. Propagated to children unless a child overrides it. Defaults to `'solid'`. */
|
|
528
555
|
variant = input('solid', /* @ts-ignore */
|
|
529
556
|
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
530
|
-
/** Layout direction of the group.
|
|
557
|
+
/** Layout direction of the group. Sets `aria-orientation` and the visual axis; both arrow-key pairs navigate in either orientation, per WAI-ARIA APG. Defaults to `'vertical'`. */
|
|
531
558
|
orientation = input('vertical', /* @ts-ignore */
|
|
532
559
|
...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
|
|
533
560
|
/** When true, disables every radio in the group and blocks keyboard interaction. Defaults to `false`. */
|
|
534
561
|
disabled = input(false, /* @ts-ignore */
|
|
535
562
|
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
536
|
-
/** When true, sets `aria-required="true"` on the group for assistive tech. Defaults to `false`. */
|
|
537
|
-
|
|
538
|
-
|
|
563
|
+
/** When true, sets `aria-required="true"` on the group for assistive tech. Also inferred from `Validators.required` on a bound control, so a reactive/template-driven form does not have to state it twice. Defaults to `false`. */
|
|
564
|
+
requiredInput = input(false, { ...(ngDevMode ? { debugName: "requiredInput" } : /* istanbul ignore next */ {}), alias: 'required' });
|
|
565
|
+
/**
|
|
566
|
+
* @internal Shared `errorState` / `required` / `errors` derivation — see
|
|
567
|
+
* `wireErrorState`. Declared here, above `required`, because class field
|
|
568
|
+
* initializers run in declaration order.
|
|
569
|
+
*/
|
|
570
|
+
errorWiring = wireErrorState({
|
|
571
|
+
ngControl: () => this.ngControl,
|
|
572
|
+
matcher: () => this.errorStateMatcher(),
|
|
573
|
+
required: () => this.requiredInput(),
|
|
574
|
+
});
|
|
575
|
+
/**
|
|
576
|
+
* @internal Resolved required state: the `required` input OR'd with
|
|
577
|
+
* `Validators.required` on a bound `NgControl`.
|
|
578
|
+
*/
|
|
579
|
+
required = this.errorWiring.required;
|
|
539
580
|
/** Optional form-association name. Propagated to each child radio's host `name` attribute so standard HTML form semantics still apply. */
|
|
540
581
|
name = input(undefined, /* @ts-ignore */
|
|
541
582
|
...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
|
|
542
|
-
/** Accessible name for the group when no visible label is provided. Mirrored to `aria-label`. */
|
|
583
|
+
/** Accessible name for the group when no visible label is provided. Mirrored to `aria-label`. Defaults to `undefined`. */
|
|
543
584
|
ariaLabel = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
|
|
544
|
-
/** ID of an external element that labels the group. Mirrored to `aria-labelledby`. */
|
|
585
|
+
/** ID of an external element that labels the group. Mirrored to `aria-labelledby`. Defaults to `undefined`. */
|
|
545
586
|
ariaLabelledby = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabelledby" } : /* istanbul ignore next */ {}), alias: 'aria-labelledby' });
|
|
546
|
-
/** ID of an external element that describes the group. Mirrored to `aria-describedby`. */
|
|
587
|
+
/** ID of an external element that describes the group. Mirrored to `aria-describedby`. Defaults to `undefined`. */
|
|
547
588
|
ariaDescribedby = input(undefined, { ...(ngDevMode ? { debugName: "ariaDescribedby" } : /* istanbul ignore next */ {}), alias: 'aria-describedby' });
|
|
548
|
-
/**
|
|
589
|
+
/**
|
|
590
|
+
* Two-way bound selected value. `null` means no selection. Defaults to
|
|
591
|
+
* `null`.
|
|
592
|
+
*
|
|
593
|
+
* The two-way binding mints a `valueChange` output. That output is the
|
|
594
|
+
* *any-change* channel: it fires when the user picks a radio **and** when a
|
|
595
|
+
* bound form writes into the group (`FormControl.setValue`, `ngModel`,
|
|
596
|
+
* `writeValue`). `(change)` is the *user-gesture-only* channel — bind that
|
|
597
|
+
* one for analytics or a confirmation prompt, where a programmatic write must
|
|
598
|
+
* not count as a click. Writing back into the same form from a
|
|
599
|
+
* `(valueChange)` handler will echo.
|
|
600
|
+
*/
|
|
549
601
|
value = model(null, /* @ts-ignore */
|
|
550
602
|
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
551
603
|
/** Per-instance override of the {@link ErrorStateMatcher}. When omitted, the group uses the `TW_ERROR_STATE_MATCHER` token's value. */
|
|
552
604
|
errorStateMatcher = input(undefined, /* @ts-ignore */
|
|
553
605
|
...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
|
|
554
|
-
/**
|
|
606
|
+
/**
|
|
607
|
+
* Fires after the selected value changes from a user interaction. Does
|
|
608
|
+
* **not** fire when the value is updated programmatically via `writeValue` /
|
|
609
|
+
* `FormControl.setValue` / `ngModel` — for those, bind the two-way binding's
|
|
610
|
+
* `(valueChange)` instead, which fires on any change.
|
|
611
|
+
*/
|
|
555
612
|
change = output();
|
|
556
613
|
/** @internal Child radios discovered via content projection. */
|
|
557
614
|
radios = contentChildren(RadioComponent, { ...(ngDevMode ? { debugName: "radios" } : /* istanbul ignore next */ {}), descendants: true });
|
|
@@ -559,31 +616,16 @@ class RadioGroupComponent {
|
|
|
559
616
|
elementRef = inject((ElementRef));
|
|
560
617
|
destroyRef = inject(DestroyRef);
|
|
561
618
|
ngControl = inject(NgControl, { optional: true, self: true });
|
|
562
|
-
parentForm = inject(NgForm, { optional: true });
|
|
563
|
-
parentFormGroup = inject(FormGroupDirective, { optional: true });
|
|
564
|
-
defaultMatcher = inject(TW_ERROR_STATE_MATCHER);
|
|
565
619
|
hostId = `tw-radio-group-${nextGroupId++}`;
|
|
566
620
|
onChange = () => { };
|
|
567
621
|
onTouched = () => { };
|
|
568
622
|
cvaDisabled = signal(false, /* @ts-ignore */
|
|
569
623
|
...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
|
|
570
|
-
_ngControlRev = signal(0, /* @ts-ignore */
|
|
571
|
-
...(ngDevMode ? [{ debugName: "_ngControlRev" }] : /* istanbul ignore next */ []));
|
|
572
|
-
_formSubmitRev = signal(0, /* @ts-ignore */
|
|
573
|
-
...(ngDevMode ? [{ debugName: "_formSubmitRev" }] : /* istanbul ignore next */ []));
|
|
574
624
|
/** Effective disabled state — input OR CVA disabled. */
|
|
575
625
|
isDisabled = computed(() => this.disabled() || this.cvaDisabled(), /* @ts-ignore */
|
|
576
626
|
...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
577
627
|
/** Group-level error state per the configured `ErrorStateMatcher`. Reads the bound `NgControl.invalid` through the matcher; child radios inherit from this when wrapped in the group. */
|
|
578
|
-
errorState =
|
|
579
|
-
this._ngControlRev();
|
|
580
|
-
this._formSubmitRev();
|
|
581
|
-
const matcher = this.errorStateMatcher() ?? this.defaultMatcher;
|
|
582
|
-
const form = this.parentFormGroup ??
|
|
583
|
-
this.parentForm;
|
|
584
|
-
return matcher.isErrorState(this.ngControl?.control ?? null, form);
|
|
585
|
-
}, /* @ts-ignore */
|
|
586
|
-
...(ngDevMode ? [{ debugName: "errorState" }] : /* istanbul ignore next */ []));
|
|
628
|
+
errorState = this.errorWiring.errorState;
|
|
587
629
|
activeValue = linkedSignal(() => this.value(), /* @ts-ignore */
|
|
588
630
|
...(ngDevMode ? [{ debugName: "activeValue" }] : /* istanbul ignore next */ []));
|
|
589
631
|
variantResult = computed(() => radioGroupVariants({
|
|
@@ -613,20 +655,25 @@ class RadioGroupComponent {
|
|
|
613
655
|
return;
|
|
614
656
|
const typed = next;
|
|
615
657
|
if (typed === this.activeValue()) {
|
|
616
|
-
this.onTouched();
|
|
617
658
|
return;
|
|
618
659
|
}
|
|
619
660
|
this.activeValue.set(typed);
|
|
620
661
|
this.value.set(typed);
|
|
621
662
|
this.onChange(typed);
|
|
622
|
-
|
|
663
|
+
// Deliberately NOT `onTouched()`. Angular's CVA contract registers
|
|
664
|
+
// `onTouched` as the BLUR notification; calling it on selection flipped
|
|
665
|
+
// `touched` with no blur, so a consumer staging error display on `touched`
|
|
666
|
+
// got different behaviour from `tw-radio-group` than from `tw-slider` /
|
|
667
|
+
// `tw-input`. `notifyTouched()` — reached from a child radio's real blur —
|
|
668
|
+
// is the only place that fires it.
|
|
623
669
|
this.change.emit(typed);
|
|
624
670
|
}
|
|
625
671
|
/** @internal Called by child radios on blur — propagates to CVA onTouched and bumps the errorState revision so a blur on a child radio refreshes the group's `aria-invalid`. */
|
|
626
672
|
notifyTouched() {
|
|
627
673
|
this.onTouched();
|
|
628
|
-
this.
|
|
674
|
+
this.errorWiring.bump();
|
|
629
675
|
}
|
|
676
|
+
/** @internal Handles roving-focus keyboard navigation (Arrow keys, Space) across the radios in the group. */
|
|
630
677
|
onKeydown(event) {
|
|
631
678
|
if (this.isDisabled())
|
|
632
679
|
return;
|
|
@@ -710,24 +757,10 @@ class RadioGroupComponent {
|
|
|
710
757
|
// NgControl's `control` is set by the parent FormControl* directive before
|
|
711
758
|
// children's `ngOnInit`. Subscribe here so errorState reacts to status/value
|
|
712
759
|
// changes on the bound control.
|
|
713
|
-
|
|
714
|
-
if (ctrl) {
|
|
715
|
-
const streams = [ctrl.statusChanges, ctrl.valueChanges].filter((s) => !!s);
|
|
716
|
-
if (streams.length) {
|
|
717
|
-
merge(...streams)
|
|
718
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
719
|
-
.subscribe(() => this._ngControlRev.update((n) => n + 1));
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
const submit = this.parentFormGroup?.ngSubmit ?? this.parentForm?.ngSubmit;
|
|
723
|
-
if (submit) {
|
|
724
|
-
submit
|
|
725
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
726
|
-
.subscribe(() => this._formSubmitRev.update((n) => n + 1));
|
|
727
|
-
}
|
|
760
|
+
this.errorWiring.connect();
|
|
728
761
|
}
|
|
729
762
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: RadioGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
730
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.7", type: RadioGroupComponent, isStandalone: true, selector: "tw-radio-group", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null },
|
|
763
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.7", type: RadioGroupComponent, isStandalone: true, selector: "tw-radio-group", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, requiredInput: { classPropertyName: "requiredInput", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedby: { classPropertyName: "ariaDescribedby", publicName: "aria-describedby", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, errorStateMatcher: { classPropertyName: "errorStateMatcher", publicName: "errorStateMatcher", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", change: "change" }, host: { attributes: { "role": "radiogroup" }, listeners: { "keydown": "onKeydown($event)" }, properties: { "id": "hostId", "class": "rootClasses()", "attr.aria-orientation": "orientation()", "attr.aria-disabled": "isDisabled() || null", "attr.aria-required": "required() || null", "attr.aria-invalid": "errorState() || null", "attr.aria-label": "ariaLabel() || null", "attr.aria-labelledby": "ariaLabelledby() || null", "attr.aria-describedby": "ariaDescribedby() || null" } }, queries: [{ propertyName: "radios", predicate: RadioComponent, descendants: true, isSignal: true }], ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
731
764
|
}
|
|
732
765
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: RadioGroupComponent, decorators: [{
|
|
733
766
|
type: Component,
|
|
@@ -749,7 +782,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
749
782
|
'(keydown)': 'onKeydown($event)',
|
|
750
783
|
},
|
|
751
784
|
}]
|
|
752
|
-
}], ctorParameters: () => [], propDecorators: { color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }],
|
|
785
|
+
}], ctorParameters: () => [], propDecorators: { color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], requiredInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-labelledby", required: false }] }], ariaDescribedby: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-describedby", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], errorStateMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorStateMatcher", required: false }] }], change: [{ type: i0.Output, args: ["change"] }], radios: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => RadioComponent), { ...{ descendants: true }, isSignal: true }] }] } });
|
|
753
786
|
|
|
754
787
|
/**
|
|
755
788
|
* Generated bundle index. Do not edit.
|