forty-cdk 0.28.0 → 0.29.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/calendar/README.md +16 -16
- package/combobox/README.md +60 -15
- package/date-field/README.md +16 -14
- package/date-picker/README.md +69 -21
- package/fesm2022/forty-cdk-breakpoints.mjs +5 -12
- package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -1
- package/fesm2022/forty-cdk-calendar.mjs +10 -1
- package/fesm2022/forty-cdk-calendar.mjs.map +1 -1
- package/fesm2022/forty-cdk-combobox.mjs +152 -20
- package/fesm2022/forty-cdk-combobox.mjs.map +1 -1
- package/fesm2022/forty-cdk-core-overlay.mjs +17 -20
- package/fesm2022/forty-cdk-core-overlay.mjs.map +1 -1
- package/fesm2022/forty-cdk-core.mjs +122 -22
- package/fesm2022/forty-cdk-core.mjs.map +1 -1
- package/fesm2022/forty-cdk-date-field.mjs +83 -22
- package/fesm2022/forty-cdk-date-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-date-picker.mjs +200 -46
- package/fesm2022/forty-cdk-date-picker.mjs.map +1 -1
- package/fesm2022/forty-cdk-dialog.mjs +3 -1
- package/fesm2022/forty-cdk-dialog.mjs.map +1 -1
- package/fesm2022/forty-cdk-drawer.mjs +3 -1
- package/fesm2022/forty-cdk-drawer.mjs.map +1 -1
- package/fesm2022/forty-cdk-field.mjs +95 -7
- package/fesm2022/forty-cdk-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-input.mjs +31 -6
- package/fesm2022/forty-cdk-input.mjs.map +1 -1
- package/fesm2022/forty-cdk-internationalized-date.mjs +18 -2
- package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
- package/fesm2022/forty-cdk-otp-input.mjs +17 -3
- package/fesm2022/forty-cdk-otp-input.mjs.map +1 -1
- package/fesm2022/forty-cdk-popover.mjs +9 -3
- package/fesm2022/forty-cdk-popover.mjs.map +1 -1
- package/fesm2022/forty-cdk-select.mjs +10 -8
- package/fesm2022/forty-cdk-select.mjs.map +1 -1
- package/fesm2022/forty-cdk-time-field.mjs +83 -22
- package/fesm2022/forty-cdk-time-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-time-picker.mjs +139 -21
- package/fesm2022/forty-cdk-time-picker.mjs.map +1 -1
- package/fesm2022/forty-cdk-toggle.mjs +15 -3
- package/fesm2022/forty-cdk-toggle.mjs.map +1 -1
- package/field/README.md +40 -1
- package/input/README.md +50 -25
- package/otp-input/README.md +10 -4
- package/package.json +1 -1
- package/select/README.md +1 -1
- package/time-field/README.md +7 -5
- package/time-picker/README.md +74 -26
- package/toggle/README.md +19 -18
- package/types/forty-cdk-breakpoints.d.ts +9 -8
- package/types/forty-cdk-calendar.d.ts +13 -6
- package/types/forty-cdk-combobox.d.ts +125 -20
- package/types/forty-cdk-core-overlay.d.ts +27 -31
- package/types/forty-cdk-core.d.ts +234 -46
- package/types/forty-cdk-date-field.d.ts +35 -6
- package/types/forty-cdk-date-picker.d.ts +143 -22
- package/types/forty-cdk-field.d.ts +82 -32
- package/types/forty-cdk-input.d.ts +12 -0
- package/types/forty-cdk-internationalized-date.d.ts +16 -4
- package/types/forty-cdk-otp-input.d.ts +12 -2
- package/types/forty-cdk-select.d.ts +4 -4
- package/types/forty-cdk-time-field.d.ts +35 -6
- package/types/forty-cdk-time-picker.d.ts +84 -15
- package/types/forty-cdk-toggle.d.ts +18 -5
package/calendar/README.md
CHANGED
|
@@ -210,22 +210,22 @@ Click the heading button to cycle from day → month → year view. Click a mont
|
|
|
210
210
|
|
|
211
211
|
### `ForCalendar`
|
|
212
212
|
|
|
213
|
-
| Property | Type | Description
|
|
214
|
-
| ------------------- | -------------------------------------- |
|
|
215
|
-
| `value` | `model<D \| null>` | Two-way bindable selected date, or `null`. Used in `selectionMode="single"`. `(valueChange)` fires only on internal selection.<br>**Default:** `null`
|
|
216
|
-
| `selectionMode` | `input<'single' \| 'range'>` | `'single'` (default) keeps the single-date `value` flow. `'range'` switches to anchor → commit and exposes `range`.<br>**Default:** `'single'`
|
|
217
|
-
| `range` | `model<DateRange<D> \| null>` | Two-way bindable committed range. Only used in `selectionMode="range"`. `(rangeChange)` fires only on internal commits/clears.<br>**Default:** `null`
|
|
218
|
-
| `minRangeLength` | `input<number \| null>` | Minimum inclusive day count. A commit shorter than this is a no-op.<br>**Default:** `null` (no minimum)
|
|
219
|
-
| `maxRangeLength` | `input<number \| null>` | Maximum inclusive day count. A commit longer than this is a no-op.<br>**Default:** `null` (no maximum)
|
|
220
|
-
| `min` | `input<D \| null>` | Minimum selectable date (inclusive). Earlier dates are unavailable.<br>**Default:** `null`
|
|
221
|
-
| `max` | `input<D \| null>` | Maximum selectable date (inclusive). Later dates are unavailable.<br>**Default:** `null`
|
|
222
|
-
| `isDateUnavailable` | `input<(date: D) => boolean>` | Per-date predicate marking a date unavailable (present but not selectable).<br>**Default:** `() => false`
|
|
223
|
-
| `dateLabel` | `input<CalendarDateLabelFormatter<D>>` | Formats each gridcell's `aria-label` (full accessible date).<br>**Default:** localized full date, outside-month days through the scope's `outsideMonthLabel`
|
|
224
|
-
| `disabled` | `input<boolean>` | Disables the whole calendar (no focus movement, no selection). Reflected as `data-disabled`.<br>**Default:** —
|
|
225
|
-
| `readonly` | `input<boolean>` | Read-only: dates stay focusable, selection is blocked. Reflected as `data-readonly`.<br>**Default:** —
|
|
226
|
-
| `firstDayOfWeek` | `input<number \| null>` | First column's weekday, **0-6** (`0` = Sunday).<br>**Default:** `null` → the adapter's value (or `provideForCalendarDefaults`)
|
|
227
|
-
| `locale` | `input<string \| null>` | BCP 47 locale for the heading, weekday headers, month-picker options and cell `aria-label` names. The calendar system stays Gregorian.<br>**Default:** `null` → the
|
|
228
|
-
| `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction.<br>**Default:** `null` resolves the ambient direction; reflected to the host `dir` and mirrors horizontal arrows
|
|
213
|
+
| Property | Type | Description |
|
|
214
|
+
| ------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
215
|
+
| `value` | `model<D \| null>` | Two-way bindable selected date, or `null`. Used in `selectionMode="single"`. `(valueChange)` fires only on internal selection.<br>**Default:** `null` |
|
|
216
|
+
| `selectionMode` | `input<'single' \| 'range'>` | `'single'` (default) keeps the single-date `value` flow. `'range'` switches to anchor → commit and exposes `range`.<br>**Default:** `'single'` |
|
|
217
|
+
| `range` | `model<DateRange<D> \| null>` | Two-way bindable committed range. Only used in `selectionMode="range"`. `(rangeChange)` fires only on internal commits/clears.<br>**Default:** `null` |
|
|
218
|
+
| `minRangeLength` | `input<number \| null>` | Minimum inclusive day count. A commit shorter than this is a no-op.<br>**Default:** `null` (no minimum) |
|
|
219
|
+
| `maxRangeLength` | `input<number \| null>` | Maximum inclusive day count. A commit longer than this is a no-op.<br>**Default:** `null` (no maximum) |
|
|
220
|
+
| `min` | `input<D \| null>` | Minimum selectable date (inclusive). Earlier dates are unavailable.<br>**Default:** `null` |
|
|
221
|
+
| `max` | `input<D \| null>` | Maximum selectable date (inclusive). Later dates are unavailable.<br>**Default:** `null` |
|
|
222
|
+
| `isDateUnavailable` | `input<(date: D) => boolean>` | Per-date predicate marking a date unavailable (present but not selectable).<br>**Default:** `() => false` |
|
|
223
|
+
| `dateLabel` | `input<CalendarDateLabelFormatter<D>>` | Formats each gridcell's `aria-label` (full accessible date).<br>**Default:** localized full date, outside-month days through the scope's `outsideMonthLabel` |
|
|
224
|
+
| `disabled` | `input<boolean>` | Disables the whole calendar (no focus movement, no selection). Reflected as `data-disabled`.<br>**Default:** — |
|
|
225
|
+
| `readonly` | `input<boolean>` | Read-only: dates stay focusable, selection is blocked. Reflected as `data-readonly`.<br>**Default:** — |
|
|
226
|
+
| `firstDayOfWeek` | `input<number \| null>` | First column's weekday, **0-6** (`0` = Sunday).<br>**Default:** `null` → the adapter's value (or `provideForCalendarDefaults`) |
|
|
227
|
+
| `locale` | `input<string \| null>` | BCP 47 locale for the heading, weekday headers, month-picker options and cell `aria-label` names. The calendar system stays Gregorian.<br>**Default:** `null` → the adapter's `locale()`, then the runtime locale |
|
|
228
|
+
| `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction.<br>**Default:** `null` resolves the ambient direction; reflected to the host `dir` and mirrors horizontal arrows |
|
|
229
229
|
|
|
230
230
|
### Data attributes
|
|
231
231
|
|
package/combobox/README.md
CHANGED
|
@@ -207,6 +207,8 @@ Input tables are not yet tabulated for this primitive. See the feature sections
|
|
|
207
207
|
| `[forCombobox]` | `data-readonly` | present / absent |
|
|
208
208
|
| `[forComboboxInput]` | `data-state` | `open` \| `closed` |
|
|
209
209
|
| `[forComboboxInput]` | `data-disabled` | present / absent |
|
|
210
|
+
| `[forComboboxToggle]` | `data-state` | `open` \| `closed` |
|
|
211
|
+
| `[forComboboxToggle]` | `data-disabled` | present / absent |
|
|
210
212
|
| `[forComboboxContent]` | `data-state` | `open` \| `closed` |
|
|
211
213
|
| `[forComboboxOption]` | `data-state` | `checked` \| `unchecked` (membership in `value()`, both modes) |
|
|
212
214
|
| `[forComboboxOption]` | `data-highlighted` | present / absent (the current `aria-activedescendant`) |
|
|
@@ -245,7 +247,31 @@ By default the listbox is positioned against `[forComboboxInput]`. When the inpu
|
|
|
245
247
|
</div>
|
|
246
248
|
```
|
|
247
249
|
|
|
248
|
-
`[forComboboxAnchor]` changes **only** positioning. The input keeps `aria-controls` / `aria-expanded` / `aria-activedescendant`, all keyboard interaction, and its exemption from outside-pointer dismissal. Without an anchor the listbox falls back to the input, so existing markup is unaffected. Each `[forCombobox]` takes
|
|
250
|
+
`[forComboboxAnchor]` changes **only** positioning. The input keeps `aria-controls` / `aria-expanded` / `aria-activedescendant`, all keyboard interaction, and its exemption from outside-pointer dismissal. Without an anchor the listbox falls back to the input, so existing markup is unaffected. It wins over a surrounding field's [`[forFieldAnchor]`](../field/README.md#positioning-anchor). Each `[forCombobox]` takes one `[forComboboxAnchor]`, and a second one warns in dev mode. In multi mode, wrap `[forComboboxChips]` (which already wraps the chips + input) to anchor against the full chip cluster.
|
|
251
|
+
|
|
252
|
+
## Toggle button
|
|
253
|
+
|
|
254
|
+
An editable combobox often carries a chevron button next to the input, as in the APG's [editable combobox examples](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-list/). Put `[forComboboxToggle]` on a real `<button>`:
|
|
255
|
+
|
|
256
|
+
```html
|
|
257
|
+
<div forCombobox #combobox="forCombobox" [(query)]="query" [(value)]="value">
|
|
258
|
+
<div forComboboxAnchor class="field-box">
|
|
259
|
+
<input forComboboxInput placeholder="Search a fruit…" />
|
|
260
|
+
<button forComboboxToggle>▾</button>
|
|
261
|
+
</div>
|
|
262
|
+
@if (combobox.open()) {
|
|
263
|
+
<div forComboboxContent>
|
|
264
|
+
@for (it of filtered; track it.id) {
|
|
265
|
+
<div forComboboxOption [value]="it.id" [label]="it.label">{{ it.label }}</div>
|
|
266
|
+
}
|
|
267
|
+
</div>
|
|
268
|
+
}
|
|
269
|
+
</div>
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
A press closes an open listbox, or opens a closed one with the committed selection highlighted (the first enabled option when nothing is selected) and moves focus into the input. The press never takes focus itself, so an input that already has focus keeps it and the combobox is not marked touched. The button is exempt from the listbox's outside-pointer dismissal, so one press is one `(openChange)`.
|
|
273
|
+
|
|
274
|
+
Unlike `[forComboboxTrigger]`, a toggle keeps the editable anatomy: `commitOnSelect` still copies the picked label into the input, and `query` survives a close. The button is out of the Tab sequence (`tabindex="-1"`) because the input already owns the keyboard, reflects `aria-expanded` and `aria-controls` (the listbox, while open), and carries native `disabled` from the combobox's effective disabled. Its accessible name defaults to `'Show options'`; override it per instance with `[ariaLabel]`, or for the scope with `provideForComboboxDefaults({ toggleAriaLabel })`.
|
|
249
275
|
|
|
250
276
|
## Picker anatomy
|
|
251
277
|
|
|
@@ -402,6 +428,7 @@ They diverge while the user types and resync on activation:
|
|
|
402
428
|
- **Multi mode** → option's value is toggled in/out of `value`. If `commitOnSelect` is on (default), `query` is **cleared** so the user can search the next item. Listbox stays open.
|
|
403
429
|
- Clear button → both reset.
|
|
404
430
|
- `clearOnQueryChange` (off by default, **single mode only**): flip on to drop `value` automatically whenever the query is edited (useful when the user editing means "I'm picking a new one").
|
|
431
|
+
- `restoreQueryOnClose` (off by default, **single mode only**): flip on to put the selected label back into the input when the listbox closes without a pick. See [`restoreQueryOnClose`](#restorequeryonclose).
|
|
405
432
|
|
|
406
433
|
### `commitOnSelect`: single vs multi
|
|
407
434
|
|
|
@@ -429,6 +456,19 @@ Multi, commitOnSelect=false
|
|
|
429
456
|
|
|
430
457
|
Disable `commitOnSelect` when your filter logic compares against `query` directly and the listbox should keep showing the just-narrowed set after activation, instead of resetting to "everything matches the picked label".
|
|
431
458
|
|
|
459
|
+
### `restoreQueryOnClose`
|
|
460
|
+
|
|
461
|
+
In the editable anatomy, closing the listbox leaves `query` as the user left it, so typing "ap" over a committed "Banana" and pressing Escape keeps showing "ap". With `[restoreQueryOnClose]="true"`, a single-select combobox restores the selected option's label on every close that is not a pick (Escape, an outside press, Tab, a `[forComboboxToggle]` press, `closeOverlay()`), and clears the input when nothing is selected. The input keeps focus on Escape, and the restored text reaches it even while focused. A pick still follows `commitOnSelect`.
|
|
462
|
+
|
|
463
|
+
```text
|
|
464
|
+
Single, restoreQueryOnClose=true
|
|
465
|
+
user activates "Banana" → query="Banana" value=["banana"]
|
|
466
|
+
user types "ap" → query="ap" value=["banana"]
|
|
467
|
+
user presses Escape → query="Banana" value=["banana"] ← label restored, listbox closes
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
The label is the one `selected()` resolves: the option's own label once it has rendered, `[itemToStringLabel]` before that (a value bound before the listbox ever opened). Multi mode and the picker anatomy ignore the input. Enable it for the whole scope with `provideForComboboxDefaults({ restoreQueryOnClose: true })`.
|
|
471
|
+
|
|
432
472
|
## Multi mode
|
|
433
473
|
|
|
434
474
|
Pass `multiple` and let the consumer render chips inside `[forComboboxChips]`. The primitive's `selected()` computed returns `{ value, label }` pairs ready for `@for`:
|
|
@@ -493,6 +533,10 @@ When the input is empty (no query) and the user presses Backspace, focus jumps t
|
|
|
493
533
|
| Auto-highlight first option | `true` | `[autoHighlight]="false"` to require arrowing to an option first |
|
|
494
534
|
| Commit label / clear query on select | `true` | `[commitOnSelect]="false"` |
|
|
495
535
|
| Clear value on query edit (single only) | `false` | `[clearOnQueryChange]="true"` |
|
|
536
|
+
| Highlight the selection on open | `first` | `openHighlight="selected"` |
|
|
537
|
+
| Restore label on close (single only) | `false` | `[restoreQueryOnClose]="true"` |
|
|
538
|
+
|
|
539
|
+
`openHighlight` decides where the editable anatomy's highlight lands when the listbox opens from focus, click, ArrowDown / ArrowUp or `openOverlay()` without an argument. With `'selected'`, a single-select combobox showing "Spain" reopens on "Spain" rather than on the first option; with nothing selected, ArrowDown still lands on the first option and ArrowUp on the last. Opening from a typed query always highlights the first match, because the list is a filter result there, and the picker anatomy always opens on the selection. Both inputs take their default from the scope: `provideForComboboxDefaults({ openHighlight: 'selected', restoreQueryOnClose: true })`.
|
|
496
540
|
|
|
497
541
|
## Autocomplete modes
|
|
498
542
|
|
|
@@ -735,20 +779,20 @@ A single-select field is modeled as the same `readonly T[]`, kept at length ≤
|
|
|
735
779
|
|
|
736
780
|
Focus stays in the input throughout: arrow keys move the listbox's _active descendant_ (the highlighted option), not DOM focus.
|
|
737
781
|
|
|
738
|
-
| Key | Action
|
|
739
|
-
| -------------------------------------------- |
|
|
740
|
-
| **ArrowDown** | Open listbox + move activedescendant to next enabled option (or first when none).
|
|
741
|
-
| **ArrowUp** | Open listbox + move activedescendant to previous enabled option (or last when none).
|
|
742
|
-
| **Home** _(open)_ | Move activedescendant to first enabled option.
|
|
743
|
-
| **End** _(open)_ | Move activedescendant to last enabled option.
|
|
744
|
-
| **PageUp** _(open)_ | Move activedescendant to first enabled option.
|
|
745
|
-
| **PageDown** _(open)_ | Move activedescendant to last enabled option.
|
|
746
|
-
| **Enter** _(open)_ | Activate the activedescendant (single: replace + close; multi: toggle + stay open).
|
|
747
|
-
| **Escape** _(open)_ | Close the listbox. Focus stays in the input.
|
|
748
|
-
| **Tab** _(open, no action)_ | Close the listbox and let Tab flow to the next focusable.
|
|
749
|
-
| **Tab / Shift+Tab** _(open, action present)_ | Move focus around the input↔actions ring without dismissing (see [Action items](#action-items)).
|
|
750
|
-
| **Backspace** _(empty input, multi only)_ | Focus the last chip; a second Backspace there removes it.
|
|
751
|
-
| Printable keys | Update `query`. With `'inline'` / `'both'` autocomplete, complete the rest of the first match into the input as selected text.
|
|
782
|
+
| Key | Action |
|
|
783
|
+
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
784
|
+
| **ArrowDown** | Open listbox + move activedescendant to next enabled option (or first when none; the selection under `openHighlight="selected"`). |
|
|
785
|
+
| **ArrowUp** | Open listbox + move activedescendant to previous enabled option (or last when none; the selection under `openHighlight="selected"`). |
|
|
786
|
+
| **Home** _(open)_ | Move activedescendant to first enabled option. |
|
|
787
|
+
| **End** _(open)_ | Move activedescendant to last enabled option. |
|
|
788
|
+
| **PageUp** _(open)_ | Move activedescendant to first enabled option. |
|
|
789
|
+
| **PageDown** _(open)_ | Move activedescendant to last enabled option. |
|
|
790
|
+
| **Enter** _(open)_ | Activate the activedescendant (single: replace + close; multi: toggle + stay open). |
|
|
791
|
+
| **Escape** _(open)_ | Close the listbox. Focus stays in the input. |
|
|
792
|
+
| **Tab** _(open, no action)_ | Close the listbox and let Tab flow to the next focusable. |
|
|
793
|
+
| **Tab / Shift+Tab** _(open, action present)_ | Move focus around the input↔actions ring without dismissing (see [Action items](#action-items)). |
|
|
794
|
+
| **Backspace** _(empty input, multi only)_ | Focus the last chip; a second Backspace there removes it. |
|
|
795
|
+
| Printable keys | Update `query`. With `'inline'` / `'both'` autocomplete, complete the rest of the first match into the input as selected text. |
|
|
752
796
|
|
|
753
797
|
Hovering an option also makes it the activedescendant, so mouse and keyboard intent stay synchronized.
|
|
754
798
|
|
|
@@ -760,6 +804,7 @@ Implements the [WAI-ARIA Combobox pattern](https://www.w3.org/WAI/ARIA/apg/patte
|
|
|
760
804
|
- `role="listbox"` lives on `[forComboboxContent]` in the editable anatomy and on `[forComboboxList]` in the picker anatomy; the input's `aria-controls` targets whichever carries it. In the picker anatomy the popup surface (`[forComboboxContent]`) is role-less so it can hold the input next to the list without an `aria-required-owned-elements` violation.
|
|
761
805
|
- `aria-multiselectable="true"` (multi mode) and the labelled role (`aria-label` / `aria-labelledby`, pointing at the input) sit on whichever element carries `role="listbox"`: content in the editable anatomy, the list in the picker anatomy.
|
|
762
806
|
- `[forComboboxTrigger]` (picker anatomy) is a real `<button>` reflecting `aria-haspopup="listbox"`, `aria-expanded`, `aria-controls` (the popup surface, while open), and native `disabled` from the combobox's effective disabled. It is exempt from the popup's outside-pointer dismissal layer, like the input.
|
|
807
|
+
- `[forComboboxToggle]` (editable anatomy) is a real `<button>` with `tabindex="-1"`, a localizable `aria-label`, `aria-expanded`, `aria-controls` (the listbox, while open) and native `disabled`. It cancels `mousedown` so focus stays in the input, and it is exempt from the outside-pointer dismissal layer.
|
|
763
808
|
- In single mode, `aria-selected="true"` follows the activedescendant (the option Enter would activate). In multi mode it follows membership in `value()`, so every selected option carries `aria-selected="true"` simultaneously.
|
|
764
809
|
- `data-state="checked" | "unchecked"` always reflects membership in `value()`, so consumers can paint a checkmark icon with pure CSS regardless of mode.
|
|
765
810
|
- `data-highlighted=""` marks the option that is the current `aria-activedescendant`. Because focus stays on the `<input>`, there is no `:focus` on the option to style. `data-highlighted` is the canonical CSS hook.
|
package/date-field/README.md
CHANGED
|
@@ -155,8 +155,8 @@ With a time-capable adapter, a `granularity` coarser than `'day'` appends time s
|
|
|
155
155
|
| `minDate` | `input<D \| null>` | Minimum date (inclusive). A composed value below it is clamped up. Named `minDate` (see note below).<br>**Default:** `null` |
|
|
156
156
|
| `maxDate` | `input<D \| null>` | Maximum date (inclusive). A composed value above it is clamped down.<br>**Default:** `null` |
|
|
157
157
|
| `granularity` | `input<'day' \| 'hour' \| 'minute' \| 'second'>` | Date-time precision. `'day'` is date-only; coarser-than-day appends time segments. See below.<br>**Default:** `'day'` |
|
|
158
|
-
| `hourCycle` | `input<12 \| 24 \| null>` | 12/24-hour cycle for the time segments. `null` → locale. 12-hour adds the AM/PM segment.<br>**Default:** `null`
|
|
159
|
-
| `locale` | `input<string \| null>` | BCP 47 locale driving segment order, separators, and month name. `null` → runtime locale.<br>**Default:** `null`
|
|
158
|
+
| `hourCycle` | `input<12 \| 24 \| null>` | 12/24-hour cycle for the time segments. `null` → the scope's `hourCycle`, then the locale. 12-hour adds the AM/PM segment.<br>**Default:** `null` |
|
|
159
|
+
| `locale` | `input<string \| null>` | BCP 47 locale driving segment order, separators, and month name. `null` → the adapter's `locale()`, then the runtime locale.<br>**Default:** `null` |
|
|
160
160
|
| `placeholder` | `input<Partial<Record<SegmentType, string>>>` | Per-segment placeholder while empty. Unspecified parts fall back to the scope's `placeholder`, then to `dd` / `mm` / `yyyy` / `hh` / `mm` / `ss` / `--`.<br>**Default:** `{}` |
|
|
161
161
|
| `ariaLabel` | `input<string \| null>` | Accessible name for the group. Emits no `aria-label` while `null`.<br>**Default:** `null` |
|
|
162
162
|
| `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction. `null` resolves the ambient direction; mirrors ArrowLeft / ArrowRight segment navigation.<br>**Default:** `null` |
|
|
@@ -220,7 +220,9 @@ providers: [
|
|
|
220
220
|
|
|
221
221
|
`segmentLabels` supplies each segment's default `aria-label`, keyed by part type. Unset keys keep the library default (the part name, and `'AM/PM'` for the `dayPeriod` segment), so overriding a single key never wipes the rest. A segment's own `[ariaLabel]` still wins over the scope default.
|
|
222
222
|
|
|
223
|
-
`placeholder` is the text an empty segment shows, keyed the same way. A field's own `[placeholder]` wins for the parts it names and only those, and a part neither names keeps the letter-repeat default. `provideForDateRangeFieldDefaults` takes the same
|
|
223
|
+
`placeholder` is the text an empty segment shows, keyed the same way. A field's own `[placeholder]` wins for the parts it names and only those, and a part neither names keeps the letter-repeat default. `provideForDateRangeFieldDefaults` takes the same keys for `[forDateRangeField]`.
|
|
224
|
+
|
|
225
|
+
`hourCycle` sets the 12- or 24-hour cycle of every field that doesn't bind `[hourCycle]`, so a product on a 24-hour clock sets it once instead of on each field. Its fallback `null` derives the cycle from the locale.
|
|
224
226
|
|
|
225
227
|
For a language the app sets or switches after bootstrap, pass the text keys as functions and the overrides as a factory, as [Localizing default text](../shared/README.md#localizing-default-text) shows.
|
|
226
228
|
|
|
@@ -268,17 +270,17 @@ readonly booking = form(this.model);
|
|
|
268
270
|
|
|
269
271
|
### `ForDateRangeField` API
|
|
270
272
|
|
|
271
|
-
| Property | Type | Description
|
|
272
|
-
| ------------- | ------------------------------------------------ |
|
|
273
|
-
| `value` | `model<DateRange<D> \| null>` | Two-way bindable committed range, or `null` while incomplete or out of order. The `FormValueControl` backing.<br>**Default:** `null`
|
|
274
|
-
| `minDate` | `input<D \| null>` | Minimum date (inclusive) for both endpoints. A composed endpoint below it is clamped up. Named `minDate` (see note below).<br>**Default:** `null`
|
|
275
|
-
| `maxDate` | `input<D \| null>` | Maximum date (inclusive) for both endpoints. A composed endpoint above it is clamped down.<br>**Default:** `null`
|
|
276
|
-
| `granularity` | `input<'day' \| 'hour' \| 'minute' \| 'second'>` | Date-time precision shared by both endpoints. `'day'` is date-only; coarser-than-day appends time segments.<br>**Default:** `'day'`
|
|
277
|
-
| `hourCycle` | `input<12 \| 24 \| null>` | 12/24-hour cycle for the time segments. `null` → locale. 12-hour adds the AM/PM segment.<br>**Default:** `null`
|
|
278
|
-
| `locale` | `input<string \| null>` | BCP 47 locale driving segment order, separators, and month name. `null` → runtime locale.<br>**Default:** `null`
|
|
279
|
-
| `placeholder` | `input<Partial<Record<SegmentType, string>>>` | Per-segment placeholder while empty, applied to both endpoints. Unspecified parts fall back to the scope's `placeholder`.<br>**Default:** `{}`
|
|
280
|
-
| `ariaLabel` | `input<string \| null>` | Accessible name for the whole range field group. Emits no `aria-label` while `null`.<br>**Default:** `null`
|
|
281
|
-
| `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction. `null` resolves the ambient direction; mirrors ArrowLeft / ArrowRight segment navigation.<br>**Default:** `null`
|
|
273
|
+
| Property | Type | Description |
|
|
274
|
+
| ------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
275
|
+
| `value` | `model<DateRange<D> \| null>` | Two-way bindable committed range, or `null` while incomplete or out of order. The `FormValueControl` backing.<br>**Default:** `null` |
|
|
276
|
+
| `minDate` | `input<D \| null>` | Minimum date (inclusive) for both endpoints. A composed endpoint below it is clamped up. Named `minDate` (see note below).<br>**Default:** `null` |
|
|
277
|
+
| `maxDate` | `input<D \| null>` | Maximum date (inclusive) for both endpoints. A composed endpoint above it is clamped down.<br>**Default:** `null` |
|
|
278
|
+
| `granularity` | `input<'day' \| 'hour' \| 'minute' \| 'second'>` | Date-time precision shared by both endpoints. `'day'` is date-only; coarser-than-day appends time segments.<br>**Default:** `'day'` |
|
|
279
|
+
| `hourCycle` | `input<12 \| 24 \| null>` | 12/24-hour cycle for the time segments. `null` → the scope's `hourCycle`, then the locale. 12-hour adds the AM/PM segment.<br>**Default:** `null` |
|
|
280
|
+
| `locale` | `input<string \| null>` | BCP 47 locale driving segment order, separators, and month name. `null` → the adapter's `locale()`, then the runtime locale.<br>**Default:** `null` |
|
|
281
|
+
| `placeholder` | `input<Partial<Record<SegmentType, string>>>` | Per-segment placeholder while empty, applied to both endpoints. Unspecified parts fall back to the scope's `placeholder`.<br>**Default:** `{}` |
|
|
282
|
+
| `ariaLabel` | `input<string \| null>` | Accessible name for the whole range field group. Emits no `aria-label` while `null`.<br>**Default:** `null` |
|
|
283
|
+
| `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction. `null` resolves the ambient direction; mirrors ArrowLeft / ArrowRight segment navigation.<br>**Default:** `null` |
|
|
282
284
|
|
|
283
285
|
The endpoint groups each accept an `ariaLabel` input for their own group label, falling back to the scope defaults (`'Start date'` / `'End date'`). Plus the shared `FormUiControl` members bound automatically by `[formField]`.
|
|
284
286
|
|
package/date-picker/README.md
CHANGED
|
@@ -18,6 +18,7 @@ Reinterpreted idiomatically for modern Angular: a focusable trigger that opens a
|
|
|
18
18
|
- **Date Picker**: a trigger plus a floating [Calendar](../calendar/README.md), and the form value itself (`FormValueControl<D | null>`). Choose it when the date is found by looking and the grid should stay out of the way until asked for.
|
|
19
19
|
- **[Calendar](../calendar/README.md)**: the same grid inline and always visible. It exposes `[(value)]` as a model but implements no form-control contract, so a form binds the picker rather than the calendar.
|
|
20
20
|
- **[Date Field](../date-field/README.md)**: segmented keyboard entry with no grid and no popup. Choose it when the user already knows the date and typing is the fast path.
|
|
21
|
+
- **Both at once**: a date field the user types into, with a calendar button beside it. That is this picker's [field anatomy](#field-anatomy).
|
|
21
22
|
|
|
22
23
|
## Date adapter
|
|
23
24
|
|
|
@@ -196,24 +197,25 @@ Open the picker and click a first day: the trigger keeps its placeholder, becaus
|
|
|
196
197
|
|
|
197
198
|
### `ForDatePicker`
|
|
198
199
|
|
|
199
|
-
| Property | Type | Description
|
|
200
|
-
| ------------------- | ------------------------------------------------ |
|
|
201
|
-
| `value` | `model<D \| null>` | Two-way bindable selected date. `(valueChange)` fires only on internal commits.<br>**Default:** `null`
|
|
202
|
-
| `open` | `model<boolean>` | Two-way bindable surface visibility. `(openChange)` fires only on internal transitions.<br>**Default:** `false`
|
|
203
|
-
| `minDate` | `input<D \| null>` | Minimum selectable date (inclusive). Forward to the projected calendar's `[min]`.<br>**Default:** `null`
|
|
204
|
-
| `maxDate` | `input<D \| null>` | Maximum selectable date (inclusive). Forward to the projected calendar's `[max]`.<br>**Default:** `null`
|
|
205
|
-
| `isDateUnavailable` | `input<(date: D) => boolean>` | Per-date predicate. Forward to the projected calendar's `[isDateUnavailable]`.<br>**Default:** `() => false`
|
|
206
|
-
| `closeOnSelect` | `input<boolean>` | Close the surface after a date is picked. Honoured
|
|
207
|
-
| `
|
|
208
|
-
| `
|
|
209
|
-
| `
|
|
210
|
-
| `
|
|
211
|
-
| `
|
|
212
|
-
| `
|
|
213
|
-
| `
|
|
214
|
-
| `
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
200
|
+
| Property | Type | Description |
|
|
201
|
+
| ------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
202
|
+
| `value` | `model<D \| null>` | Two-way bindable selected date. `(valueChange)` fires only on internal commits.<br>**Default:** `null` |
|
|
203
|
+
| `open` | `model<boolean>` | Two-way bindable surface visibility. `(openChange)` fires only on internal transitions.<br>**Default:** `false` |
|
|
204
|
+
| `minDate` | `input<D \| null>` | Minimum selectable date (inclusive). Forward to the projected calendar's `[min]`.<br>**Default:** `null` |
|
|
205
|
+
| `maxDate` | `input<D \| null>` | Maximum selectable date (inclusive). Forward to the projected calendar's `[max]`.<br>**Default:** `null` |
|
|
206
|
+
| `isDateUnavailable` | `input<(date: D) => boolean>` | Per-date predicate. Forward to the projected calendar's `[isDateUnavailable]`.<br>**Default:** `() => false` |
|
|
207
|
+
| `closeOnSelect` | `input<boolean>` | Close the surface after a date is picked. Honoured at `granularity="day"`, and at any granularity with `anatomy="field"`.<br>**Default:** `true` |
|
|
208
|
+
| `anatomy` | `input<'trigger' \| 'field'>` | Which piece is the control. `'field'` makes a projected `[forDateField]` the control and the trigger a plain button; see [Field anatomy](#field-anatomy).<br>**Default:** `'trigger'` |
|
|
209
|
+
| `granularity` | `input<'day' \| 'hour' \| 'minute' \| 'second'>` | Date-time precision. `'day'` (default) is a pure date picker; coarser-than-day off composes a time field.<br>**Default:** `'day'` |
|
|
210
|
+
| `hourCycle` | `input<12 \| 24 \| null>` | 12/24-hour cycle for the value display (and typically the projected `[forTimeField]`).<br>**Default:** `null` → the scope's `hourCycle` (`provideForDatePickerDefaults`), then the locale |
|
|
211
|
+
| `modal` | `input<boolean>` | Trap focus + inert background + scroll lock (centered dialog) instead of an anchored popover.<br>**Default:** `false` |
|
|
212
|
+
| `dismissible` | `input<boolean>` | Escape / outside-pointer dismiss the surface.<br>**Default:** `true` |
|
|
213
|
+
| `returnFocus` | `input<boolean>` | Return focus to the trigger on close.<br>**Default:** `true` |
|
|
214
|
+
| `formatOptions` | `input<Intl.DateTimeFormatOptions>` | Options for the text rendered by `[forDatePickerValue]`.<br>**Default:** `{ year: 'numeric', month: 'long', day: 'numeric' }` |
|
|
215
|
+
| `locale` | `input<string \| null>` | BCP 47 locale for the text rendered by `[forDatePickerValue]`. Not forwarded to the projected calendar, so bind its `[locale]` too.<br>**Default:** `null` → the adapter's `locale()`, then the runtime locale |
|
|
216
|
+
| `placeholder` | `input<string>` | Fallback text for `[forDatePickerValue]` when empty.<br>**Default:** `''` |
|
|
217
|
+
| `side` / `align` | `input` | Anchored placement (popover mode only). Defaults from `provideForDatePickerDefaults` / `provideForDateRangePickerDefaults`.<br>**Default:** `'bottom'` / `'start'` |
|
|
218
|
+
| `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction.<br>**Default:** `null` resolves the ambient direction; reflected to the host `dir` |
|
|
217
219
|
|
|
218
220
|
Plus the shared `FormUiControl` inputs from the base (`disabled`, `readonly`, `required`, `invalid`, `pending`, `dirty`, `name`, `errors`, and the `touched` model) and the floating tunables (`sideOffset`, `alignOffset`, `avoidCollisions`, `collisionPadding`, `sticky`, `hideWhenDetached`).
|
|
219
221
|
|
|
@@ -274,7 +276,7 @@ By default the surface is positioned against `[forDatePickerTrigger]`. When the
|
|
|
274
276
|
</div>
|
|
275
277
|
```
|
|
276
278
|
|
|
277
|
-
`[forDatePickerAnchor]` changes **only** positioning. The trigger keeps `aria-haspopup` / `aria-expanded` / `aria-controls`, the click toggle, focus return on close, and its exemption from outside-pointer dismissal. Without an anchor the surface falls back to the trigger, so existing markup is unaffected. Each `[forDatePicker]` accepts
|
|
279
|
+
`[forDatePickerAnchor]` changes **only** positioning. The trigger keeps `aria-haspopup` / `aria-expanded` / `aria-controls`, the click toggle, focus return on close, and its exemption from outside-pointer dismissal. Without an anchor the surface falls back to the trigger, so existing markup is unaffected. It wins over a surrounding field's [`[forFieldAnchor]`](../field/README.md#positioning-anchor). Each `[forDatePicker]` accepts one `[forDatePickerAnchor]`, and a second one warns in dev mode. (A calendar has its own intrinsic width and ignores `--for-floating-anchor-width`, so the anchor mainly affects start / side alignment to the box edge.)
|
|
278
280
|
|
|
279
281
|
## Modal vs non-modal
|
|
280
282
|
|
|
@@ -328,6 +330,52 @@ The content is a field boundary. Inside a [`[forField]`](../field/README.md#how-
|
|
|
328
330
|
|
|
329
331
|
The value display (`[forDatePickerValue]`) automatically appends the time to its formatting when `granularity > 'day'` and you haven't set time fields in `formatOptions`.
|
|
330
332
|
|
|
333
|
+
## Field anatomy
|
|
334
|
+
|
|
335
|
+
The default anatomy makes the trigger the control: it shows the value and takes the label. The common form-field composite is the other way round, and so is the APG example: an editable field that holds the value, plus a separate button that opens the calendar. Set `anatomy="field"` and project a [`[forDateField]`](../date-field/README.md) inside the picker to get that shape.
|
|
336
|
+
|
|
337
|
+
```html
|
|
338
|
+
<div forField>
|
|
339
|
+
<label forLabel>Appointment</label>
|
|
340
|
+
<div
|
|
341
|
+
forDatePicker
|
|
342
|
+
anatomy="field"
|
|
343
|
+
[formField]="form.when"
|
|
344
|
+
granularity="minute"
|
|
345
|
+
[minDate]="min"
|
|
346
|
+
[maxDate]="max"
|
|
347
|
+
#picker="forDatePicker"
|
|
348
|
+
>
|
|
349
|
+
<div forDateField #field="forDateField">
|
|
350
|
+
@for (seg of field.segments(); track seg.id) { @if (seg.isLiteral) {
|
|
351
|
+
<span forDateFieldLiteral>{{ seg.text }}</span>
|
|
352
|
+
} @else {
|
|
353
|
+
<span forDateFieldSegment [segment]="seg.type!">{{ seg.text }}</span>
|
|
354
|
+
} }
|
|
355
|
+
</div>
|
|
356
|
+
<button forDatePickerTrigger aria-label="Open calendar">…icon…</button>
|
|
357
|
+
|
|
358
|
+
@if (picker.open()) {
|
|
359
|
+
<div forDatePickerContent>
|
|
360
|
+
<div forCalendar [value]="picker.value()" [min]="picker.minDate()" [max]="picker.maxDate()">
|
|
361
|
+
<!-- …calendar header + grid… -->
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
}
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
With the field adopted:
|
|
370
|
+
|
|
371
|
+
- **One form control.** The picker stays the `FormValueControl`, so `[formField]` binds once, on the picker. A typed date and a calendar pick both write the picker's value, and both mark it dirty; a pick marks it touched, and so does focus leaving the field.
|
|
372
|
+
- **State is set once.** `minDate`, `maxDate`, `disabled`, `readonly`, `granularity`, `hourCycle` and `locale` on the picker apply to the field. A read-only picker blocks typing and picking alike.
|
|
373
|
+
- **The field is the control.** A surrounding `[forField]` names and describes the field's `role="group"`, `aria-invalid` lands there, a label press focuses its first segment, and the picker's `focus()` goes there too.
|
|
374
|
+
- **The trigger is a plain button.** It drops `role="combobox"` and the form-control `aria-*` state, and keeps `aria-haspopup="dialog"`, `aria-expanded` and `aria-controls`. Give it a name of its own.
|
|
375
|
+
- **A pick closes the surface at any granularity.** The time is typed in the field rather than in the surface, so a picked day keeps the field's time and the surface closes (unless `closeOnSelect` is off).
|
|
376
|
+
|
|
377
|
+
The anatomy is explicit, so a date field placed inside the surface of a trigger-anatomy picker changes nothing. With `anatomy="field"` and no projected `[forDateField]`, opening the calendar or calling `focus()` throws `FORCDK-DATE-PICKER-007` in dev mode.
|
|
378
|
+
|
|
331
379
|
## Range selection — `ForDateRangePicker`
|
|
332
380
|
|
|
333
381
|
For date-range selection use the dedicated `ForDateRangePicker` root (selector `[forDateRangePicker]`). It is the root **and** the form value, implementing `FormValueControl<DateRange<D> | null>`, so the committed range auto-wires with `[formField]` exactly like any other control.
|
|
@@ -396,10 +444,10 @@ Inside the surface, the projected `ForCalendar` owns the full grid keyboard map
|
|
|
396
444
|
|
|
397
445
|
Implements the [WAI-ARIA Date Picker Dialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/).
|
|
398
446
|
|
|
399
|
-
- **`role="combobox"`** on the trigger with **`aria-haspopup="dialog"`**, `aria-expanded` reflecting `open()`, and `aria-controls` pointing at the surface while open. This is the same shape `[forSelectTrigger]` / `[forTimePickerTrigger]` ship, with the `dialog` popup token ARIA 1.2 allows for a combobox surface. The role is also what makes the form-control ARIA below legal: `role="button"` supports neither `aria-readonly` nor `aria-required`.
|
|
447
|
+
- **`role="combobox"`** on the trigger with **`aria-haspopup="dialog"`**, `aria-expanded` reflecting `open()`, and `aria-controls` pointing at the surface while open. This is the same shape `[forSelectTrigger]` / `[forTimePickerTrigger]` ship, with the `dialog` popup token ARIA 1.2 allows for a combobox surface. The role is also what makes the form-control ARIA below legal: `role="button"` supports neither `aria-readonly` nor `aria-required`. In the [field anatomy](#field-anatomy) the trigger is a plain button with the same three popup attributes and none of the form-control state, which the date field carries instead.
|
|
400
448
|
- **`role="dialog"`** on the surface, named by `[ariaLabel]` (or `aria-labelledby` the trigger when no label is set). `aria-modal="true"` only in modal mode (truthy-only).
|
|
401
449
|
- **Form-control ARIA** (`aria-readonly` / `aria-required` / `aria-invalid` / `aria-busy`) is reflected on the focusable trigger so assistive tech announces validity on the element that takes focus, alongside the `data-readonly` styling hook. The disabled state is the exception. It reflects through one channel only: the native `disabled` attribute (plus `data-disabled`), never `aria-disabled`.
|
|
402
|
-
- **Inside a `[forField]` the labelled element is the trigger**, not the `[forDatePicker]` / `[forDateRangePicker]` wrapper: the field's `controlId` and its `aria-labelledby` / `aria-describedby` / `aria-errormessage` land on `[forDatePickerTrigger]`, so `[forLabel]`'s `for` points at the element that takes focus, clicking a non-`<label>` `[forLabel]` opens the surface, and Signal Forms' focus-on-error reaches the trigger. `role="combobox"` takes its name from the author, so this is the channel that names the control. The root's `[ariaLabel]` names the `role="dialog"` surface instead.
|
|
450
|
+
- **Inside a `[forField]` the labelled element is the trigger**, not the `[forDatePicker]` / `[forDateRangePicker]` wrapper: the field's `controlId` and its `aria-labelledby` / `aria-describedby` / `aria-errormessage` land on `[forDatePickerTrigger]`, so `[forLabel]`'s `for` points at the element that takes focus, clicking a non-`<label>` `[forLabel]` opens the surface, and Signal Forms' focus-on-error reaches the trigger. `role="combobox"` takes its name from the author, so this is the channel that names the control. The root's `[ariaLabel]` names the `role="dialog"` surface instead. In the [field anatomy](#field-anatomy) the same association lands on the date field's `role="group"` instead, and a label press focuses its first segment.
|
|
403
451
|
- **Focus management**: focus enters the surface on open (the calendar's roving cell in non-modal mode) and returns to the trigger on close, both vetoable via `(autoFocusOnOpen)` / `(autoFocusOnClose)`.
|
|
404
452
|
- **Dismissal**: Escape (`(escapeKeyDown)`) and outside-pointer (`(pointerDownOutside)` / `(interactOutside)`) close the surface, each vetoable.
|
|
405
453
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createDefaults,
|
|
1
|
+
import { createDefaults, MediaQueryRegistry, fortyError } from 'forty-cdk/core';
|
|
2
2
|
export { injectPrefersReducedMotion } from 'forty-cdk/core';
|
|
3
|
-
import { inject,
|
|
3
|
+
import { inject, computed } from '@angular/core';
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
@@ -74,21 +74,14 @@ function provideForBreakpointsDefaults(breakpoints) {
|
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
|
|
77
78
|
|
|
78
79
|
|
|
79
80
|
function injectBreakpoints() {
|
|
80
|
-
const
|
|
81
|
+
const registry = inject(MediaQueryRegistry);
|
|
81
82
|
const map = inject(FOR_BREAKPOINTS_DEFAULTS).breakpoints;
|
|
82
83
|
const names = Object.keys(map).sort((a, b) => map[a] - map[b]);
|
|
83
|
-
const
|
|
84
|
-
const observe = (query) => {
|
|
85
|
-
let result = cache.get(query);
|
|
86
|
-
if (result === undefined) {
|
|
87
|
-
result = runInInjectionContext(injector, () => injectMediaQuery(query));
|
|
88
|
-
cache.set(query, result);
|
|
89
|
-
}
|
|
90
|
-
return result;
|
|
91
|
-
};
|
|
84
|
+
const observe = (query) => registry.observe(query);
|
|
92
85
|
const thresholdOf = (name) => {
|
|
93
86
|
const value = map[name];
|
|
94
87
|
if (value === undefined) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"forty-cdk-breakpoints.mjs","sources":["../../../projects/forty-cdk/breakpoints/src/breakpoints-defaults.ts","../../../projects/forty-cdk/breakpoints/src/breakpoints.ts","../../../projects/forty-cdk/breakpoints/src/forty-cdk-breakpoints.ts"],"sourcesContent":["import { type Provider } from '@angular/core';\n\nimport { createDefaults } from 'forty-cdk/core';\n\n/**\n * A breakpoint map: each named breakpoint paired with its `min-width`\n * threshold in CSS pixels. Names are arbitrary — `injectBreakpoints` derives\n * its query methods (`up`, `down`, `between`, `only`) from whatever keys this\n * map declares, sorted ascending by threshold.\n */\nexport type BreakpointMap = Record<string, number>;\n\n/**\n * The default breakpoint map, mirroring Tailwind CSS's `sm`–`2xl` scale. Used\n * as the library fallback when no `provideForBreakpointsDefaults` is in scope,\n * and the source of the typed breakpoint names when the consumer has not\n * augmented {@link BreakpointRegistry}.\n */\nexport const forBreakpointsTailwind = {\n sm: 640,\n md: 768,\n lg: 1024,\n xl: 1280,\n '2xl': 1536,\n} as const satisfies BreakpointMap;\n\n/** The breakpoint names from the default Tailwind scale. */\nexport type TailwindBreakpointName = keyof typeof forBreakpointsTailwind;\n\n/** Resolved shape stored against {@link FOR_BREAKPOINTS_DEFAULTS}. */\nexport interface ForBreakpointsDefaults {\n /** The active breakpoint map for the current injector scope. */\n breakpoints: BreakpointMap;\n}\n\n/**\n * Library fallback for breakpoints defaults, read at the root injector when no\n * consumer has called `provideForBreakpointsDefaults`. Exported for the shared\n * defaults contract spec; not re-exported from the primitive's public entry.\n */\nexport const FOR_BREAKPOINTS_FALLBACK_DEFAULTS: ForBreakpointsDefaults = {\n breakpoints: forBreakpointsTailwind,\n};\n\nconst { token, provideDefaults } = createDefaults<ForBreakpointsDefaults>(\n 'FOR_BREAKPOINTS_DEFAULTS',\n FOR_BREAKPOINTS_FALLBACK_DEFAULTS,\n);\n\n/**\n * Token holding the resolved breakpoint map for the current injector scope.\n * The library always provides a fully-populated value (the Tailwind fallback\n * at the root, or the map from the nearest `provideForBreakpointsDefaults`).\n */\nexport const FOR_BREAKPOINTS_DEFAULTS = token;\n\n/**\n * Configures the breakpoint map for this injector scope so `injectBreakpoints`\n * can be called with no arguments. Provide it once in your application config:\n *\n * ```ts\n * providers: [\n * provideForBreakpointsDefaults({ mobile: 0, tablet: 640, laptop: 1024, desktop: 1280 }),\n * ]\n * ```\n *\n * Providing it again on a component injector replaces the map for that subtree\n * only (the nearest scope wins; the map is replaced wholesale, never merged\n * key-by-key). Omit it entirely to use {@link forBreakpointsTailwind}.\n *\n * Pass a function instead of a map to build it where `inject()` is available;\n * it runs once per injector that resolves the defaults.\n */\nexport function provideForBreakpointsDefaults(\n breakpoints: BreakpointMap | (() => BreakpointMap),\n): Provider[] {\n return provideDefaults(\n typeof breakpoints === 'function' ? () => ({ breakpoints: breakpoints() }) : { breakpoints },\n );\n}\n","import { Injector, computed, inject, runInInjectionContext, type Signal } from '@angular/core';\n\nimport { fortyError, injectMediaQuery } from 'forty-cdk/core';\nimport { FOR_BREAKPOINTS_DEFAULTS, type TailwindBreakpointName } from './breakpoints-defaults';\n\n/**\n * Extension point for typing custom breakpoint names. Augment it via module\n * declaration merging so `injectBreakpoints` autocompletes your own names\n * instead of the default Tailwind scale:\n *\n * ```ts\n * import type { appBreakpoints } from './breakpoints';\n *\n * declare module 'forty-cdk' {\n * interface BreakpointRegistry extends Record<keyof typeof appBreakpoints, true> {}\n * }\n * ```\n *\n * When left empty (no augmentation), {@link BreakpointName} falls back to the\n * keys of {@link forBreakpointsTailwind}.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface BreakpointRegistry {}\n\n/**\n * The set of valid breakpoint names. Resolves to the augmented\n * {@link BreakpointRegistry} keys when present, otherwise the Tailwind scale\n * (`'sm' | 'md' | 'lg' | 'xl' | '2xl'`).\n */\nexport type BreakpointName = [keyof BreakpointRegistry] extends [never]\n ? TailwindBreakpointName\n : keyof BreakpointRegistry & string;\n\n/** Reactive handle returned by {@link injectBreakpoints}. */\nexport interface ForBreakpoints<K extends string = BreakpointName> {\n /**\n * Matches the named breakpoint and wider — `(min-width: <threshold>px)`.\n * @param name A breakpoint declared in the active map.\n */\n up(name: K): Signal<boolean>;\n /**\n * Matches narrower than the named breakpoint —\n * `(max-width: <threshold - 0.02>px)`. The 0.02px back-off keeps `up(name)`\n * and `down(name)` from both matching at the exact threshold.\n * @param name A breakpoint declared in the active map.\n */\n down(name: K): Signal<boolean>;\n /**\n * Matches from `min` (inclusive) up to but not including `max`.\n * @param min Lower breakpoint, inclusive.\n * @param max Upper breakpoint, exclusive.\n */\n between(min: K, max: K): Signal<boolean>;\n /**\n * Matches only the named breakpoint's own band — from its threshold up to\n * but not including the next-larger breakpoint (open-ended for the largest).\n * @param name A breakpoint declared in the active map.\n */\n only(name: K): Signal<boolean>;\n /**\n * The largest breakpoint whose `min-width` currently matches, or `null` when\n * the viewport is narrower than the smallest breakpoint (and on the server).\n */\n readonly active: Signal<K | null>;\n /**\n * Escape hatch for an arbitrary media query string (orientation, pointer,\n * `prefers-color-scheme`, …) that the named helpers don't cover.\n * @param query Any valid media query.\n */\n matches(query: string): Signal<boolean>;\n}\n\n/**\n * A signal-first, zoneless, SSR-safe viewport breakpoint observer. Reads the\n * breakpoint map from the ambient {@link FOR_BREAKPOINTS_DEFAULTS} token\n * (configured with `provideForBreakpointsDefaults`, or the Tailwind fallback),\n * so call sites never repeat the breakpoint set:\n *\n * ```ts\n * private bp = injectBreakpoints();\n * protected isDesktop = this.bp.up('desktop');\n * protected active = this.bp.active;\n * ```\n *\n * Each query method returns a `Signal<boolean>` backed by a cached\n * `MediaQueryList`. The returned handle captures the calling injection\n * context, so its methods can be invoked lazily from `computed()` or a\n * template — not only during construction. Listeners are torn down with the\n * injector. `active`'s per-breakpoint queries are materialized eagerly at call\n * time, so reading `active` never attaches a listener from inside a reactive\n * computation or after teardown. On the server (or where `matchMedia` is\n * unavailable) every signal reads `false` and `active` reads `null`.\n *\n * Must be called from an injection context.\n *\n * @returns A {@link ForBreakpoints} handle of reactive query methods.\n */\nexport function injectBreakpoints(): ForBreakpoints {\n const injector = inject(Injector);\n const map = inject(FOR_BREAKPOINTS_DEFAULTS).breakpoints;\n const names = Object.keys(map).sort((a, b) => map[a]! - map[b]!);\n const cache = new Map<string, Signal<boolean>>();\n\n const observe = (query: string): Signal<boolean> => {\n let result = cache.get(query);\n if (result === undefined) {\n result = runInInjectionContext(injector, () => injectMediaQuery(query));\n cache.set(query, result);\n }\n return result;\n };\n\n const thresholdOf = (name: string): number => {\n const value = map[name];\n if (value === undefined) {\n throw fortyError({\n code: 'FORCDK-BREAKPOINTS-001',\n message: `Unknown breakpoint \"${name}\".`,\n cause: `The active breakpoint map defines: ${names.join(', ') || '(none)'}.`,\n fix: 'Use one of the names above, or register yours with provideForBreakpointsDefaults().',\n });\n }\n return value;\n };\n\n const up = (name: string): Signal<boolean> => observe(`(min-width: ${thresholdOf(name)}px)`);\n\n const down = (name: string): Signal<boolean> =>\n observe(`(max-width: ${thresholdOf(name) - 0.02}px)`);\n\n const between = (min: string, max: string): Signal<boolean> =>\n observe(`(min-width: ${thresholdOf(min)}px) and (max-width: ${thresholdOf(max) - 0.02}px)`);\n\n const only = (name: string): Signal<boolean> => {\n const lower = thresholdOf(name);\n const index = names.indexOf(name);\n const next = index < names.length - 1 ? thresholdOf(names[index + 1]!) : null;\n return next === null\n ? observe(`(min-width: ${lower}px)`)\n : observe(`(min-width: ${lower}px) and (max-width: ${next - 0.02}px)`);\n };\n\n const activeCandidates = names.map((name) => ({\n name,\n matches: observe(`(min-width: ${map[name]}px)`),\n }));\n\n const active = computed<BreakpointName | null>(() => {\n let current: string | null = null;\n for (const candidate of activeCandidates) {\n if (candidate.matches()) {\n current = candidate.name;\n }\n }\n return current as BreakpointName | null;\n });\n\n return { up, down, between, only, active, matches: (query) => observe(query) };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;AAYA;;;;;AAKG;AACI,MAAM,sBAAsB,GAAG;AACpC,IAAA,EAAE,EAAE,GAAG;AACP,IAAA,EAAE,EAAE,GAAG;AACP,IAAA,EAAE,EAAE,IAAI;AACR,IAAA,EAAE,EAAE,IAAI;AACR,IAAA,KAAK,EAAE,IAAI;;AAYb;;;;AAIG;AACI,MAAM,iCAAiC,GAA2B;AACvE,IAAA,WAAW,EAAE,sBAAsB;CACpC;AAED,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,GAAG,cAAc,CAC/C,0BAA0B,EAC1B,iCAAiC,CAClC;AAED;;;;AAIG;AACI,MAAM,wBAAwB,GAAG;AAExC;;;;;;;;;;;;;;;;AAgBG;AACG,SAAU,6BAA6B,CAC3C,WAAkD,EAAA;IAElD,OAAO,eAAe,CACpB,OAAO,WAAW,KAAK,UAAU,GAAG,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,CAC7F;AACH;;ACPA;;;;;;;;;;;;;;;;;;;;;;;;AAwBG;SACa,iBAAiB,GAAA;AAC/B,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IACjC,MAAM,GAAG,GAAG,MAAM,CAAC,wBAAwB,CAAC,CAAC,WAAW;AACxD,IAAA,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAE,GAAG,GAAG,CAAC,CAAC,CAAE,CAAC;AAChE,IAAA,MAAM,KAAK,GAAG,IAAI,GAAG,EAA2B;AAEhD,IAAA,MAAM,OAAO,GAAG,CAAC,KAAa,KAAqB;QACjD,IAAI,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;AAC7B,QAAA,IAAI,MAAM,KAAK,SAAS,EAAE;AACxB,YAAA,MAAM,GAAG,qBAAqB,CAAC,QAAQ,EAAE,MAAM,gBAAgB,CAAC,KAAK,CAAC,CAAC;AACvE,YAAA,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC;QAC1B;AACA,QAAA,OAAO,MAAM;AACf,IAAA,CAAC;AAED,IAAA,MAAM,WAAW,GAAG,CAAC,IAAY,KAAY;AAC3C,QAAA,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC;AACvB,QAAA,IAAI,KAAK,KAAK,SAAS,EAAE;AACvB,YAAA,MAAM,UAAU,CAAC;AACf,gBAAA,IAAI,EAAE,wBAAwB;gBAC9B,OAAO,EAAE,CAAA,oBAAA,EAAuB,IAAI,CAAA,EAAA,CAAI;gBACxC,KAAK,EAAE,CAAA,mCAAA,EAAsC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAA,CAAA,CAAG;AAC5E,gBAAA,GAAG,EAAE,qFAAqF;AAC3F,aAAA,CAAC;QACJ;AACA,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;AAED,IAAA,MAAM,EAAE,GAAG,CAAC,IAAY,KAAsB,OAAO,CAAC,CAAA,YAAA,EAAe,WAAW,CAAC,IAAI,CAAC,CAAA,GAAA,CAAK,CAAC;AAE5F,IAAA,MAAM,IAAI,GAAG,CAAC,IAAY,KACxB,OAAO,CAAC,CAAA,YAAA,EAAe,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA,GAAA,CAAK,CAAC;IAEvD,MAAM,OAAO,GAAG,CAAC,GAAW,EAAE,GAAW,KACvC,OAAO,CAAC,CAAA,YAAA,EAAe,WAAW,CAAC,GAAG,CAAC,CAAA,oBAAA,EAAuB,WAAW,CAAC,GAAG,CAAC,GAAG,IAAI,CAAA,GAAA,CAAK,CAAC;AAE7F,IAAA,MAAM,IAAI,GAAG,CAAC,IAAY,KAAqB;AAC7C,QAAA,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC;QAC/B,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QACjC,MAAM,IAAI,GAAG,KAAK,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAE,CAAC,GAAG,IAAI;QAC7E,OAAO,IAAI,KAAK;AACd,cAAE,OAAO,CAAC,CAAA,YAAA,EAAe,KAAK,KAAK;cACjC,OAAO,CAAC,CAAA,YAAA,EAAe,KAAK,CAAA,oBAAA,EAAuB,IAAI,GAAG,IAAI,CAAA,GAAA,CAAK,CAAC;AAC1E,IAAA,CAAC;IAED,MAAM,gBAAgB,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,MAAM;QAC5C,IAAI;QACJ,OAAO,EAAE,OAAO,CAAC,CAAA,YAAA,EAAe,GAAG,CAAC,IAAI,CAAC,CAAA,GAAA,CAAK,CAAC;AAChD,KAAA,CAAC,CAAC;AAEH,IAAA,MAAM,MAAM,GAAG,QAAQ,CAAwB,MAAK;QAClD,IAAI,OAAO,GAAkB,IAAI;AACjC,QAAA,KAAK,MAAM,SAAS,IAAI,gBAAgB,EAAE;AACxC,YAAA,IAAI,SAAS,CAAC,OAAO,EAAE,EAAE;AACvB,gBAAA,OAAO,GAAG,SAAS,CAAC,IAAI;YAC1B;QACF;AACA,QAAA,OAAO,OAAgC;IACzC,CAAC;+EAAC;IAEF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK,CAAC,EAAE;AAChF;;AC9JA;;AAEG;;;;"}
|
|
1
|
+
{"version":3,"file":"forty-cdk-breakpoints.mjs","sources":["../../../projects/forty-cdk/breakpoints/src/breakpoints-defaults.ts","../../../projects/forty-cdk/breakpoints/src/breakpoints.ts","../../../projects/forty-cdk/breakpoints/src/forty-cdk-breakpoints.ts"],"sourcesContent":["import { type Provider } from '@angular/core';\n\nimport { createDefaults } from 'forty-cdk/core';\n\n/**\n * A breakpoint map: each named breakpoint paired with its `min-width`\n * threshold in CSS pixels. Names are arbitrary — `injectBreakpoints` derives\n * its query methods (`up`, `down`, `between`, `only`) from whatever keys this\n * map declares, sorted ascending by threshold.\n */\nexport type BreakpointMap = Record<string, number>;\n\n/**\n * The default breakpoint map, mirroring Tailwind CSS's `sm`–`2xl` scale. Used\n * as the library fallback when no `provideForBreakpointsDefaults` is in scope,\n * and the source of the typed breakpoint names when the consumer has not\n * augmented {@link BreakpointRegistry}.\n */\nexport const forBreakpointsTailwind = {\n sm: 640,\n md: 768,\n lg: 1024,\n xl: 1280,\n '2xl': 1536,\n} as const satisfies BreakpointMap;\n\n/** The breakpoint names from the default Tailwind scale. */\nexport type TailwindBreakpointName = keyof typeof forBreakpointsTailwind;\n\n/** Resolved shape stored against {@link FOR_BREAKPOINTS_DEFAULTS}. */\nexport interface ForBreakpointsDefaults {\n /** The active breakpoint map for the current injector scope. */\n breakpoints: BreakpointMap;\n}\n\n/**\n * Library fallback for breakpoints defaults, read at the root injector when no\n * consumer has called `provideForBreakpointsDefaults`. Exported for the shared\n * defaults contract spec; not re-exported from the primitive's public entry.\n */\nexport const FOR_BREAKPOINTS_FALLBACK_DEFAULTS: ForBreakpointsDefaults = {\n breakpoints: forBreakpointsTailwind,\n};\n\nconst { token, provideDefaults } = createDefaults<ForBreakpointsDefaults>(\n 'FOR_BREAKPOINTS_DEFAULTS',\n FOR_BREAKPOINTS_FALLBACK_DEFAULTS,\n);\n\n/**\n * Token holding the resolved breakpoint map for the current injector scope.\n * The library always provides a fully-populated value (the Tailwind fallback\n * at the root, or the map from the nearest `provideForBreakpointsDefaults`).\n */\nexport const FOR_BREAKPOINTS_DEFAULTS = token;\n\n/**\n * Configures the breakpoint map for this injector scope so `injectBreakpoints`\n * can be called with no arguments. Provide it once in your application config:\n *\n * ```ts\n * providers: [\n * provideForBreakpointsDefaults({ mobile: 0, tablet: 640, laptop: 1024, desktop: 1280 }),\n * ]\n * ```\n *\n * Providing it again on a component injector replaces the map for that subtree\n * only (the nearest scope wins; the map is replaced wholesale, never merged\n * key-by-key). Omit it entirely to use {@link forBreakpointsTailwind}.\n *\n * Pass a function instead of a map to build it where `inject()` is available;\n * it runs once per injector that resolves the defaults.\n */\nexport function provideForBreakpointsDefaults(\n breakpoints: BreakpointMap | (() => BreakpointMap),\n): Provider[] {\n return provideDefaults(\n typeof breakpoints === 'function' ? () => ({ breakpoints: breakpoints() }) : { breakpoints },\n );\n}\n","import { computed, inject, type Signal } from '@angular/core';\n\nimport { MediaQueryRegistry, fortyError } from 'forty-cdk/core';\nimport { FOR_BREAKPOINTS_DEFAULTS, type TailwindBreakpointName } from './breakpoints-defaults';\n\n/**\n * Extension point for typing custom breakpoint names. Augment it via module\n * declaration merging so `injectBreakpoints` autocompletes your own names\n * instead of the default Tailwind scale:\n *\n * ```ts\n * import type { appBreakpoints } from './breakpoints';\n *\n * declare module 'forty-cdk' {\n * interface BreakpointRegistry extends Record<keyof typeof appBreakpoints, true> {}\n * }\n * ```\n *\n * When left empty (no augmentation), {@link BreakpointName} falls back to the\n * keys of {@link forBreakpointsTailwind}.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface BreakpointRegistry {}\n\n/**\n * The set of valid breakpoint names. Resolves to the augmented\n * {@link BreakpointRegistry} keys when present, otherwise the Tailwind scale\n * (`'sm' | 'md' | 'lg' | 'xl' | '2xl'`).\n */\nexport type BreakpointName = [keyof BreakpointRegistry] extends [never]\n ? TailwindBreakpointName\n : keyof BreakpointRegistry & string;\n\n/** Reactive handle returned by {@link injectBreakpoints}. */\nexport interface ForBreakpoints<K extends string = BreakpointName> {\n /**\n * Matches the named breakpoint and wider — `(min-width: <threshold>px)`.\n * @param name A breakpoint declared in the active map.\n */\n up(name: K): Signal<boolean>;\n /**\n * Matches narrower than the named breakpoint —\n * `(max-width: <threshold - 0.02>px)`. The 0.02px back-off keeps `up(name)`\n * and `down(name)` from both matching at the exact threshold.\n * @param name A breakpoint declared in the active map.\n */\n down(name: K): Signal<boolean>;\n /**\n * Matches from `min` (inclusive) up to but not including `max`.\n * @param min Lower breakpoint, inclusive.\n * @param max Upper breakpoint, exclusive.\n */\n between(min: K, max: K): Signal<boolean>;\n /**\n * Matches only the named breakpoint's own band — from its threshold up to\n * but not including the next-larger breakpoint (open-ended for the largest).\n * @param name A breakpoint declared in the active map.\n */\n only(name: K): Signal<boolean>;\n /**\n * The largest breakpoint whose `min-width` currently matches, or `null` when\n * the viewport is narrower than the smallest breakpoint (and on the server).\n */\n readonly active: Signal<K | null>;\n /**\n * Escape hatch for an arbitrary media query string (orientation, pointer,\n * `prefers-color-scheme`, …) that the named helpers don't cover.\n * @param query Any valid media query.\n */\n matches(query: string): Signal<boolean>;\n}\n\n/**\n * A signal-first, zoneless, SSR-safe viewport breakpoint observer. Reads the\n * breakpoint map from the ambient {@link FOR_BREAKPOINTS_DEFAULTS} token\n * (configured with `provideForBreakpointsDefaults`, or the Tailwind fallback),\n * so call sites never repeat the breakpoint set:\n *\n * ```ts\n * private bp = injectBreakpoints();\n * protected isDesktop = this.bp.up('desktop');\n * protected active = this.bp.active;\n * ```\n *\n * Each query method returns a `Signal<boolean>` backed by a `MediaQueryList`\n * shared across the application: any number of calls reading the same query\n * open it once, and its listener lives as long as the application. The\n * methods need no injection context, so they can be invoked lazily from\n * `computed()` or a template — not only during construction. `active`'s\n * per-breakpoint queries are materialized eagerly at call time, so reading\n * `active` never attaches a listener from inside a reactive computation. On\n * the server (or where `matchMedia` is unavailable) every signal reads\n * `false` and `active` reads `null`.\n *\n * Must be called from an injection context.\n *\n * @returns A {@link ForBreakpoints} handle of reactive query methods.\n */\nexport function injectBreakpoints(): ForBreakpoints {\n const registry = inject(MediaQueryRegistry);\n const map = inject(FOR_BREAKPOINTS_DEFAULTS).breakpoints;\n const names = Object.keys(map).sort((a, b) => map[a]! - map[b]!);\n\n const observe = (query: string): Signal<boolean> => registry.observe(query);\n\n const thresholdOf = (name: string): number => {\n const value = map[name];\n if (value === undefined) {\n throw fortyError({\n code: 'FORCDK-BREAKPOINTS-001',\n message: `Unknown breakpoint \"${name}\".`,\n cause: `The active breakpoint map defines: ${names.join(', ') || '(none)'}.`,\n fix: 'Use one of the names above, or register yours with provideForBreakpointsDefaults().',\n });\n }\n return value;\n };\n\n const up = (name: string): Signal<boolean> => observe(`(min-width: ${thresholdOf(name)}px)`);\n\n const down = (name: string): Signal<boolean> =>\n observe(`(max-width: ${thresholdOf(name) - 0.02}px)`);\n\n const between = (min: string, max: string): Signal<boolean> =>\n observe(`(min-width: ${thresholdOf(min)}px) and (max-width: ${thresholdOf(max) - 0.02}px)`);\n\n const only = (name: string): Signal<boolean> => {\n const lower = thresholdOf(name);\n const index = names.indexOf(name);\n const next = index < names.length - 1 ? thresholdOf(names[index + 1]!) : null;\n return next === null\n ? observe(`(min-width: ${lower}px)`)\n : observe(`(min-width: ${lower}px) and (max-width: ${next - 0.02}px)`);\n };\n\n const activeCandidates = names.map((name) => ({\n name,\n matches: observe(`(min-width: ${map[name]}px)`),\n }));\n\n const active = computed<BreakpointName | null>(() => {\n let current: string | null = null;\n for (const candidate of activeCandidates) {\n if (candidate.matches()) {\n current = candidate.name;\n }\n }\n return current as BreakpointName | null;\n });\n\n return { up, down, between, only, active, matches: (query) => observe(query) };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -971,6 +971,7 @@ class ForCalendar {
|
|
|
971
971
|
|
|
972
972
|
|
|
973
973
|
|
|
974
|
+
|
|
974
975
|
locale = input(null, /* @ts-ignore */
|
|
975
976
|
...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
|
|
976
977
|
#initialToday = this.adapter.today();
|
|
@@ -2042,8 +2043,16 @@ class NativeDateAdapter {
|
|
|
2042
2043
|
|
|
2043
2044
|
|
|
2044
2045
|
|
|
2046
|
+
|
|
2047
|
+
locale() {
|
|
2048
|
+
return null;
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
|
|
2052
|
+
|
|
2053
|
+
|
|
2045
2054
|
format(date, options, locale) {
|
|
2046
|
-
return this.#formatter(locale, options).format(date);
|
|
2055
|
+
return this.#formatter(locale ?? this.locale() ?? undefined, options).format(date);
|
|
2047
2056
|
}
|
|
2048
2057
|
supportsTime() {
|
|
2049
2058
|
return true;
|