@eifi1/ui-kit 0.34.0 → 0.35.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/README.md +82 -38
- package/dist/billing/billing-banner.d.ts +5 -2
- package/dist/billing/billing-banner.js.map +1 -1
- package/dist/billing/plan-limit-notice.d.ts +5 -2
- package/dist/billing/plan-limit-notice.js.map +1 -1
- package/dist/chart.js.map +1 -1
- package/dist/components/amount-input.d.ts +0 -3
- package/dist/components/amount-input.js +2 -2
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/calculator.d.ts +1 -4
- package/dist/components/calculator.js +1 -3
- package/dist/components/calculator.js.map +1 -1
- package/dist/components/chart-zoom.d.ts +0 -37
- package/dist/components/chart-zoom.js +1 -28
- package/dist/components/chart-zoom.js.map +1 -1
- package/dist/components/chip-input.d.ts +0 -4
- package/dist/components/chip-input.js +1 -5
- package/dist/components/chip-input.js.map +1 -1
- package/dist/components/combobox.d.ts +1 -1
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/copy-button.d.ts +5 -1
- package/dist/components/copy-button.js.map +1 -1
- package/dist/components/currency-option-list.d.ts +3 -8
- package/dist/components/currency-option-list.js +1 -29
- package/dist/components/currency-option-list.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +3 -3
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-filter-popover.d.ts +1 -1
- package/dist/components/data-table-filter-popover.js +1 -1
- package/dist/components/data-table-filter-popover.js.map +1 -1
- package/dist/components/data-table-labels.d.ts +1 -42
- package/dist/components/data-table-labels.js +2 -31
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table-shared.d.ts +17 -1
- package/dist/components/data-table-shared.js +22 -0
- package/dist/components/data-table-shared.js.map +1 -1
- package/dist/components/data-table.d.ts +12 -2
- package/dist/components/data-table.js +18 -14
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/dialog-frame.d.ts +3 -1
- package/dist/components/dialog-frame.js.map +1 -1
- package/dist/components/dropdown.d.ts +18 -15
- package/dist/components/dropdown.js +11 -3
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +1 -1
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/field-base.d.ts +0 -5
- package/dist/components/field-base.js +0 -5
- package/dist/components/field-base.js.map +1 -1
- package/dist/components/field-parts.d.ts +3 -11
- package/dist/components/field-parts.js +1 -9
- package/dist/components/field-parts.js.map +1 -1
- package/dist/components/field-strip.d.ts +8 -23
- package/dist/components/field-strip.js +24 -22
- package/dist/components/field-strip.js.map +1 -1
- package/dist/components/field-sync.d.ts +0 -4
- package/dist/components/field-sync.js +0 -4
- package/dist/components/field-sync.js.map +1 -1
- package/dist/components/field.d.ts +4 -4
- package/dist/components/field.js.map +1 -1
- package/dist/components/full-bleed-dialog.js +2 -2
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/hover-menu.d.ts +6 -13
- package/dist/components/hover-menu.js +3 -3
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/inline-entity-combobox.d.ts +1 -1
- package/dist/components/inline-entity-combobox.js.map +1 -1
- package/dist/components/modal.d.ts +9 -1
- package/dist/components/modal.js +7 -4
- package/dist/components/modal.js.map +1 -1
- package/dist/components/multi-select.d.ts +5 -16
- package/dist/components/multi-select.js +5 -10
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-input.d.ts +0 -3
- package/dist/components/number-input.js +2 -4
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/popover.js +2 -2
- package/dist/components/popover.js.map +1 -1
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/server-wake.d.ts +0 -6
- package/dist/components/server-wake.js.map +1 -1
- package/dist/components/settings-fields.d.ts +4 -2
- package/dist/components/settings-fields.js.map +1 -1
- package/dist/components/stat-tile.d.ts +8 -2
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/swipeable-row.d.ts +2 -17
- package/dist/components/swipeable-row.js +11 -16
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/tooltip.d.ts +9 -1
- package/dist/components/tooltip.js +20 -5
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/translation-review.d.ts +10 -2
- package/dist/components/translation-review.js +11 -3
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/use-table-state.d.ts +31 -3
- package/dist/components/use-table-state.js +53 -8
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/components/user-avatar.d.ts +2 -2
- package/dist/components/user-avatar.js.map +1 -1
- package/dist/components/wizard-stepper.d.ts +1 -4
- package/dist/components/wizard-stepper.js +2 -3
- package/dist/components/wizard-stepper.js.map +1 -1
- package/dist/currencies.d.ts +23 -0
- package/dist/currencies.js +38 -0
- package/dist/currencies.js.map +1 -0
- package/dist/demo/demo-banner.d.ts +7 -2
- package/dist/demo/demo-banner.js.map +1 -1
- package/dist/eslint/index.d.ts +83 -0
- package/dist/eslint/index.js +69 -0
- package/dist/eslint/index.js.map +1 -0
- package/dist/eslint/role-utilities.d.ts +48 -0
- package/dist/eslint/role-utilities.generated.d.ts +2 -0
- package/dist/eslint/role-utilities.generated.js +143 -0
- package/dist/eslint/role-utilities.generated.js.map +1 -0
- package/dist/eslint/role-utilities.js +43 -0
- package/dist/eslint/role-utilities.js.map +1 -0
- package/dist/feedback/feedback-menu.d.ts +2 -2
- package/dist/feedback/feedback-menu.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +3 -8
- package/dist/feedback/feedback-thread.js +0 -2
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/hooks/use-dismiss.d.ts +16 -3
- package/dist/hooks/use-dismiss.js +14 -2
- package/dist/hooks/use-dismiss.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +14 -4
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/en.js.map +2 -2
- package/dist/i18n/locales/fr.js +3 -2
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/it.js +3 -2
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/refs.d.ts +13 -0
- package/dist/lib/refs.js +15 -0
- package/dist/lib/refs.js.map +1 -0
- package/dist/rhf/fields.d.ts +10 -10
- package/dist/rhf/fields.js.map +1 -1
- package/dist/rhf/form-core.d.ts +5 -4
- package/dist/rhf/form-core.js.map +1 -1
- package/dist/rhf/form.d.ts +7 -36
- package/dist/rhf/form.js +1 -16
- package/dist/rhf/form.js.map +1 -1
- package/dist/rhf/line-items.d.ts +1 -1
- package/dist/rhf/line-items.js.map +1 -1
- package/dist/rhf/use-rhf-wizard-step.js.map +1 -1
- package/dist/rhf.js.map +1 -1
- package/dist/shell/option-switcher-menu.d.ts +5 -17
- package/dist/shell/option-switcher-menu.js +1 -3
- package/dist/shell/option-switcher-menu.js.map +1 -1
- package/dist/shell/role-switcher.d.ts +1 -4
- package/dist/shell/role-switcher.js +2 -3
- package/dist/shell/role-switcher.js.map +1 -1
- package/dist/shell/topbar-action-menu.d.ts +7 -19
- package/dist/shell/topbar-action-menu.js +1 -3
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell/topbar-controls.d.ts +3 -12
- package/dist/shell/topbar-controls.js +6 -9
- package/dist/shell/topbar-controls.js.map +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +2 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/viewport.d.ts +25 -2
- package/dist/testing/viewport.js +61 -10
- package/dist/testing/viewport.js.map +1 -1
- package/dist/theme/palette-presets.d.ts +0 -7
- package/dist/theme/palette-presets.js +0 -279
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/wizard/types.d.ts +0 -5
- package/dist/wizard/types.js +1 -5
- package/dist/wizard/types.js.map +1 -1
- package/package.json +9 -1
- package/src/billing/billing-banner.tsx +5 -2
- package/src/billing/plan-limit-notice.tsx +5 -2
- package/src/chart.ts +5 -3
- package/src/components/amount-input.tsx +2 -6
- package/src/components/calculator.tsx +2 -7
- package/src/components/chart-zoom.tsx +4 -47
- package/src/components/chip-input.tsx +0 -7
- package/src/components/combobox.tsx +1 -1
- package/src/components/copy-button.tsx +5 -1
- package/src/components/currency-option-list.tsx +8 -42
- package/src/components/danger-confirm.tsx +3 -3
- package/src/components/data-table-filter-popover.tsx +2 -1
- package/src/components/data-table-labels.ts +2 -84
- package/src/components/data-table-shared.tsx +55 -2
- package/src/components/data-table.tsx +29 -17
- package/src/components/dialog-frame.tsx +3 -1
- package/src/components/dropdown.tsx +42 -18
- package/src/components/entity-combobox.tsx +1 -1
- package/src/components/field-base.tsx +0 -11
- package/src/components/field-parts.tsx +5 -19
- package/src/components/field-strip.tsx +9 -25
- package/src/components/field-sync.tsx +0 -7
- package/src/components/field.tsx +4 -4
- package/src/components/full-bleed-dialog.tsx +4 -3
- package/src/components/hover-menu.tsx +12 -16
- package/src/components/inline-entity-combobox.tsx +1 -1
- package/src/components/modal.tsx +20 -4
- package/src/components/multi-select.tsx +13 -30
- package/src/components/number-input.tsx +3 -8
- package/src/components/popover.tsx +5 -3
- package/src/components/series-chart.tsx +1 -1
- package/src/components/server-wake.tsx +0 -6
- package/src/components/settings-fields.tsx +4 -2
- package/src/components/stat-tile.tsx +8 -2
- package/src/components/swipeable-row.tsx +15 -37
- package/src/components/tooltip.tsx +37 -6
- package/src/components/translation-review.tsx +25 -7
- package/src/components/use-table-state.ts +91 -10
- package/src/components/user-avatar.tsx +2 -2
- package/src/components/wizard-stepper.tsx +2 -6
- package/src/currencies.ts +70 -0
- package/src/demo/demo-banner.tsx +7 -2
- package/src/eslint/index.ts +161 -0
- package/src/eslint/role-utilities.generated.ts +146 -0
- package/src/eslint/role-utilities.ts +99 -0
- package/src/feedback/feedback-menu.tsx +2 -2
- package/src/feedback/feedback-thread.tsx +3 -8
- package/src/hooks/use-dismiss.ts +39 -4
- package/src/i18n/kit-labels.tsx +15 -5
- package/src/i18n/locales/fr.ts +3 -2
- package/src/i18n/locales/it.ts +3 -2
- package/src/index.ts +3 -1
- package/src/lib/refs.ts +22 -0
- package/src/rhf/fields.tsx +10 -10
- package/src/rhf/form-core.tsx +5 -4
- package/src/rhf/form.tsx +7 -43
- package/src/rhf/line-items.tsx +1 -1
- package/src/rhf/use-rhf-wizard-step.ts +4 -4
- package/src/rhf.ts +3 -2
- package/src/shell/option-switcher-menu.tsx +5 -22
- package/src/shell/role-switcher.tsx +2 -6
- package/src/shell/topbar-action-menu.tsx +7 -24
- package/src/shell/topbar-controls.tsx +6 -18
- package/src/testing/index.ts +3 -1
- package/src/testing/viewport.ts +125 -19
- package/src/theme/palette-presets.ts +7 -293
- package/src/wizard/types.ts +0 -8
package/src/rhf/fields.tsx
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* - **The red border.** Every field gets `invalid` from the field state, so an errored
|
|
28
28
|
* field PAINTS as well as announces. kastlan's number, money, date and entity fields
|
|
29
29
|
* passed only `aria-invalid` through `FormControl`, or nothing, and `Input`, `Select`
|
|
30
|
-
* and `Textarea` paint only from the prop (see the note in ./form).
|
|
30
|
+
* and `Textarea` paint only from the prop (see the note in ./form-core).
|
|
31
31
|
* - **Focus on error.** `field.ref` reaches the focusable element itself — the input,
|
|
32
32
|
* a picker's trigger, a toggle group whose `focus()` lands on its option (since 0.34
|
|
33
33
|
* every kit field takes a ref; the number field and the pickers were reached by an id
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
* through react-hook-form's context. Pass it for the field's value types
|
|
38
38
|
* (`control={form.control}` checks `name` against the form's values).
|
|
39
39
|
*
|
|
40
|
-
* The common props: `name`, `control`, `label` (
|
|
40
|
+
* The common props: `name`, `control`, `label` (the label above the field;
|
|
41
41
|
* omitted, none — then give the field an `aria-label`), `hint` (standing advice under
|
|
42
42
|
* the field, attached to it with `aria-describedby`), `required` (the label's mark,
|
|
43
43
|
* and `aria-required` where the control takes it — validation is `rules`' or the
|
|
@@ -110,7 +110,7 @@ export interface RhfFieldBaseProps<
|
|
|
110
110
|
control?: ControllerProps<TFieldValues, TName, TTransformed>["control"];
|
|
111
111
|
/** react-hook-form's `rules` (`required`, `min`, `validate` …). */
|
|
112
112
|
rules?: ControllerProps<TFieldValues, TName, TTransformed>["rules"];
|
|
113
|
-
/**
|
|
113
|
+
/** The label above the field. Omitted: no label — name the control with
|
|
114
114
|
* an `aria-label` instead. */
|
|
115
115
|
label?: ReactNode;
|
|
116
116
|
/** Standing advice under the field ("As printed on the card"), attached to the
|
|
@@ -146,8 +146,8 @@ export interface RhfFieldRenderContext<
|
|
|
146
146
|
invalid: boolean;
|
|
147
147
|
/** The error's message, if any. */
|
|
148
148
|
error: string | undefined;
|
|
149
|
-
/** The control's id, which the label points at.
|
|
150
|
-
*
|
|
149
|
+
/** The control's id, which the label points at. The rendered element has it already
|
|
150
|
+
* (unless `asControl` is false); this is for a control that routes it elsewhere. */
|
|
151
151
|
id: string;
|
|
152
152
|
/** The label's id, for a control that is named with `aria-labelledby`. */
|
|
153
153
|
labelId: string;
|
|
@@ -160,16 +160,16 @@ export interface RhfFieldProps<
|
|
|
160
160
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
161
161
|
TTransformed = TFieldValues,
|
|
162
162
|
> extends RhfFieldBaseProps<TFieldValues, TName, TTransformed> {
|
|
163
|
-
/** The control.
|
|
164
|
-
* `
|
|
163
|
+
/** The control. Given the field's `id`, `aria-describedby` and `aria-invalid` unless
|
|
164
|
+
* `asControl` is false. */
|
|
165
165
|
render: (ctx: RhfFieldRenderContext<TFieldValues, TName>) => ReactElement;
|
|
166
166
|
/** `false`: render the control as is, for one that wires its own ids from the
|
|
167
167
|
* context. Default `true`. */
|
|
168
168
|
asControl?: boolean;
|
|
169
|
-
/** `false`: no
|
|
169
|
+
/** `false`: no error message under the field, for a control that renders its own (its
|
|
170
170
|
* `error` prop). Default `true`. */
|
|
171
171
|
message?: boolean;
|
|
172
|
-
/** `false`: no
|
|
172
|
+
/** `false`: no label above the field, for a control that carries its label itself (a
|
|
173
173
|
* checkbox). Default `true`. */
|
|
174
174
|
showLabel?: boolean;
|
|
175
175
|
}
|
|
@@ -1622,7 +1622,7 @@ export type RhfToggleGroupProps<
|
|
|
1622
1622
|
* asked for it with {@link RhfTimeInput} and {@link RhfDateRangePicker}: the controls
|
|
1623
1623
|
* its forms still had to wire by hand through {@link RhfField}'s render.
|
|
1624
1624
|
*
|
|
1625
|
-
* The label is the
|
|
1625
|
+
* The label is the field's own label above the bare group, as on every other
|
|
1626
1626
|
* bound field — the `labelPlacement="above"` shape, not the group's own field chrome —
|
|
1627
1627
|
* and it names the group (`aria-labelledby`); the hint and the message describe it,
|
|
1628
1628
|
* and an error paints the group's border. Focus on error goes to the option a
|
package/src/rhf/form-core.tsx
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The react-hook-form adapter: the five parts every
|
|
3
|
-
* they share. `@eifi1/ui-kit/rhf
|
|
2
|
+
* The react-hook-form adapter: the five parts every bound field is built from, and the
|
|
3
|
+
* hook they share. Behind `@eifi1/ui-kit/rhf`, which exports `Form` alone of them.
|
|
4
4
|
*
|
|
5
5
|
* The kit's own module since 0.34 (refactor audit 2026-10, API-6f): the bound `Rhf*`
|
|
6
|
-
* fields build on these parts, and import them from here. ./form is the
|
|
7
|
-
*
|
|
6
|
+
* fields and `RhfField` build on these parts, and import them from here. ./form is the
|
|
7
|
+
* public face — `Form`; the parts left the public surface in 0.35 (deprecated in 0.34),
|
|
8
|
+
* when no app imported them any more.
|
|
8
9
|
*
|
|
9
10
|
* ```tsx
|
|
10
11
|
* <Form {...form}>
|
package/src/rhf/form.tsx
CHANGED
|
@@ -1,48 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `Form
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* `Form` — the public face of ./form-core, where the shadcn scaffold the bound fields are
|
|
3
|
+
* built from lives (its header has the whole story: what differs from shadcn's file, why
|
|
4
|
+
* /rhf is its own entry, how a picker clears).
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* The scaffold itself (`FormField`, `useFormField`, `FormItem`, `FormLabel`, `FormControl`,
|
|
7
|
+
* `FormDescription`, `FormMessage` and five props types) was public until 0.35 (refactor
|
|
8
|
+
* audit 2026-10, API-6f; deprecated in 0.34): no app builds a form from it any more.
|
|
7
9
|
* kastlan, the one /rhf consumer, uses `Form`, the bound `Rhf*` fields and `RhfField` for
|
|
8
|
-
* a control of its own
|
|
9
|
-
* surface in 0.35. The bound fields import the parts from the core, so the removal is
|
|
10
|
-
* the block below.
|
|
10
|
+
* a control of its own — the same parts, folded into one element.
|
|
11
11
|
*/
|
|
12
|
-
import * as core from "./form-core";
|
|
13
|
-
|
|
14
12
|
export { Form } from "./form-core";
|
|
15
|
-
|
|
16
|
-
/* ── Deprecated 0.34, removed in 0.35 ───────────────────────────────────────
|
|
17
|
-
* Each is the core's own value under its public name. The replacement for all of them:
|
|
18
|
-
* a bound field (`RhfTextField`, `RhfSelect`, …), or `RhfField` around a control of
|
|
19
|
-
* your own — the same parts, folded into one element. */
|
|
20
|
-
|
|
21
|
-
/** @deprecated 0.34 — the bound `Rhf*` fields, or `RhfField` for a control of your own;
|
|
22
|
-
* removed in 0.35 (API-6f). */
|
|
23
|
-
export const FormField = core.FormField;
|
|
24
|
-
/** @deprecated 0.34 — `RhfField`, which reads the field's ids itself; removed in 0.35
|
|
25
|
-
* (API-6f). */
|
|
26
|
-
export const useFormField = core.useFormField;
|
|
27
|
-
/** @deprecated 0.34 — the bound `Rhf*` fields, or `RhfField`; removed in 0.35 (API-6f). */
|
|
28
|
-
export const FormItem = core.FormItem;
|
|
29
|
-
/** @deprecated 0.34 — the bound fields' and `RhfField`'s `label`; removed in 0.35 (API-6f). */
|
|
30
|
-
export const FormLabel = core.FormLabel;
|
|
31
|
-
/** @deprecated 0.34 — `RhfField`, which wires its control the same way; removed in 0.35
|
|
32
|
-
* (API-6f). */
|
|
33
|
-
export const FormControl = core.FormControl;
|
|
34
|
-
/** @deprecated 0.34 — the bound fields' and `RhfField`'s `hint`; removed in 0.35 (API-6f). */
|
|
35
|
-
export const FormDescription = core.FormDescription;
|
|
36
|
-
/** @deprecated 0.34 — the bound fields and `RhfField`, which show the error themselves;
|
|
37
|
-
* removed in 0.35 (API-6f). */
|
|
38
|
-
export const FormMessage = core.FormMessage;
|
|
39
|
-
/** @deprecated 0.34 — goes with `FormItem`; removed in 0.35 (API-6f). */
|
|
40
|
-
export type FormItemProps = core.FormItemProps;
|
|
41
|
-
/** @deprecated 0.34 — goes with `FormLabel`; removed in 0.35 (API-6f). */
|
|
42
|
-
export type FormLabelProps = core.FormLabelProps;
|
|
43
|
-
/** @deprecated 0.34 — goes with `FormControl`; removed in 0.35 (API-6f). */
|
|
44
|
-
export type FormControlProps = core.FormControlProps;
|
|
45
|
-
/** @deprecated 0.34 — goes with `FormDescription`; removed in 0.35 (API-6f). */
|
|
46
|
-
export type FormDescriptionProps = core.FormDescriptionProps;
|
|
47
|
-
/** @deprecated 0.34 — goes with `FormMessage`; removed in 0.35 (API-6f). */
|
|
48
|
-
export type FormMessageProps = core.FormMessageProps;
|
package/src/rhf/line-items.tsx
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* `summary`, `fieldLabels`, `narrowColumns`, `removePlacement` and `removeAlign` pass
|
|
28
28
|
* through to {@link LineItems}. With
|
|
29
29
|
* `fieldLabels="floating"` a cell's `fieldLabel` is the column's name; the bound
|
|
30
|
-
* fields' own `label` is a
|
|
30
|
+
* fields' own `label` is a label ABOVE the control, so for the floating
|
|
31
31
|
* one render the kit field through `RhfField`:
|
|
32
32
|
* `render={({ field, invalid }) => <Input label={fieldLabel} aria-label={label} {...field} invalid={invalid} />}`.
|
|
33
33
|
*/
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Inside a `<StepperNav>` the hook registers the step's validator with the wizard, so
|
|
14
14
|
* Next (and Finish on a form-bearing last step) runs it: on success the values go to
|
|
15
|
-
* `onValid` and the wizard advances; on failure
|
|
16
|
-
*
|
|
17
|
-
* since the result carries no per-field errors, says its "fill in the
|
|
18
|
-
* fields" line so the button did not seem to do nothing. It also RETURNS the
|
|
15
|
+
* `onValid` and the wizard advances; on failure the bound fields show their inline
|
|
16
|
+
* messages, react-hook-form focuses the first field in error, and the wizard stays
|
|
17
|
+
* put — and, since the result carries no per-field errors, says its "fill in the
|
|
18
|
+
* required fields" line so the button did not seem to do nothing. It also RETURNS the
|
|
19
19
|
* validator, for a nav of your own outside `<StepperNav>` (where there is no wizard
|
|
20
20
|
* context to register with, so nothing is registered).
|
|
21
21
|
*
|
package/src/rhf.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// `@eifi1/ui-kit/rhf` — the react-hook-form adapter: Form and the bound Rhf* fields (and
|
|
2
|
-
// RhfField as the escape hatch). The shadcn scaffold (FormField,
|
|
3
|
-
// FormControl, FormDescription, FormMessage, useFormField)
|
|
2
|
+
// RhfField as the escape hatch). The shadcn scaffold they are built from (FormField,
|
|
3
|
+
// FormItem, FormLabel, FormControl, FormDescription, FormMessage, useFormField) left the
|
|
4
|
+
// surface in 0.35 (refactor audit 2026-10, API-6f; deprecated in 0.34).
|
|
4
5
|
//
|
|
5
6
|
// The only entry point in the package that requires `react-hook-form`, and an optional
|
|
6
7
|
// peer for that reason: an app without a form library installs nothing for it. NOT
|
|
@@ -9,23 +9,6 @@ export interface OptionSwitcherOption<T extends string> {
|
|
|
9
9
|
label: ReactNode;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* Names the trigger and the menu: one of the two spellings is required (API-1, refactor
|
|
14
|
-
* audit 2026-10). `aria-label` wins when both are given. TopBarActionMenu has the same
|
|
15
|
-
* type; both become a plain required `aria-label` when `ariaLabel` goes in 0.35.
|
|
16
|
-
*/
|
|
17
|
-
type RequiredMenuName =
|
|
18
|
-
| {
|
|
19
|
-
"aria-label": string;
|
|
20
|
-
/** @deprecated 0.34 — use `aria-label`; removed in 0.35. */
|
|
21
|
-
ariaLabel?: string;
|
|
22
|
-
}
|
|
23
|
-
| {
|
|
24
|
-
"aria-label"?: string;
|
|
25
|
-
/** @deprecated 0.34 — use `aria-label`; removed in 0.35. */
|
|
26
|
-
ariaLabel: string;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
12
|
/**
|
|
30
13
|
* A top-bar hover menu that switches between a small set of options, with a
|
|
31
14
|
* check on the active one and an optional heading. Generic glue over
|
|
@@ -34,8 +17,7 @@ type RequiredMenuName =
|
|
|
34
17
|
*/
|
|
35
18
|
export function OptionSwitcherMenu<T extends string>({
|
|
36
19
|
icon,
|
|
37
|
-
"aria-label":
|
|
38
|
-
ariaLabel: ariaLabelProp,
|
|
20
|
+
"aria-label": ariaLabel,
|
|
39
21
|
title,
|
|
40
22
|
heading,
|
|
41
23
|
options,
|
|
@@ -43,14 +25,15 @@ export function OptionSwitcherMenu<T extends string>({
|
|
|
43
25
|
onSelect,
|
|
44
26
|
}: {
|
|
45
27
|
icon: ReactNode;
|
|
28
|
+
/** Names the trigger and the menu. Required: the trigger is an icon (refactor audit
|
|
29
|
+
* 2026-10, API-1 — the `ariaLabel` spelling went in 0.35). */
|
|
30
|
+
"aria-label": string;
|
|
46
31
|
title?: string;
|
|
47
32
|
heading?: string;
|
|
48
33
|
options: OptionSwitcherOption<T>[];
|
|
49
34
|
value: T;
|
|
50
35
|
onSelect: (value: T) => void;
|
|
51
|
-
}
|
|
52
|
-
// The DOM spelling wins; `ariaLabel` is the fallback for call sites not moved yet.
|
|
53
|
-
const ariaLabel = ariaLabelAttr ?? ariaLabelProp;
|
|
36
|
+
}) {
|
|
54
37
|
return (
|
|
55
38
|
<HoverMenu
|
|
56
39
|
aria-label={ariaLabel}
|
|
@@ -8,9 +8,6 @@ export interface RoleSwitcherProps<R extends string> {
|
|
|
8
8
|
onChange: (role: R) => void;
|
|
9
9
|
/** Default: `topBar.switchRole` from the {@link UiKitProvider}, else English. */
|
|
10
10
|
"aria-label"?: string;
|
|
11
|
-
/** @deprecated 0.34 — use `aria-label`; removed in 0.35 (refactor audit 2026-10, API-1).
|
|
12
|
-
* Names the switcher only when `aria-label` is absent. */
|
|
13
|
-
ariaLabel?: string;
|
|
14
11
|
heading?: string;
|
|
15
12
|
/** The trigger's tooltip. Default: `topBar.role(value)`. */
|
|
16
13
|
title?: string;
|
|
@@ -30,12 +27,11 @@ export function RoleSwitcher<R extends string>({
|
|
|
30
27
|
roles,
|
|
31
28
|
value,
|
|
32
29
|
onChange,
|
|
33
|
-
"aria-label":
|
|
34
|
-
ariaLabel,
|
|
30
|
+
"aria-label": ariaLabel,
|
|
35
31
|
heading,
|
|
36
32
|
title,
|
|
37
33
|
}: RoleSwitcherProps<R>) {
|
|
38
|
-
const labels = useKitLabels("topBar", DEFAULT_TOP_BAR_LABELS, { switchRole:
|
|
34
|
+
const labels = useKitLabels("topBar", DEFAULT_TOP_BAR_LABELS, { switchRole: ariaLabel });
|
|
39
35
|
return (
|
|
40
36
|
<OptionSwitcherMenu<R>
|
|
41
37
|
aria-label={labels.switchRole}
|
|
@@ -179,23 +179,6 @@ function keepTabInsidePanel(e: ReactKeyboardEvent<HTMLUListElement>) {
|
|
|
179
179
|
if (stops.some((el) => el !== from && from.compareDocumentPosition(el) & way)) e.stopPropagation();
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
/**
|
|
183
|
-
* Names the trigger and the menu: one of the two spellings is required (API-1, refactor
|
|
184
|
-
* audit 2026-10). `aria-label` wins when both are given. OptionSwitcherMenu has the same
|
|
185
|
-
* type; both become a plain required `aria-label` when `ariaLabel` goes in 0.35.
|
|
186
|
-
*/
|
|
187
|
-
type RequiredMenuName =
|
|
188
|
-
| {
|
|
189
|
-
"aria-label": string;
|
|
190
|
-
/** @deprecated 0.34 — use `aria-label`; removed in 0.35. */
|
|
191
|
-
ariaLabel?: string;
|
|
192
|
-
}
|
|
193
|
-
| {
|
|
194
|
-
"aria-label"?: string;
|
|
195
|
-
/** @deprecated 0.34 — use `aria-label`; removed in 0.35. */
|
|
196
|
-
ariaLabel: string;
|
|
197
|
-
};
|
|
198
|
-
|
|
199
182
|
/**
|
|
200
183
|
* A top-bar icon button that opens a {@link HoverMenu} of actions and/or links,
|
|
201
184
|
* with an optional uppercase heading and divider rules. Generic glue over the
|
|
@@ -219,8 +202,7 @@ export function TopBarActionMenu({
|
|
|
219
202
|
trigger,
|
|
220
203
|
triggerClassName,
|
|
221
204
|
href,
|
|
222
|
-
"aria-label":
|
|
223
|
-
ariaLabel: ariaLabelProp,
|
|
205
|
+
"aria-label": ariaLabel,
|
|
224
206
|
heading,
|
|
225
207
|
header,
|
|
226
208
|
entries,
|
|
@@ -265,6 +247,9 @@ export function TopBarActionMenu({
|
|
|
265
247
|
* inside interactive content.
|
|
266
248
|
*/
|
|
267
249
|
href?: string;
|
|
250
|
+
/** Names the trigger and the menu. Required: the trigger is an icon (refactor audit
|
|
251
|
+
* 2026-10, API-1 — the `ariaLabel` spelling went in 0.35). */
|
|
252
|
+
"aria-label": string;
|
|
268
253
|
heading?: string;
|
|
269
254
|
/** A non-interactive identity block above the rows. */
|
|
270
255
|
header?: TopBarMenuHeader;
|
|
@@ -283,13 +268,11 @@ export function TopBarActionMenu({
|
|
|
283
268
|
footer?: ReactNode | ((close: () => void) => ReactNode);
|
|
284
269
|
/** Wraps the footer in a `<nav>` with this name ("Legal"). Without it, a plain block. */
|
|
285
270
|
footerLabel?: string;
|
|
286
|
-
/** Which edge of the trigger the panel lines up with
|
|
287
|
-
*
|
|
271
|
+
/** Which edge of the trigger the panel lines up with, `start` or `end` in the reading
|
|
272
|
+
* direction (see `HoverMenu`, whose type this is). Default `end`. */
|
|
288
273
|
align?: HoverMenuProps["align"];
|
|
289
274
|
panelClassName?: string;
|
|
290
|
-
}
|
|
291
|
-
// The DOM spelling wins; `ariaLabel` is the fallback for call sites not moved yet.
|
|
292
|
-
const ariaLabel = ariaLabelAttr ?? ariaLabelProp;
|
|
275
|
+
}) {
|
|
293
276
|
// Names the groups' headings for `aria-labelledby`. Positional rather than from the
|
|
294
277
|
// entry's `key`, which is the caller's text and may hold a space — and a space in
|
|
295
278
|
// `aria-labelledby` is a list separator.
|
|
@@ -29,8 +29,7 @@ function MenuHeading({ children }: { children: string }) {
|
|
|
29
29
|
export function ThemeToggle({
|
|
30
30
|
mode,
|
|
31
31
|
onToggle,
|
|
32
|
-
"aria-label":
|
|
33
|
-
ariaLabel,
|
|
32
|
+
"aria-label": ariaLabel,
|
|
34
33
|
title,
|
|
35
34
|
className,
|
|
36
35
|
}: {
|
|
@@ -38,15 +37,12 @@ export function ThemeToggle({
|
|
|
38
37
|
onToggle: () => void;
|
|
39
38
|
/** Default: `topBar.theme` from the {@link UiKitProvider}, else English. */
|
|
40
39
|
"aria-label"?: string;
|
|
41
|
-
/** @deprecated 0.34 — use `aria-label`; removed in 0.35 (refactor audit 2026-10, API-1).
|
|
42
|
-
* Names the control only when `aria-label` is absent. */
|
|
43
|
-
ariaLabel?: string;
|
|
44
40
|
title?: string;
|
|
45
41
|
className?: string;
|
|
46
42
|
}) {
|
|
47
43
|
// This had no default at all, so a toggle rendered without `aria-label` was an
|
|
48
44
|
// icon button with no name — a reader heard "button" and nothing else.
|
|
49
|
-
const labels = useKitLabels("topBar", DEFAULT_TOP_BAR_LABELS, { theme:
|
|
45
|
+
const labels = useKitLabels("topBar", DEFAULT_TOP_BAR_LABELS, { theme: ariaLabel });
|
|
50
46
|
const Icon = mode === "dark" ? Moon : Sun;
|
|
51
47
|
return (
|
|
52
48
|
<Tooltip label={title} portal>
|
|
@@ -69,8 +65,7 @@ export function PaletteMenu({
|
|
|
69
65
|
activeId,
|
|
70
66
|
mode,
|
|
71
67
|
onSelect,
|
|
72
|
-
"aria-label":
|
|
73
|
-
ariaLabel,
|
|
68
|
+
"aria-label": ariaLabel,
|
|
74
69
|
heading,
|
|
75
70
|
}: {
|
|
76
71
|
palettes: PalettePreset[];
|
|
@@ -79,13 +74,10 @@ export function PaletteMenu({
|
|
|
79
74
|
onSelect: (id: string) => void;
|
|
80
75
|
/** Default: `topBar.palette` from the {@link UiKitProvider}, else English. */
|
|
81
76
|
"aria-label"?: string;
|
|
82
|
-
/** @deprecated 0.34 — use `aria-label`; removed in 0.35 (refactor audit 2026-10, API-1).
|
|
83
|
-
* Names the control only when `aria-label` is absent. */
|
|
84
|
-
ariaLabel?: string;
|
|
85
77
|
/** Optional small heading row above the list. */
|
|
86
78
|
heading?: string;
|
|
87
79
|
}) {
|
|
88
|
-
const labels = useKitLabels("topBar", DEFAULT_TOP_BAR_LABELS, { palette:
|
|
80
|
+
const labels = useKitLabels("topBar", DEFAULT_TOP_BAR_LABELS, { palette: ariaLabel });
|
|
89
81
|
const activeName = palettes.find((p) => p.id === activeId)?.name ?? activeId;
|
|
90
82
|
return (
|
|
91
83
|
<HoverMenu
|
|
@@ -177,8 +169,7 @@ export function LanguageMenu({
|
|
|
177
169
|
options,
|
|
178
170
|
current,
|
|
179
171
|
onChange,
|
|
180
|
-
"aria-label":
|
|
181
|
-
ariaLabel,
|
|
172
|
+
"aria-label": ariaLabel,
|
|
182
173
|
heading,
|
|
183
174
|
}: {
|
|
184
175
|
options: LanguageOption[];
|
|
@@ -186,14 +177,11 @@ export function LanguageMenu({
|
|
|
186
177
|
onChange: (code: string) => void;
|
|
187
178
|
/** Default: `topBar.language` from the {@link UiKitProvider}, else English. */
|
|
188
179
|
"aria-label"?: string;
|
|
189
|
-
/** @deprecated 0.34 — use `aria-label`; removed in 0.35 (refactor audit 2026-10, API-1).
|
|
190
|
-
* Names the control only when `aria-label` is absent. */
|
|
191
|
-
ariaLabel?: string;
|
|
192
180
|
/** Optional small heading row above the list — as on `TopBarActionMenu`,
|
|
193
181
|
* `OptionSwitcherMenu` and `PaletteMenu`. */
|
|
194
182
|
heading?: string;
|
|
195
183
|
}) {
|
|
196
|
-
const labels = useKitLabels("topBar", DEFAULT_TOP_BAR_LABELS, { language:
|
|
184
|
+
const labels = useKitLabels("topBar", DEFAULT_TOP_BAR_LABELS, { language: ariaLabel });
|
|
197
185
|
const active = options.find((o) => o.code === current);
|
|
198
186
|
return (
|
|
199
187
|
<HoverMenu
|
package/src/testing/index.ts
CHANGED
|
@@ -13,8 +13,10 @@ export {
|
|
|
13
13
|
} from "./css-variables";
|
|
14
14
|
// 0.34 (refactor audit 2026-10, TST-10): the environment jsdom lacks — a `matchMedia`
|
|
15
15
|
// that answers from a viewport width (right at every text size) and a `ResizeObserver`
|
|
16
|
-
// the test drives. Framework-free: each returns its own undo.
|
|
16
|
+
// the test drives. Framework-free: each returns its own undo. 0.35: `setViewportWidth`,
|
|
17
|
+
// a live resize of the stub in force — its lists flip and fire `change` (keksdose).
|
|
17
18
|
export {
|
|
19
|
+
setViewportWidth,
|
|
18
20
|
stubResizeObserver,
|
|
19
21
|
stubViewportWidth,
|
|
20
22
|
viewportMatchMedia,
|
package/src/testing/viewport.ts
CHANGED
|
@@ -85,14 +85,91 @@ function condition(part: string, width: number, options: ViewportOptions): boole
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/** A whole query: comma lists are alternatives, `and` joins conditions, as in CSS. */
|
|
89
|
+
function answer(query: string, width: number, options: ViewportOptions): boolean {
|
|
90
|
+
return query
|
|
91
|
+
.split(",")
|
|
92
|
+
.some((alternative) =>
|
|
93
|
+
alternative.split(/\s+and\s+/).every((part) => condition(part, width, options)),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** A list {@link viewportMatchMedia} handed out: what {@link setViewportWidth} flips. */
|
|
98
|
+
interface StubMediaQueryList {
|
|
99
|
+
matches: boolean;
|
|
100
|
+
readonly media: string;
|
|
101
|
+
onchange: ((this: MediaQueryList, event: MediaQueryListEvent) => unknown) | null;
|
|
102
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject | null): void;
|
|
103
|
+
removeEventListener(type: string, listener: EventListenerOrEventListenerObject | null): void;
|
|
104
|
+
addListener(listener: EventListenerOrEventListenerObject | null): void;
|
|
105
|
+
removeListener(listener: EventListenerOrEventListenerObject | null): void;
|
|
106
|
+
dispatchEvent(event: Event): boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** One stub's viewport: its width now, and every list it has handed out. */
|
|
110
|
+
interface StubViewport {
|
|
111
|
+
width: number;
|
|
112
|
+
readonly options: ViewportOptions;
|
|
113
|
+
readonly lists: Set<StubMediaQueryList>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Each `matchMedia` {@link viewportMatchMedia} made → its viewport. Keyed by the function,
|
|
117
|
+
* so {@link setViewportWidth} finds whichever stub is in force however it was put in. */
|
|
118
|
+
const VIEWPORTS = new WeakMap<object, StubViewport>();
|
|
119
|
+
|
|
120
|
+
function mediaQueryList(query: string, viewport: StubViewport): StubMediaQueryList {
|
|
121
|
+
// One set for both APIs: in a browser `addListener(fn)` IS `addEventListener("change",
|
|
122
|
+
// fn)`, so adding the same function through both registers it once.
|
|
123
|
+
const listeners = new Set<EventListenerOrEventListenerObject>();
|
|
124
|
+
const list: StubMediaQueryList = {
|
|
125
|
+
matches: answer(query, viewport.width, viewport.options),
|
|
126
|
+
media: query,
|
|
127
|
+
onchange: null,
|
|
128
|
+
addEventListener(type, listener) {
|
|
129
|
+
if (type === "change" && listener) listeners.add(listener);
|
|
130
|
+
},
|
|
131
|
+
removeEventListener(type, listener) {
|
|
132
|
+
if (type === "change" && listener) listeners.delete(listener);
|
|
133
|
+
},
|
|
134
|
+
addListener(listener) {
|
|
135
|
+
if (listener) listeners.add(listener);
|
|
136
|
+
},
|
|
137
|
+
removeListener(listener) {
|
|
138
|
+
if (listener) listeners.delete(listener);
|
|
139
|
+
},
|
|
140
|
+
dispatchEvent(event) {
|
|
141
|
+
if (event.type !== "change") return true;
|
|
142
|
+
const self = list as unknown as MediaQueryList;
|
|
143
|
+
list.onchange?.call(self, event as MediaQueryListEvent);
|
|
144
|
+
for (const listener of [...listeners]) {
|
|
145
|
+
if (typeof listener === "function") listener.call(self, event);
|
|
146
|
+
else listener.handleEvent(event);
|
|
147
|
+
}
|
|
148
|
+
return !event.defaultPrevented;
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
return list;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The `change` a browser fires at a list whose answer flipped: a `MediaQueryListEvent`'s
|
|
155
|
+
* `matches` and `media`, on an `Event` (jsdom has no `MediaQueryListEvent`). */
|
|
156
|
+
function changeEvent(list: StubMediaQueryList): Event {
|
|
157
|
+
const event = new Event("change");
|
|
158
|
+
for (const [key, value] of Object.entries({ matches: list.matches, media: list.media, target: list, currentTarget: list })) {
|
|
159
|
+
Object.defineProperty(event, key, { value, enumerable: true });
|
|
160
|
+
}
|
|
161
|
+
return event;
|
|
162
|
+
}
|
|
163
|
+
|
|
88
164
|
/**
|
|
89
165
|
* A `window.matchMedia` for a viewport `width` CSS px wide.
|
|
90
166
|
*
|
|
91
167
|
* Width queries answer from `width`: the range syntax the kit writes for `max-*`
|
|
92
168
|
* (`(width < 768px)`) and every other comparison, `(min-width: …)` and `(max-width: …)`,
|
|
93
169
|
* in px or rem; `and` and comma lists combine as in CSS. The features in `options` answer
|
|
94
|
-
* as set there; every other query is false. The lists
|
|
95
|
-
*
|
|
170
|
+
* as set there; every other query is false. The lists keep their listeners, and
|
|
171
|
+
* {@link setViewportWidth} resizes the viewport under them the way a browser window
|
|
172
|
+
* does: each list whose answer flips fires `change`.
|
|
96
173
|
*
|
|
97
174
|
* Hand it to the runner's own global stub where the suite undoes stubs that way —
|
|
98
175
|
* `vi.stubGlobal("matchMedia", viewportMatchMedia(375))` — or use
|
|
@@ -102,23 +179,52 @@ export function viewportMatchMedia(
|
|
|
102
179
|
width: number,
|
|
103
180
|
options: ViewportOptions = {},
|
|
104
181
|
): (query: string) => MediaQueryList {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
182
|
+
const viewport: StubViewport = { width, options, lists: new Set() };
|
|
183
|
+
const matchMedia = (query: string) => {
|
|
184
|
+
const list = mediaQueryList(query, viewport);
|
|
185
|
+
viewport.lists.add(list);
|
|
186
|
+
return list as unknown as MediaQueryList;
|
|
187
|
+
};
|
|
188
|
+
VIEWPORTS.set(matchMedia, viewport);
|
|
189
|
+
return matchMedia;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Resize the stubbed viewport to `width` CSS px, live — the window being dragged narrower,
|
|
194
|
+
* a phone turned (0.35, keksdose's 0.34 report). Every list the stub in force has handed
|
|
195
|
+
* out answers from the new width, and each one whose answer flipped fires `change` to its
|
|
196
|
+
* `addEventListener` listeners, its legacy `addListener` ones and its `onchange`, as in a
|
|
197
|
+
* browser; a list whose answer stayed the same tells nobody. So a mounted component that
|
|
198
|
+
* listens to a breakpoint (`usePhoneLayout()`, `useBreakpoint()`) switches without being
|
|
199
|
+
* rendered again.
|
|
200
|
+
*
|
|
201
|
+
* The stub in force is whatever `window.matchMedia` is now — one {@link stubViewportWidth}
|
|
202
|
+
* or `vi.stubGlobal("matchMedia", viewportMatchMedia(…))` put in; its features
|
|
203
|
+
* (`options`) stay as they were. Throws when `matchMedia` is not one of these stubs, rather
|
|
204
|
+
* than resize nothing. Synchronous: wrap it in `act()` when a listener sets state.
|
|
205
|
+
*
|
|
206
|
+
* ```ts
|
|
207
|
+
* undo = stubViewportWidth(1280);
|
|
208
|
+
* render(<Page />);
|
|
209
|
+
* act(() => setViewportWidth(375));
|
|
210
|
+
* expect(screen.getByRole("list")).toBeInTheDocument(); // the cards
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
export function setViewportWidth(width: number): void {
|
|
214
|
+
const viewport = typeof window === "undefined" ? undefined : VIEWPORTS.get(window.matchMedia);
|
|
215
|
+
if (!viewport) {
|
|
216
|
+
throw new Error(
|
|
217
|
+
"setViewportWidth: no viewport stub is in force — put one in first with " +
|
|
218
|
+
"stubViewportWidth(width), or vi.stubGlobal(\"matchMedia\", viewportMatchMedia(width)).",
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
viewport.width = width;
|
|
222
|
+
for (const list of [...viewport.lists]) {
|
|
223
|
+
const matches = answer(list.media, width, viewport.options);
|
|
224
|
+
if (matches === list.matches) continue;
|
|
225
|
+
list.matches = matches;
|
|
226
|
+
list.dispatchEvent(changeEvent(list));
|
|
227
|
+
}
|
|
122
228
|
}
|
|
123
229
|
|
|
124
230
|
/**
|