forty-cdk 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/accordion/README.md +122 -0
- package/aspect-ratio/README.md +76 -0
- package/avatar/README.md +100 -0
- package/breadcrumbs/README.md +49 -0
- package/breakpoints/README.md +81 -0
- package/button/README.md +49 -0
- package/calendar/README.md +458 -0
- package/carousel/README.md +358 -0
- package/checkbox/README.md +146 -0
- package/combobox/README.md +535 -0
- package/context-menu/README.md +139 -0
- package/date-field/README.md +184 -0
- package/date-picker/README.md +338 -0
- package/dialog/README.md +388 -0
- package/disclosure/README.md +114 -0
- package/drag-drop/README.md +359 -0
- package/drawer/README.md +560 -0
- package/dropdown-menu/README.md +176 -0
- package/fesm2022/forty-cdk-accordion.mjs +348 -0
- package/fesm2022/forty-cdk-accordion.mjs.map +1 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs +74 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs.map +1 -0
- package/fesm2022/forty-cdk-avatar.mjs +308 -0
- package/fesm2022/forty-cdk-avatar.mjs.map +1 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs +125 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -0
- package/fesm2022/forty-cdk-breakpoints.mjs +117 -0
- package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -0
- package/fesm2022/forty-cdk-button.mjs +134 -0
- package/fesm2022/forty-cdk-button.mjs.map +1 -0
- package/fesm2022/forty-cdk-calendar.mjs +2034 -0
- package/fesm2022/forty-cdk-calendar.mjs.map +1 -0
- package/fesm2022/forty-cdk-carousel.mjs +968 -0
- package/fesm2022/forty-cdk-carousel.mjs.map +1 -0
- package/fesm2022/forty-cdk-checkbox.mjs +226 -0
- package/fesm2022/forty-cdk-checkbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-combobox.mjs +2596 -0
- package/fesm2022/forty-cdk-combobox.mjs.map +1 -0
- package/fesm2022/forty-cdk-context-menu.mjs +413 -0
- package/fesm2022/forty-cdk-context-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-core.mjs +9022 -0
- package/fesm2022/forty-cdk-core.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-field.mjs +744 -0
- package/fesm2022/forty-cdk-date-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-picker.mjs +1011 -0
- package/fesm2022/forty-cdk-date-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-dialog.mjs +707 -0
- package/fesm2022/forty-cdk-dialog.mjs.map +1 -0
- package/fesm2022/forty-cdk-disclosure.mjs +190 -0
- package/fesm2022/forty-cdk-disclosure.mjs.map +1 -0
- package/fesm2022/forty-cdk-drag-drop.mjs +1180 -0
- package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -0
- package/fesm2022/forty-cdk-drawer.mjs +1641 -0
- package/fesm2022/forty-cdk-drawer.mjs.map +1 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs +350 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-field.mjs +425 -0
- package/fesm2022/forty-cdk-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-fieldset.mjs +164 -0
- package/fesm2022/forty-cdk-fieldset.mjs.map +1 -0
- package/fesm2022/forty-cdk-file-upload.mjs +221 -0
- package/fesm2022/forty-cdk-file-upload.mjs.map +1 -0
- package/fesm2022/forty-cdk-hover-card.mjs +496 -0
- package/fesm2022/forty-cdk-hover-card.mjs.map +1 -0
- package/fesm2022/forty-cdk-input.mjs +274 -0
- package/fesm2022/forty-cdk-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-internationalized-date.mjs +1 -1
- package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
- package/fesm2022/forty-cdk-listbox.mjs +1279 -0
- package/fesm2022/forty-cdk-listbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-menu.mjs +1439 -0
- package/fesm2022/forty-cdk-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-menubar.mjs +787 -0
- package/fesm2022/forty-cdk-menubar.mjs.map +1 -0
- package/fesm2022/forty-cdk-meter.mjs +211 -0
- package/fesm2022/forty-cdk-meter.mjs.map +1 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs +1145 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-number-input.mjs +559 -0
- package/fesm2022/forty-cdk-number-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-otp-input.mjs +527 -0
- package/fesm2022/forty-cdk-otp-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-pagination.mjs +323 -0
- package/fesm2022/forty-cdk-pagination.mjs.map +1 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs +297 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -0
- package/fesm2022/forty-cdk-popover.mjs +698 -0
- package/fesm2022/forty-cdk-popover.mjs.map +1 -0
- package/fesm2022/forty-cdk-progress.mjs +226 -0
- package/fesm2022/forty-cdk-progress.mjs.map +1 -0
- package/fesm2022/forty-cdk-radio-group.mjs +378 -0
- package/fesm2022/forty-cdk-radio-group.mjs.map +1 -0
- package/fesm2022/forty-cdk-scroll-area.mjs +640 -0
- package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -0
- package/fesm2022/forty-cdk-search.mjs +205 -0
- package/fesm2022/forty-cdk-search.mjs.map +1 -0
- package/fesm2022/forty-cdk-select.mjs +1661 -0
- package/fesm2022/forty-cdk-select.mjs.map +1 -0
- package/fesm2022/forty-cdk-separator.mjs +82 -0
- package/fesm2022/forty-cdk-separator.mjs.map +1 -0
- package/fesm2022/forty-cdk-signal-forms.mjs +97 -0
- package/fesm2022/forty-cdk-signal-forms.mjs.map +1 -0
- package/fesm2022/forty-cdk-slider.mjs +803 -0
- package/fesm2022/forty-cdk-slider.mjs.map +1 -0
- package/fesm2022/forty-cdk-stepper.mjs +886 -0
- package/fesm2022/forty-cdk-stepper.mjs.map +1 -0
- package/fesm2022/forty-cdk-switch.mjs +137 -0
- package/fesm2022/forty-cdk-switch.mjs.map +1 -0
- package/fesm2022/forty-cdk-table.mjs +1518 -0
- package/fesm2022/forty-cdk-table.mjs.map +1 -0
- package/fesm2022/forty-cdk-tabs.mjs +400 -0
- package/fesm2022/forty-cdk-tabs.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-field.mjs +593 -0
- package/fesm2022/forty-cdk-time-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-picker.mjs +1013 -0
- package/fesm2022/forty-cdk-time-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-toast.mjs +1153 -0
- package/fesm2022/forty-cdk-toast.mjs.map +1 -0
- package/fesm2022/forty-cdk-toggle.mjs +516 -0
- package/fesm2022/forty-cdk-toggle.mjs.map +1 -0
- package/fesm2022/forty-cdk-toolbar.mjs +374 -0
- package/fesm2022/forty-cdk-toolbar.mjs.map +1 -0
- package/fesm2022/forty-cdk-tooltip.mjs +672 -0
- package/fesm2022/forty-cdk-tooltip.mjs.map +1 -0
- package/fesm2022/forty-cdk-tree.mjs +2007 -0
- package/fesm2022/forty-cdk-tree.mjs.map +1 -0
- package/fesm2022/forty-cdk-virtualization.mjs +1 -1
- package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
- package/fesm2022/forty-cdk.mjs +0 -43310
- package/fesm2022/forty-cdk.mjs.map +1 -1
- package/field/README.md +97 -0
- package/fieldset/README.md +86 -0
- package/file-upload/README.md +73 -0
- package/hover-card/README.md +171 -0
- package/input/README.md +156 -0
- package/listbox/README.md +424 -0
- package/menu/README.md +181 -0
- package/menubar/README.md +140 -0
- package/meter/README.md +128 -0
- package/navigation-menu/README.md +253 -0
- package/number-input/README.md +171 -0
- package/otp-input/README.md +198 -0
- package/package.json +213 -1
- package/pagination/README.md +61 -0
- package/pane-resizer/README.md +136 -0
- package/popover/README.md +262 -0
- package/progress/README.md +115 -0
- package/radio-group/README.md +129 -0
- package/scroll-area/README.md +184 -0
- package/search/README.md +42 -0
- package/select/README.md +488 -0
- package/separator/README.md +84 -0
- package/signal-forms/README.md +72 -0
- package/slider/README.md +152 -0
- package/stepper/README.md +292 -0
- package/switch/README.md +116 -0
- package/table/README.md +769 -0
- package/tabs/README.md +130 -0
- package/time-field/README.md +157 -0
- package/time-picker/README.md +172 -0
- package/toast/README.md +398 -0
- package/toggle/README.md +224 -0
- package/toolbar/README.md +109 -0
- package/tooltip/README.md +274 -0
- package/tree/README.md +708 -0
- package/types/forty-cdk-accordion.d.ts +242 -0
- package/types/forty-cdk-aspect-ratio.d.ts +59 -0
- package/types/forty-cdk-avatar.d.ts +133 -0
- package/types/forty-cdk-breadcrumbs.d.ts +92 -0
- package/types/forty-cdk-breakpoints.d.ts +141 -0
- package/types/forty-cdk-button.d.ts +80 -0
- package/types/forty-cdk-calendar.d.ts +914 -0
- package/types/forty-cdk-carousel.d.ts +530 -0
- package/types/forty-cdk-checkbox.d.ts +141 -0
- package/types/forty-cdk-combobox.d.ts +1259 -0
- package/types/forty-cdk-context-menu.d.ts +313 -0
- package/types/forty-cdk-core.d.ts +5774 -0
- package/types/forty-cdk-date-field.d.ts +307 -0
- package/types/forty-cdk-date-picker.d.ts +622 -0
- package/types/forty-cdk-dialog.d.ts +546 -0
- package/types/forty-cdk-disclosure.d.ts +127 -0
- package/types/forty-cdk-drag-drop.d.ts +456 -0
- package/types/forty-cdk-drawer.d.ts +871 -0
- package/types/forty-cdk-dropdown-menu.d.ts +242 -0
- package/types/forty-cdk-field.d.ts +236 -0
- package/types/forty-cdk-fieldset.d.ts +119 -0
- package/types/forty-cdk-file-upload.d.ts +124 -0
- package/types/forty-cdk-hover-card.d.ts +320 -0
- package/types/forty-cdk-input.d.ts +169 -0
- package/types/forty-cdk-internationalized-date.d.ts +1 -1
- package/types/forty-cdk-listbox.d.ts +513 -0
- package/types/forty-cdk-menu.d.ts +629 -0
- package/types/forty-cdk-menubar.d.ts +451 -0
- package/types/forty-cdk-meter.d.ts +122 -0
- package/types/forty-cdk-navigation-menu.d.ts +514 -0
- package/types/forty-cdk-number-input.d.ts +319 -0
- package/types/forty-cdk-otp-input.d.ts +248 -0
- package/types/forty-cdk-pagination.d.ts +214 -0
- package/types/forty-cdk-pane-resizer.d.ts +145 -0
- package/types/forty-cdk-popover.d.ts +509 -0
- package/types/forty-cdk-progress.d.ts +143 -0
- package/types/forty-cdk-radio-group.d.ts +222 -0
- package/types/forty-cdk-scroll-area.d.ts +258 -0
- package/types/forty-cdk-search.d.ts +142 -0
- package/types/forty-cdk-select.d.ts +899 -0
- package/types/forty-cdk-separator.d.ts +59 -0
- package/types/forty-cdk-signal-forms.d.ts +58 -0
- package/types/forty-cdk-slider.d.ts +379 -0
- package/types/forty-cdk-stepper.d.ts +650 -0
- package/types/forty-cdk-switch.d.ts +87 -0
- package/types/forty-cdk-table.d.ts +723 -0
- package/types/forty-cdk-tabs.d.ts +235 -0
- package/types/forty-cdk-time-field.d.ts +307 -0
- package/types/forty-cdk-time-picker.d.ts +578 -0
- package/types/forty-cdk-toast.d.ts +598 -0
- package/types/forty-cdk-toggle.d.ts +310 -0
- package/types/forty-cdk-toolbar.d.ts +217 -0
- package/types/forty-cdk-tooltip.d.ts +436 -0
- package/types/forty-cdk-tree.d.ts +688 -0
- package/types/forty-cdk.d.ts +1 -19743
|
@@ -0,0 +1,803 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, ElementRef, DOCUMENT, model, input, computed, booleanAttribute, output, signal, DestroyRef, Directive, numberAttribute } from '@angular/core';
|
|
3
|
+
import { createDefaults, FormUiControlBase, injectTextDirection, Collection, injectHiddenInput, snapToStep, registerHandle } from 'forty-cdk/core';
|
|
4
|
+
|
|
5
|
+
const FOR_SLIDER_CONTEXT = new InjectionToken('FOR_SLIDER_CONTEXT');
|
|
6
|
+
function injectSliderContext(piece) {
|
|
7
|
+
const ctx = inject(FOR_SLIDER_CONTEXT, { optional: true });
|
|
8
|
+
if (!ctx) {
|
|
9
|
+
throw new Error(`[forty-cdk/slider] ${piece} must be used inside a [forSlider] element.`);
|
|
10
|
+
}
|
|
11
|
+
return ctx;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Library fallback for slider defaults, read at the root injector when no
|
|
16
|
+
* consumer has called `provideForSliderDefaults`. Exported for the shared defaults
|
|
17
|
+
* contract spec; not re-exported from the primitive's public entry.
|
|
18
|
+
*/
|
|
19
|
+
const FOR_SLIDER_FALLBACK_DEFAULTS = {
|
|
20
|
+
largeStep: 10,
|
|
21
|
+
};
|
|
22
|
+
const { token, provideDefaults } = createDefaults('FOR_SLIDER_DEFAULTS', FOR_SLIDER_FALLBACK_DEFAULTS);
|
|
23
|
+
/** Token holding the resolved slider defaults for the current scope. */
|
|
24
|
+
const FOR_SLIDER_DEFAULTS = token;
|
|
25
|
+
/**
|
|
26
|
+
* Configures forty-cdk slider defaults for this injector scope. Partial
|
|
27
|
+
* overrides inherit unspecified keys from the parent scope (or library
|
|
28
|
+
* defaults at the root).
|
|
29
|
+
*/
|
|
30
|
+
function provideForSliderDefaults(defaults = {}) {
|
|
31
|
+
return provideDefaults(defaults);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Headless implementation of the [WAI-ARIA Slider pattern](https://www.w3.org/WAI/ARIA/apg/patterns/slider/)
|
|
36
|
+
* (single thumb) and [Slider (Multi-Thumb)](https://www.w3.org/WAI/ARIA/apg/patterns/slider-multi-thumb/)
|
|
37
|
+
* (range / N thumbs). Implements `FormValueControl<readonly number[]>` from
|
|
38
|
+
* `@angular/forms/signals` for `[formField]` auto-wiring.
|
|
39
|
+
*
|
|
40
|
+
* Selection is always modeled as `readonly number[]`:
|
|
41
|
+
* - 1 entry → single-thumb slider.
|
|
42
|
+
* - 2 entries → range slider.
|
|
43
|
+
* - N entries → multi-thumb slider.
|
|
44
|
+
*
|
|
45
|
+
* Values are kept clamped to `[min, max]` and snapped to `step` increments.
|
|
46
|
+
* In multi-thumb mode they're constrained between neighbors so thumbs can't
|
|
47
|
+
* cross (`minStepsBetweenThumbs` forces a minimum gap, in step units).
|
|
48
|
+
*
|
|
49
|
+
* The `model()` change emitter (`(valueChange)`) fires only on internal
|
|
50
|
+
* updates (drag, keyboard, track click), never on consumer writes via
|
|
51
|
+
* `[(value)]` — observe transitions without binding back.
|
|
52
|
+
*
|
|
53
|
+
* For trailing-edge work (network calls, undo entries) bind `(valueCommit)`
|
|
54
|
+
* instead — it fires once at the end of an interaction with the final value
|
|
55
|
+
* array, never per drag step.
|
|
56
|
+
*/
|
|
57
|
+
class ForSlider extends FormUiControlBase {
|
|
58
|
+
#host = inject(ElementRef);
|
|
59
|
+
#document = inject(DOCUMENT);
|
|
60
|
+
#defaults = inject(FOR_SLIDER_DEFAULTS);
|
|
61
|
+
/**
|
|
62
|
+
* Two-way bindable. Selected values, one per thumb. Single-thumb sliders
|
|
63
|
+
* keep one entry, range sliders two, multi-thumb N. Values must be sorted
|
|
64
|
+
* ascending for multi-thumb; thumb-by-thumb clamping keeps the order
|
|
65
|
+
* during interaction. Defaults to `[0]`; set an explicit `[(value)]`
|
|
66
|
+
* when `min` is non-zero.
|
|
67
|
+
*/
|
|
68
|
+
value = model([0], /* @ts-ignore */
|
|
69
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
70
|
+
/**
|
|
71
|
+
* Minimum value. Falls back to `0` internally. A slider's bounds are scalar
|
|
72
|
+
* by the ARIA slider pattern, but since Signal Forms v22 `FormUiControl.min`
|
|
73
|
+
* is typed `NonNullable<TValue>` — an array for this control — so `min` and
|
|
74
|
+
* `max` are excluded from the `FormValueControl` `implements` clause, and
|
|
75
|
+
* the transform widens the write type to satisfy the `[formField]` template
|
|
76
|
+
* type-check (which pushes `readonly number[] | undefined`). The form never
|
|
77
|
+
* produces a `min`/`max` state for an array-valued field at runtime (the
|
|
78
|
+
* `min()`/`max()` validators only apply to numeric fields), so non-number
|
|
79
|
+
* writes normalize to `undefined`.
|
|
80
|
+
*/
|
|
81
|
+
min = input(0, { ...(ngDevMode ? { debugName: "min" } : /* istanbul ignore next */ {}), transform: (value) => typeof value === 'number' ? value : undefined });
|
|
82
|
+
/**
|
|
83
|
+
* Maximum value. Falls back to `100` internally. Excluded from the
|
|
84
|
+
* `FormValueControl` `implements` clause and write-type-widened — see
|
|
85
|
+
* {@link min}.
|
|
86
|
+
*/
|
|
87
|
+
max = input(100, { ...(ngDevMode ? { debugName: "max" } : /* istanbul ignore next */ {}), transform: (value) => typeof value === 'number' ? value : undefined });
|
|
88
|
+
/**
|
|
89
|
+
* Increment values snap to. Fractional steps (e.g. `0.1`) are supported: the
|
|
90
|
+
* snapped value is rounded to the step's decimal precision so float noise
|
|
91
|
+
* (`0.1 * 3`) can't spuriously emit `valueCommit` or leak into
|
|
92
|
+
* `aria-valuenow`.
|
|
93
|
+
*/
|
|
94
|
+
step = input(1, /* @ts-ignore */
|
|
95
|
+
...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
|
|
96
|
+
/**
|
|
97
|
+
* Step used for PageUp / PageDown. Defaults to 10× `step`. The default is
|
|
98
|
+
* read from `provideForSliderDefaults` for the surrounding scope.
|
|
99
|
+
*/
|
|
100
|
+
largeStep = input(this.#defaults.largeStep, /* @ts-ignore */
|
|
101
|
+
...(ngDevMode ? [{ debugName: "largeStep" }] : /* istanbul ignore next */ []));
|
|
102
|
+
/** Effective minimum (defaults `0` when input is unset). Exposed to children via context. */
|
|
103
|
+
minValue = computed(() => this.min() ?? 0, /* @ts-ignore */
|
|
104
|
+
...(ngDevMode ? [{ debugName: "minValue" }] : /* istanbul ignore next */ []));
|
|
105
|
+
/** Effective maximum (defaults `100`). Exposed to children via context. */
|
|
106
|
+
maxValue = computed(() => this.max() ?? 100, /* @ts-ignore */
|
|
107
|
+
...(ngDevMode ? [{ debugName: "maxValue" }] : /* istanbul ignore next */ []));
|
|
108
|
+
orientation = input('horizontal', /* @ts-ignore */
|
|
109
|
+
...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
|
|
110
|
+
/**
|
|
111
|
+
* Writing direction. When unset (default `null`), the inherited ambient
|
|
112
|
+
* direction is resolved from the nearest ancestor carrying a `dir` attribute
|
|
113
|
+
* (or `<html dir>`), defaulting to `'ltr'`. An explicit `[dir]` always wins.
|
|
114
|
+
* The resolved value is reflected to the host `dir` attribute and flips the
|
|
115
|
+
* horizontal increase direction in RTL.
|
|
116
|
+
*/
|
|
117
|
+
_dirInput = input(null, { ...(ngDevMode ? { debugName: "_dirInput" } : /* istanbul ignore next */ {}), alias: 'dir' });
|
|
118
|
+
dir = injectTextDirection(this._dirInput);
|
|
119
|
+
/**
|
|
120
|
+
* Visual inversion. Flips the mapping between value and screen position
|
|
121
|
+
* (e.g. horizontal LTR with `inverted=true`: max sits on the left).
|
|
122
|
+
* Keyboard semantics still target "toward max" with ArrowUp / ArrowRight
|
|
123
|
+
* (LTR), independent of visual flip.
|
|
124
|
+
*/
|
|
125
|
+
inverted = input(false, { ...(ngDevMode ? { debugName: "inverted" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
126
|
+
/**
|
|
127
|
+
* Multi-thumb only: minimum gap between adjacent thumbs, in step units.
|
|
128
|
+
* Default `0` (touch but never cross — non-passing per APG).
|
|
129
|
+
*/
|
|
130
|
+
minStepsBetweenThumbs = input(0, /* @ts-ignore */
|
|
131
|
+
...(ngDevMode ? [{ debugName: "minStepsBetweenThumbs" }] : /* istanbul ignore next */ []));
|
|
132
|
+
/**
|
|
133
|
+
* Emitted at the end of a value-changing interaction — pointerup /
|
|
134
|
+
* pointercancel after a drag, or keyup after one or more keyboard
|
|
135
|
+
* adjustments — with the final value array. Use it to defer expensive work
|
|
136
|
+
* (network calls,
|
|
137
|
+
* history entries) to the trailing edge of the interaction instead of
|
|
138
|
+
* running it per step. Stays silent when nothing changed (e.g. press +
|
|
139
|
+
* release without movement, or a non-navigation key on a focused thumb).
|
|
140
|
+
*/
|
|
141
|
+
valueCommit = output();
|
|
142
|
+
#thumbs = new Collection();
|
|
143
|
+
#trackEl = signal(null, /* @ts-ignore */
|
|
144
|
+
...(ngDevMode ? [{ debugName: "#trackEl" }] : /* istanbul ignore next */ []));
|
|
145
|
+
#destroyRef = inject(DestroyRef);
|
|
146
|
+
#activeDragCleanups = new Set();
|
|
147
|
+
#interactionMutated = false;
|
|
148
|
+
#armedThumb = null;
|
|
149
|
+
fractions = computed(() => {
|
|
150
|
+
const min = this.minValue();
|
|
151
|
+
const max = this.maxValue();
|
|
152
|
+
const inv = this.inverted();
|
|
153
|
+
const span = max - min;
|
|
154
|
+
if (span <= 0) {
|
|
155
|
+
return this.value().map(() => 0);
|
|
156
|
+
}
|
|
157
|
+
return this.value().map((v) => {
|
|
158
|
+
const f = (v - min) / span;
|
|
159
|
+
const clamped = f < 0 ? 0 : f > 1 ? 1 : f;
|
|
160
|
+
return inv ? 1 - clamped : clamped;
|
|
161
|
+
});
|
|
162
|
+
}, /* @ts-ignore */
|
|
163
|
+
...(ngDevMode ? [{ debugName: "fractions" }] : /* istanbul ignore next */ []));
|
|
164
|
+
/**
|
|
165
|
+
* Range start fraction. Visual semantics: in single-thumb mode the range
|
|
166
|
+
* always grows from the min edge to the thumb (`0 → fraction[0]`); in
|
|
167
|
+
* multi-thumb mode it spans `min(values) → max(values)`. `inverted` is
|
|
168
|
+
* already baked into the fractions, so consumers can paint blindly.
|
|
169
|
+
*/
|
|
170
|
+
rangeStart = computed(() => {
|
|
171
|
+
const fr = this.fractions();
|
|
172
|
+
if (fr.length === 0) {
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
175
|
+
if (fr.length === 1) {
|
|
176
|
+
return Math.min(fr[0], this.inverted() ? 1 : 0);
|
|
177
|
+
}
|
|
178
|
+
return Math.min(...fr);
|
|
179
|
+
}, /* @ts-ignore */
|
|
180
|
+
...(ngDevMode ? [{ debugName: "rangeStart" }] : /* istanbul ignore next */ []));
|
|
181
|
+
rangeEnd = computed(() => {
|
|
182
|
+
const fr = this.fractions();
|
|
183
|
+
if (fr.length === 0) {
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
if (fr.length === 1) {
|
|
187
|
+
return Math.max(fr[0], this.inverted() ? 1 : 0);
|
|
188
|
+
}
|
|
189
|
+
return Math.max(...fr);
|
|
190
|
+
}, /* @ts-ignore */
|
|
191
|
+
...(ngDevMode ? [{ debugName: "rangeEnd" }] : /* istanbul ignore next */ []));
|
|
192
|
+
constructor() {
|
|
193
|
+
super();
|
|
194
|
+
const stringValues = computed(() => this.value().map((v) => String(v)), /* @ts-ignore */
|
|
195
|
+
...(ngDevMode ? [{ debugName: "stringValues" }] : /* istanbul ignore next */ []));
|
|
196
|
+
injectHiddenInput({
|
|
197
|
+
name: this.name,
|
|
198
|
+
values: stringValues,
|
|
199
|
+
disabled: this.effectiveDisabled,
|
|
200
|
+
});
|
|
201
|
+
this.#destroyRef.onDestroy(() => {
|
|
202
|
+
for (const cleanup of this.#activeDragCleanups) {
|
|
203
|
+
cleanup();
|
|
204
|
+
}
|
|
205
|
+
this.#activeDragCleanups.clear();
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
setValueAt(index, raw) {
|
|
209
|
+
if (this.effectiveDisabled() || this.readonly()) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const current = this.value();
|
|
213
|
+
if (index < 0 || index >= current.length) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const next = this.#clampForIndex(current, index, raw);
|
|
217
|
+
if (next === current[index]) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const updated = current.slice();
|
|
221
|
+
updated[index] = next;
|
|
222
|
+
this.value.set(updated);
|
|
223
|
+
this.#interactionMutated = true;
|
|
224
|
+
this.#armedThumb = index;
|
|
225
|
+
}
|
|
226
|
+
bumpAt(index, key, large) {
|
|
227
|
+
if (this.effectiveDisabled() || this.readonly()) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const current = this.value();
|
|
231
|
+
if (index < 0 || index >= current.length) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const direction = this.#directionFor(key);
|
|
235
|
+
if (direction === 0) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const stepUnit = large ? this.largeStep() : this.step();
|
|
239
|
+
const target = current[index] + direction * stepUnit;
|
|
240
|
+
this.setValueAt(index, target);
|
|
241
|
+
}
|
|
242
|
+
setExtreme(index, which) {
|
|
243
|
+
if (this.effectiveDisabled() || this.readonly()) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
this.setValueAt(index, which === 'min' ? this.minValue() : this.maxValue());
|
|
247
|
+
}
|
|
248
|
+
pointerToValue(clientX, clientY) {
|
|
249
|
+
const track = this.#trackEl();
|
|
250
|
+
const min = this.minValue();
|
|
251
|
+
const max = this.maxValue();
|
|
252
|
+
if (!track) {
|
|
253
|
+
return min;
|
|
254
|
+
}
|
|
255
|
+
const rect = track.getBoundingClientRect();
|
|
256
|
+
const horizontal = this.orientation() === 'horizontal';
|
|
257
|
+
let fraction;
|
|
258
|
+
if (horizontal) {
|
|
259
|
+
const width = rect.width || 1;
|
|
260
|
+
fraction = (clientX - rect.left) / width;
|
|
261
|
+
if (this.dir() === 'rtl') {
|
|
262
|
+
fraction = 1 - fraction;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
const height = rect.height || 1;
|
|
267
|
+
// Vertical default: bottom = min, top = max.
|
|
268
|
+
fraction = (rect.bottom - clientY) / height;
|
|
269
|
+
}
|
|
270
|
+
if (this.inverted()) {
|
|
271
|
+
fraction = 1 - fraction;
|
|
272
|
+
}
|
|
273
|
+
fraction = fraction < 0 ? 0 : fraction > 1 ? 1 : fraction;
|
|
274
|
+
return min + fraction * (max - min);
|
|
275
|
+
}
|
|
276
|
+
nearestThumbIndex(target) {
|
|
277
|
+
const values = this.value();
|
|
278
|
+
if (values.length === 0) {
|
|
279
|
+
return -1;
|
|
280
|
+
}
|
|
281
|
+
let bestIndex = 0;
|
|
282
|
+
let bestDelta = Math.abs(values[0] - target);
|
|
283
|
+
let bestValue = values[0];
|
|
284
|
+
for (let i = 1; i < values.length; i++) {
|
|
285
|
+
const value = values[i];
|
|
286
|
+
const delta = Math.abs(value - target);
|
|
287
|
+
if (delta < bestDelta) {
|
|
288
|
+
bestIndex = i;
|
|
289
|
+
bestDelta = delta;
|
|
290
|
+
bestValue = value;
|
|
291
|
+
}
|
|
292
|
+
else if (delta === bestDelta) {
|
|
293
|
+
if (target > bestValue) {
|
|
294
|
+
bestIndex = i;
|
|
295
|
+
bestValue = value;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return bestIndex;
|
|
300
|
+
}
|
|
301
|
+
beginDrag(index, event) {
|
|
302
|
+
if (this.effectiveDisabled() || this.readonly()) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const pointerId = event.pointerId;
|
|
306
|
+
const move = (e) => {
|
|
307
|
+
if (e.pointerId !== pointerId) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
e.preventDefault();
|
|
311
|
+
this.setValueAt(index, this.pointerToValue(e.clientX, e.clientY));
|
|
312
|
+
};
|
|
313
|
+
// Drag tracking listens on the document's defaultView (the window)
|
|
314
|
+
// so the pointer can leave the slider track without losing the drag.
|
|
315
|
+
// Read it through the injected DOCUMENT to stay SSR-friendly — pointer
|
|
316
|
+
// events themselves only fire in the browser, so the optional chain
|
|
317
|
+
// simply makes this a no-op on the server.
|
|
318
|
+
const win = this.#document.defaultView;
|
|
319
|
+
const cleanup = () => {
|
|
320
|
+
win?.removeEventListener('pointermove', move);
|
|
321
|
+
win?.removeEventListener('pointerup', stop);
|
|
322
|
+
win?.removeEventListener('pointercancel', stop);
|
|
323
|
+
this.#activeDragCleanups.delete(cleanup);
|
|
324
|
+
};
|
|
325
|
+
const stop = (e) => {
|
|
326
|
+
if (e.pointerId !== pointerId) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
cleanup();
|
|
330
|
+
this.markTouched();
|
|
331
|
+
this.commitInteraction();
|
|
332
|
+
};
|
|
333
|
+
win?.addEventListener('pointermove', move);
|
|
334
|
+
win?.addEventListener('pointerup', stop);
|
|
335
|
+
win?.addEventListener('pointercancel', stop);
|
|
336
|
+
this.#activeDragCleanups.add(cleanup);
|
|
337
|
+
}
|
|
338
|
+
setTrack(el) {
|
|
339
|
+
this.#trackEl.set(el);
|
|
340
|
+
}
|
|
341
|
+
trackElement() {
|
|
342
|
+
return this.#trackEl();
|
|
343
|
+
}
|
|
344
|
+
markTouched() {
|
|
345
|
+
if (!this.touched()) {
|
|
346
|
+
super.markTouched();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Emit `valueCommit` if the running interaction mutated the value at least
|
|
351
|
+
* once, and clear the flag. Pointer drags call this on pointerup with no
|
|
352
|
+
* argument (the drag's own pointerup is already scoped to one thumb). Thumbs
|
|
353
|
+
* call it on keyup of a navigation key, passing their own `thumbIndex` so a
|
|
354
|
+
* keyup on a thumb that did not arm the pending commit (e.g. a second thumb
|
|
355
|
+
* focused and released without moving) cannot mis-attribute or steal another
|
|
356
|
+
* thumb's pending commit — only the thumb that armed it commits.
|
|
357
|
+
*/
|
|
358
|
+
commitInteraction(thumbIndex) {
|
|
359
|
+
if (!this.#interactionMutated) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (thumbIndex !== undefined && this.#armedThumb !== thumbIndex) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
this.#interactionMutated = false;
|
|
366
|
+
this.#armedThumb = null;
|
|
367
|
+
this.valueCommit.emit(this.value());
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Move focus to a thumb, implementing `FormUiControl.focus` from
|
|
371
|
+
* `@angular/forms/signals`. Without this override Signal Forms would focus
|
|
372
|
+
* the host `role="group"` wrapper — which is not focusable and carries no
|
|
373
|
+
* keyboard map — so focus-on-error would silently go nowhere. Targets the
|
|
374
|
+
* first registered thumb (the roving-tabindex entry point); no-op when the
|
|
375
|
+
* slider is disabled or has no thumbs.
|
|
376
|
+
*/
|
|
377
|
+
focus(options) {
|
|
378
|
+
if (this.effectiveDisabled()) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
this.#thumbs.items()[0]?.host.focus(options);
|
|
382
|
+
}
|
|
383
|
+
registerThumb(handle) {
|
|
384
|
+
this.#thumbs.register(handle);
|
|
385
|
+
}
|
|
386
|
+
unregisterThumb(handle) {
|
|
387
|
+
this.#thumbs.unregister(handle);
|
|
388
|
+
}
|
|
389
|
+
onFocusOut(event) {
|
|
390
|
+
const next = event.relatedTarget;
|
|
391
|
+
if (next && this.#host.nativeElement.contains(next)) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
this.markTouched();
|
|
395
|
+
}
|
|
396
|
+
/** Snap, clamp, and (multi-thumb) keep within neighbor bounds. */
|
|
397
|
+
#clampForIndex(values, index, raw) {
|
|
398
|
+
const min = this.minValue();
|
|
399
|
+
const max = this.maxValue();
|
|
400
|
+
const step = this.step();
|
|
401
|
+
const gap = this.minStepsBetweenThumbs() * step;
|
|
402
|
+
const snapped = snapToStep(raw, step, min);
|
|
403
|
+
let lo = min;
|
|
404
|
+
let hi = max;
|
|
405
|
+
if (index > 0) {
|
|
406
|
+
lo = Math.max(lo, values[index - 1] + gap);
|
|
407
|
+
}
|
|
408
|
+
if (index < values.length - 1) {
|
|
409
|
+
hi = Math.min(hi, values[index + 1] - gap);
|
|
410
|
+
}
|
|
411
|
+
return snapped < lo ? lo : snapped > hi ? hi : snapped;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Returns +1 / -1 / 0 for the keyboard direction's effect on the value,
|
|
415
|
+
* resolving orientation × dir × inverted. Up / Right (LTR) increase by
|
|
416
|
+
* default; `inverted` flips both axes.
|
|
417
|
+
*/
|
|
418
|
+
#directionFor(key) {
|
|
419
|
+
let positive;
|
|
420
|
+
switch (key) {
|
|
421
|
+
case 'ArrowUp':
|
|
422
|
+
positive = true;
|
|
423
|
+
break;
|
|
424
|
+
case 'ArrowDown':
|
|
425
|
+
positive = false;
|
|
426
|
+
break;
|
|
427
|
+
case 'ArrowRight':
|
|
428
|
+
positive = this.dir() !== 'rtl';
|
|
429
|
+
break;
|
|
430
|
+
case 'ArrowLeft':
|
|
431
|
+
positive = this.dir() === 'rtl';
|
|
432
|
+
break;
|
|
433
|
+
default:
|
|
434
|
+
return 0;
|
|
435
|
+
}
|
|
436
|
+
if (this.inverted()) {
|
|
437
|
+
positive = !positive;
|
|
438
|
+
}
|
|
439
|
+
return positive ? 1 : -1;
|
|
440
|
+
}
|
|
441
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForSlider, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
442
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForSlider, isStandalone: true, selector: "[forSlider]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, largeStep: { classPropertyName: "largeStep", publicName: "largeStep", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, _dirInput: { classPropertyName: "_dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, inverted: { classPropertyName: "inverted", publicName: "inverted", isSignal: true, isRequired: false, transformFunction: null }, minStepsBetweenThumbs: { classPropertyName: "minStepsBetweenThumbs", publicName: "minStepsBetweenThumbs", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", valueCommit: "valueCommit" }, host: { attributes: { "role": "group" }, listeners: { "focusout": "onFocusOut($event)" }, properties: { "attr.aria-disabled": "effectiveDisabled() ? \"true\" : null", "attr.aria-readonly": "readonly() ? \"true\" : null", "attr.aria-required": "required() ? \"true\" : null", "attr.aria-invalid": "invalid() ? \"true\" : null", "attr.aria-busy": "pending() ? \"true\" : null", "attr.data-orientation": "orientation()", "attr.data-disabled": "effectiveDisabled() ? \"\" : null", "attr.data-readonly": "readonly() ? \"\" : null", "attr.dir": "dir()" } }, providers: [{ provide: FOR_SLIDER_CONTEXT, useExisting: ForSlider }], exportAs: ["forSlider"], usesInheritance: true, ngImport: i0 });
|
|
443
|
+
}
|
|
444
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForSlider, decorators: [{
|
|
445
|
+
type: Directive,
|
|
446
|
+
args: [{
|
|
447
|
+
selector: '[forSlider]',
|
|
448
|
+
exportAs: 'forSlider',
|
|
449
|
+
host: {
|
|
450
|
+
role: 'group',
|
|
451
|
+
'[attr.aria-disabled]': 'effectiveDisabled() ? "true" : null',
|
|
452
|
+
'[attr.aria-readonly]': 'readonly() ? "true" : null',
|
|
453
|
+
'[attr.aria-required]': 'required() ? "true" : null',
|
|
454
|
+
'[attr.aria-invalid]': 'invalid() ? "true" : null',
|
|
455
|
+
'[attr.aria-busy]': 'pending() ? "true" : null',
|
|
456
|
+
'[attr.data-orientation]': 'orientation()',
|
|
457
|
+
'[attr.data-disabled]': 'effectiveDisabled() ? "" : null',
|
|
458
|
+
'[attr.data-readonly]': 'readonly() ? "" : null',
|
|
459
|
+
'[attr.dir]': 'dir()',
|
|
460
|
+
'(focusout)': 'onFocusOut($event)',
|
|
461
|
+
},
|
|
462
|
+
providers: [{ provide: FOR_SLIDER_CONTEXT, useExisting: ForSlider }],
|
|
463
|
+
}]
|
|
464
|
+
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], largeStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "largeStep", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], _dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], inverted: [{ type: i0.Input, args: [{ isSignal: true, alias: "inverted", required: false }] }], minStepsBetweenThumbs: [{ type: i0.Input, args: [{ isSignal: true, alias: "minStepsBetweenThumbs", required: false }] }], valueCommit: [{ type: i0.Output, args: ["valueCommit"] }] } });
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* The clickable track surface. PointerDown anywhere on the track that isn't
|
|
468
|
+
* a thumb finds the nearest thumb, jumps it to the clicked position, focuses
|
|
469
|
+
* it, and starts a drag (so the user can keep refining without releasing).
|
|
470
|
+
*
|
|
471
|
+
* Thumbs handle their own pointerdown, so a click directly on a thumb
|
|
472
|
+
* skips the "nearest" lookup and drags that specific thumb.
|
|
473
|
+
*
|
|
474
|
+
* Reflects `data-orientation` and `data-disabled` so consumers can paint
|
|
475
|
+
* the track from CSS. Position the visible track however you want — this
|
|
476
|
+
* directive just owns the bounding-rect for the math.
|
|
477
|
+
*/
|
|
478
|
+
class ForSliderTrack {
|
|
479
|
+
ctx = injectSliderContext('ForSliderTrack');
|
|
480
|
+
#host = inject(ElementRef);
|
|
481
|
+
constructor() {
|
|
482
|
+
this.ctx.setTrack(this.#host.nativeElement);
|
|
483
|
+
inject(DestroyRef).onDestroy(() => this.ctx.setTrack(null));
|
|
484
|
+
}
|
|
485
|
+
onPointerDown(event) {
|
|
486
|
+
if (this.ctx.effectiveDisabled() || this.ctx.readonly()) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
// Only react to primary button / touch / pen contact.
|
|
490
|
+
if (event.button !== undefined && event.button !== 0) {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
// Direct thumb clicks are handled by the thumb's own listener; the thumb
|
|
494
|
+
// calls `stopPropagation()` so we don't double-handle.
|
|
495
|
+
event.preventDefault();
|
|
496
|
+
const target = this.ctx.pointerToValue(event.clientX, event.clientY);
|
|
497
|
+
const index = this.ctx.nearestThumbIndex(target);
|
|
498
|
+
if (index < 0) {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
this.ctx.setValueAt(index, target);
|
|
502
|
+
this.ctx.beginDrag(index, event);
|
|
503
|
+
}
|
|
504
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForSliderTrack, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
505
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForSliderTrack, isStandalone: true, selector: "[forSliderTrack]", host: { listeners: { "pointerdown": "onPointerDown($event)" }, properties: { "attr.data-orientation": "ctx.orientation()", "attr.data-disabled": "ctx.effectiveDisabled() ? \"\" : null" } }, exportAs: ["forSliderTrack"], ngImport: i0 });
|
|
506
|
+
}
|
|
507
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForSliderTrack, decorators: [{
|
|
508
|
+
type: Directive,
|
|
509
|
+
args: [{
|
|
510
|
+
selector: '[forSliderTrack]',
|
|
511
|
+
exportAs: 'forSliderTrack',
|
|
512
|
+
host: {
|
|
513
|
+
'[attr.data-orientation]': 'ctx.orientation()',
|
|
514
|
+
'[attr.data-disabled]': 'ctx.effectiveDisabled() ? "" : null',
|
|
515
|
+
'(pointerdown)': 'onPointerDown($event)',
|
|
516
|
+
},
|
|
517
|
+
}]
|
|
518
|
+
}], ctorParameters: () => [] });
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Optional decorative band between the slider's lowest and highest thumbs
|
|
522
|
+
* (or `0 → thumb` for single-thumb sliders). The directive doesn't paint
|
|
523
|
+
* anything — it exposes start / end fractions as CSS variables and `data-*`
|
|
524
|
+
* so the consumer can size and position it from styles.
|
|
525
|
+
*
|
|
526
|
+
* Custom properties on the host:
|
|
527
|
+
* - `--for-slider-range-start` — fraction `[0, 1]`.
|
|
528
|
+
* - `--for-slider-range-end` — fraction `[0, 1]`.
|
|
529
|
+
* - `--for-slider-range-size` — `end - start`, useful for `width` / `height`.
|
|
530
|
+
*
|
|
531
|
+
* The fractions already account for `inverted`, so painting can be blind.
|
|
532
|
+
* Combine with `data-orientation` to pick the right CSS axis.
|
|
533
|
+
*/
|
|
534
|
+
class ForSliderRange {
|
|
535
|
+
ctx = injectSliderContext('ForSliderRange');
|
|
536
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForSliderRange, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
537
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForSliderRange, isStandalone: true, selector: "[forSliderRange]", host: { properties: { "attr.data-orientation": "ctx.orientation()", "attr.data-disabled": "ctx.effectiveDisabled() ? \"\" : null", "style.--for-slider-range-start": "ctx.rangeStart()", "style.--for-slider-range-end": "ctx.rangeEnd()", "style.--for-slider-range-size": "ctx.rangeEnd() - ctx.rangeStart()" } }, exportAs: ["forSliderRange"], ngImport: i0 });
|
|
538
|
+
}
|
|
539
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForSliderRange, decorators: [{
|
|
540
|
+
type: Directive,
|
|
541
|
+
args: [{
|
|
542
|
+
selector: '[forSliderRange]',
|
|
543
|
+
exportAs: 'forSliderRange',
|
|
544
|
+
host: {
|
|
545
|
+
'[attr.data-orientation]': 'ctx.orientation()',
|
|
546
|
+
'[attr.data-disabled]': 'ctx.effectiveDisabled() ? "" : null',
|
|
547
|
+
'[style.--for-slider-range-start]': 'ctx.rangeStart()',
|
|
548
|
+
'[style.--for-slider-range-end]': 'ctx.rangeEnd()',
|
|
549
|
+
'[style.--for-slider-range-size]': 'ctx.rangeEnd() - ctx.rangeStart()',
|
|
550
|
+
},
|
|
551
|
+
}]
|
|
552
|
+
}] });
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* One slider thumb. Apply on any focusable element (commonly `<span>` /
|
|
556
|
+
* `<div>` with the directive's auto-injected `tabindex`). The directive
|
|
557
|
+
* sets `role="slider"`, the live `aria-value*` attributes, keyboard
|
|
558
|
+
* handling, and starts a drag on pointerdown.
|
|
559
|
+
*
|
|
560
|
+
* `[index]` is the 0-based position in the parent slider's `value()` array
|
|
561
|
+
* — pass the loop index when rendering N thumbs:
|
|
562
|
+
*
|
|
563
|
+
* ```html
|
|
564
|
+
* @for (v of value(); let i = $index; track i) {
|
|
565
|
+
* <span forSliderThumb [index]="i" [label]="i === 0 ? 'Min' : 'Max'"></span>
|
|
566
|
+
* }
|
|
567
|
+
* ```
|
|
568
|
+
*
|
|
569
|
+
* Custom property exposed:
|
|
570
|
+
* - `--for-slider-thumb-position` — fraction `[0, 1]` already accounting
|
|
571
|
+
* for `inverted`. Use it via e.g.
|
|
572
|
+
* `inset-inline-start: calc(var(--for-slider-thumb-position) * 100%)`.
|
|
573
|
+
*/
|
|
574
|
+
class ForSliderThumb {
|
|
575
|
+
ctx = injectSliderContext('ForSliderThumb');
|
|
576
|
+
#host = inject(ElementRef);
|
|
577
|
+
/** Position in the parent slider's `value()` array (0-based). */
|
|
578
|
+
index = input.required({ ...(ngDevMode ? { debugName: "index" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
579
|
+
/**
|
|
580
|
+
* Optional fixed label for assistive tech (e.g. "Minimum price",
|
|
581
|
+
* "Maximum price"). Mirrored as `aria-label`. Use `[labelledby]`
|
|
582
|
+
* instead if the label lives elsewhere in the DOM.
|
|
583
|
+
*/
|
|
584
|
+
label = input('', /* @ts-ignore */
|
|
585
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
586
|
+
labelledby = input('', /* @ts-ignore */
|
|
587
|
+
...(ngDevMode ? [{ debugName: "labelledby" }] : /* istanbul ignore next */ []));
|
|
588
|
+
/**
|
|
589
|
+
* Optional human-readable value override (e.g. "$1,200" instead of "1200").
|
|
590
|
+
* Mirrored as `aria-valuetext` only when non-empty; when omitted, no
|
|
591
|
+
* `aria-valuetext` is emitted so assistive tech reads `aria-valuenow`.
|
|
592
|
+
*/
|
|
593
|
+
valueText = input('', /* @ts-ignore */
|
|
594
|
+
...(ngDevMode ? [{ debugName: "valueText" }] : /* istanbul ignore next */ []));
|
|
595
|
+
tabindex = computed(() => (this.ctx.effectiveDisabled() ? -1 : 0), /* @ts-ignore */
|
|
596
|
+
...(ngDevMode ? [{ debugName: "tabindex" }] : /* istanbul ignore next */ []));
|
|
597
|
+
currentValue = computed(() => {
|
|
598
|
+
const i = this.index();
|
|
599
|
+
const values = this.ctx.value();
|
|
600
|
+
if (i < 0 || i >= values.length) {
|
|
601
|
+
return this.ctx.minValue();
|
|
602
|
+
}
|
|
603
|
+
return values[i];
|
|
604
|
+
}, /* @ts-ignore */
|
|
605
|
+
...(ngDevMode ? [{ debugName: "currentValue" }] : /* istanbul ignore next */ []));
|
|
606
|
+
fraction = computed(() => {
|
|
607
|
+
const i = this.index();
|
|
608
|
+
const fr = this.ctx.fractions();
|
|
609
|
+
if (i < 0 || i >= fr.length) {
|
|
610
|
+
return 0;
|
|
611
|
+
}
|
|
612
|
+
return fr[i];
|
|
613
|
+
}, /* @ts-ignore */
|
|
614
|
+
...(ngDevMode ? [{ debugName: "fraction" }] : /* istanbul ignore next */ []));
|
|
615
|
+
ariaValueNow = computed(() => this.currentValue(), /* @ts-ignore */
|
|
616
|
+
...(ngDevMode ? [{ debugName: "ariaValueNow" }] : /* istanbul ignore next */ []));
|
|
617
|
+
ariaValueText = computed(() => this.valueText() || null, /* @ts-ignore */
|
|
618
|
+
...(ngDevMode ? [{ debugName: "ariaValueText" }] : /* istanbul ignore next */ []));
|
|
619
|
+
ariaValueMin = computed(() => {
|
|
620
|
+
const i = this.index();
|
|
621
|
+
const values = this.ctx.value();
|
|
622
|
+
if (i > 0 && i < values.length) {
|
|
623
|
+
// Multi-thumb non-passing: this thumb's lower bound is the previous
|
|
624
|
+
// thumb's value (matches APG multi-thumb guidance).
|
|
625
|
+
return values[i - 1];
|
|
626
|
+
}
|
|
627
|
+
return this.ctx.minValue();
|
|
628
|
+
}, /* @ts-ignore */
|
|
629
|
+
...(ngDevMode ? [{ debugName: "ariaValueMin" }] : /* istanbul ignore next */ []));
|
|
630
|
+
ariaValueMax = computed(() => {
|
|
631
|
+
const i = this.index();
|
|
632
|
+
const values = this.ctx.value();
|
|
633
|
+
if (i >= 0 && i < values.length - 1) {
|
|
634
|
+
return values[i + 1];
|
|
635
|
+
}
|
|
636
|
+
return this.ctx.maxValue();
|
|
637
|
+
}, /* @ts-ignore */
|
|
638
|
+
...(ngDevMode ? [{ debugName: "ariaValueMax" }] : /* istanbul ignore next */ []));
|
|
639
|
+
constructor() {
|
|
640
|
+
const handle = {
|
|
641
|
+
host: this.#host.nativeElement,
|
|
642
|
+
index: this.index,
|
|
643
|
+
};
|
|
644
|
+
registerHandle(handle, (h) => this.ctx.registerThumb(h), (h) => this.ctx.unregisterThumb(h));
|
|
645
|
+
}
|
|
646
|
+
onKeyDown(event) {
|
|
647
|
+
if (this.ctx.effectiveDisabled() || this.ctx.readonly()) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
switch (event.key) {
|
|
651
|
+
case 'ArrowUp':
|
|
652
|
+
case 'ArrowDown':
|
|
653
|
+
case 'ArrowLeft':
|
|
654
|
+
case 'ArrowRight':
|
|
655
|
+
event.preventDefault();
|
|
656
|
+
this.ctx.bumpAt(this.index(), event.key, false);
|
|
657
|
+
return;
|
|
658
|
+
case 'PageUp':
|
|
659
|
+
event.preventDefault();
|
|
660
|
+
this.ctx.bumpAt(this.index(), 'ArrowUp', true);
|
|
661
|
+
return;
|
|
662
|
+
case 'PageDown':
|
|
663
|
+
event.preventDefault();
|
|
664
|
+
this.ctx.bumpAt(this.index(), 'ArrowDown', true);
|
|
665
|
+
return;
|
|
666
|
+
case 'Home':
|
|
667
|
+
event.preventDefault();
|
|
668
|
+
this.ctx.setExtreme(this.index(), 'min');
|
|
669
|
+
return;
|
|
670
|
+
case 'End':
|
|
671
|
+
event.preventDefault();
|
|
672
|
+
this.ctx.setExtreme(this.index(), 'max');
|
|
673
|
+
return;
|
|
674
|
+
default:
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
onKeyUp(event) {
|
|
679
|
+
switch (event.key) {
|
|
680
|
+
case 'ArrowUp':
|
|
681
|
+
case 'ArrowDown':
|
|
682
|
+
case 'ArrowLeft':
|
|
683
|
+
case 'ArrowRight':
|
|
684
|
+
case 'PageUp':
|
|
685
|
+
case 'PageDown':
|
|
686
|
+
case 'Home':
|
|
687
|
+
case 'End':
|
|
688
|
+
this.ctx.commitInteraction(this.index());
|
|
689
|
+
return;
|
|
690
|
+
default:
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
onPointerDown(event) {
|
|
695
|
+
if (this.ctx.effectiveDisabled() || this.ctx.readonly()) {
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
if (event.button !== undefined && event.button !== 0) {
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
// Stop the track's pointerdown handler from re-running "nearest thumb"
|
|
702
|
+
// — clicking directly on a thumb should drag *that* thumb.
|
|
703
|
+
event.stopPropagation();
|
|
704
|
+
event.preventDefault();
|
|
705
|
+
this.#host.nativeElement.focus();
|
|
706
|
+
this.ctx.beginDrag(this.index(), event);
|
|
707
|
+
}
|
|
708
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForSliderThumb, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
709
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForSliderThumb, isStandalone: true, selector: "[forSliderThumb]", inputs: { index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, labelledby: { classPropertyName: "labelledby", publicName: "labelledby", isSignal: true, isRequired: false, transformFunction: null }, valueText: { classPropertyName: "valueText", publicName: "valueText", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "slider" }, listeners: { "keydown": "onKeyDown($event)", "keyup": "onKeyUp($event)", "pointerdown": "onPointerDown($event)" }, properties: { "attr.tabindex": "tabindex()", "attr.aria-valuemin": "ariaValueMin()", "attr.aria-valuemax": "ariaValueMax()", "attr.aria-valuenow": "ariaValueNow()", "attr.aria-valuetext": "ariaValueText()", "attr.aria-orientation": "ctx.orientation()", "attr.aria-label": "label() || null", "attr.aria-labelledby": "labelledby() || null", "attr.aria-disabled": "ctx.effectiveDisabled() ? \"true\" : null", "attr.aria-readonly": "ctx.readonly() ? \"true\" : null", "attr.data-orientation": "ctx.orientation()", "attr.data-disabled": "ctx.effectiveDisabled() ? \"\" : null", "attr.data-readonly": "ctx.readonly() ? \"\" : null", "attr.data-index": "index()", "style.--for-slider-thumb-position": "fraction()" } }, exportAs: ["forSliderThumb"], ngImport: i0 });
|
|
710
|
+
}
|
|
711
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForSliderThumb, decorators: [{
|
|
712
|
+
type: Directive,
|
|
713
|
+
args: [{
|
|
714
|
+
selector: '[forSliderThumb]',
|
|
715
|
+
exportAs: 'forSliderThumb',
|
|
716
|
+
host: {
|
|
717
|
+
role: 'slider',
|
|
718
|
+
'[attr.tabindex]': 'tabindex()',
|
|
719
|
+
'[attr.aria-valuemin]': 'ariaValueMin()',
|
|
720
|
+
'[attr.aria-valuemax]': 'ariaValueMax()',
|
|
721
|
+
'[attr.aria-valuenow]': 'ariaValueNow()',
|
|
722
|
+
'[attr.aria-valuetext]': 'ariaValueText()',
|
|
723
|
+
'[attr.aria-orientation]': 'ctx.orientation()',
|
|
724
|
+
'[attr.aria-label]': 'label() || null',
|
|
725
|
+
'[attr.aria-labelledby]': 'labelledby() || null',
|
|
726
|
+
'[attr.aria-disabled]': 'ctx.effectiveDisabled() ? "true" : null',
|
|
727
|
+
'[attr.aria-readonly]': 'ctx.readonly() ? "true" : null',
|
|
728
|
+
'[attr.data-orientation]': 'ctx.orientation()',
|
|
729
|
+
'[attr.data-disabled]': 'ctx.effectiveDisabled() ? "" : null',
|
|
730
|
+
'[attr.data-readonly]': 'ctx.readonly() ? "" : null',
|
|
731
|
+
'[attr.data-index]': 'index()',
|
|
732
|
+
'[style.--for-slider-thumb-position]': 'fraction()',
|
|
733
|
+
'(keydown)': 'onKeyDown($event)',
|
|
734
|
+
'(keyup)': 'onKeyUp($event)',
|
|
735
|
+
'(pointerdown)': 'onPointerDown($event)',
|
|
736
|
+
},
|
|
737
|
+
}]
|
|
738
|
+
}], ctorParameters: () => [], propDecorators: { index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], labelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelledby", required: false }] }], valueText: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueText", required: false }] }] } });
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Exact public names of every `ForSlider` input, its models included. Spread it into the
|
|
742
|
+
* `inputs` array of a `hostDirectives` entry so a wrapper component re-exposes the
|
|
743
|
+
* primitive's full surface — the Signal Forms members `[formField]` binds among them —
|
|
744
|
+
* without hand-maintaining the list. Always spread into an inline object literal as shown
|
|
745
|
+
* below: the literal is what keeps the entry statically analyzable for consumers compiling
|
|
746
|
+
* against the published package. An anti-drift spec fails when this list no longer matches
|
|
747
|
+
* the directive's actual API. See `docs/wrapping-form-primitives.md` for both supported
|
|
748
|
+
* wrapping patterns.
|
|
749
|
+
*
|
|
750
|
+
* @example
|
|
751
|
+
* ```ts
|
|
752
|
+
* @Component({
|
|
753
|
+
* selector: 'div[mySlider]',
|
|
754
|
+
* template: '',
|
|
755
|
+
* hostDirectives: [
|
|
756
|
+
* {
|
|
757
|
+
* directive: ForSlider,
|
|
758
|
+
* inputs: [...FOR_SLIDER_HOST_DIRECTIVE_INPUTS],
|
|
759
|
+
* outputs: [...FOR_SLIDER_HOST_DIRECTIVE_OUTPUTS],
|
|
760
|
+
* },
|
|
761
|
+
* ],
|
|
762
|
+
* })
|
|
763
|
+
* export class MySlider {}
|
|
764
|
+
* ```
|
|
765
|
+
*/
|
|
766
|
+
const FOR_SLIDER_HOST_DIRECTIVE_INPUTS = [
|
|
767
|
+
'value',
|
|
768
|
+
'dir',
|
|
769
|
+
'dirty',
|
|
770
|
+
'disabled',
|
|
771
|
+
'errors',
|
|
772
|
+
'invalid',
|
|
773
|
+
'inverted',
|
|
774
|
+
'largeStep',
|
|
775
|
+
'max',
|
|
776
|
+
'min',
|
|
777
|
+
'minStepsBetweenThumbs',
|
|
778
|
+
'name',
|
|
779
|
+
'orientation',
|
|
780
|
+
'pending',
|
|
781
|
+
'readonly',
|
|
782
|
+
'required',
|
|
783
|
+
'step',
|
|
784
|
+
'touched',
|
|
785
|
+
];
|
|
786
|
+
/**
|
|
787
|
+
* Exact public names of every `ForSlider` output, the Signal Forms `touch` output
|
|
788
|
+
* included. Spread it into the `outputs` array of the same `hostDirectives` entry as
|
|
789
|
+
* {@link FOR_SLIDER_HOST_DIRECTIVE_INPUTS}.
|
|
790
|
+
*/
|
|
791
|
+
const FOR_SLIDER_HOST_DIRECTIVE_OUTPUTS = [
|
|
792
|
+
'valueChange',
|
|
793
|
+
'valueCommit',
|
|
794
|
+
'touchedChange',
|
|
795
|
+
'touch',
|
|
796
|
+
];
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Generated bundle index. Do not edit.
|
|
800
|
+
*/
|
|
801
|
+
|
|
802
|
+
export { FOR_SLIDER_CONTEXT, FOR_SLIDER_DEFAULTS, FOR_SLIDER_HOST_DIRECTIVE_INPUTS, FOR_SLIDER_HOST_DIRECTIVE_OUTPUTS, ForSlider, ForSliderRange, ForSliderThumb, ForSliderTrack, provideForSliderDefaults };
|
|
803
|
+
//# sourceMappingURL=forty-cdk-slider.mjs.map
|