@funstack/ui 0.1.0-alpha.3 → 0.1.0-alpha.4
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/dist/accordion/accordion-root.mjs +2 -1
- package/dist/accordion/accordion-root.mjs.map +1 -1
- package/dist/combobox/combobox-clear.mjs +2 -1
- package/dist/combobox/combobox-clear.mjs.map +1 -1
- package/dist/combobox/combobox-list.mjs +2 -1
- package/dist/combobox/combobox-list.mjs.map +1 -1
- package/dist/combobox/combobox-popup.d.mts.map +1 -1
- package/dist/combobox/combobox-popup.mjs +7 -2
- package/dist/combobox/combobox-popup.mjs.map +1 -1
- package/dist/combobox/combobox-root.mjs +1 -1
- package/dist/combobox/combobox-trigger.mjs +2 -1
- package/dist/combobox/combobox-trigger.mjs.map +1 -1
- package/dist/dialog/dialog-close.mjs +2 -1
- package/dist/dialog/dialog-close.mjs.map +1 -1
- package/dist/dialog/dialog-form.d.mts +14 -8
- package/dist/dialog/dialog-form.d.mts.map +1 -1
- package/dist/dialog/dialog-form.mjs +36 -5
- package/dist/dialog/dialog-form.mjs.map +1 -1
- package/dist/dialog/dialog-popup.d.mts.map +1 -1
- package/dist/dialog/dialog-popup.mjs +13 -5
- package/dist/dialog/dialog-popup.mjs.map +1 -1
- package/dist/dialog/dialog-root.mjs +1 -1
- package/dist/dialog/dialog-trigger.mjs +2 -1
- package/dist/dialog/dialog-trigger.mjs.map +1 -1
- package/dist/field/field-root.mjs +5 -3
- package/dist/field/field-root.mjs.map +1 -1
- package/dist/form/form-root.mjs +2 -1
- package/dist/form/form-root.mjs.map +1 -1
- package/dist/form/form-submit.mjs +2 -1
- package/dist/form/form-submit.mjs.map +1 -1
- package/dist/index.d.mts +27 -1
- package/dist/index.mjs +6 -1
- package/dist/internal/announce.mjs +53 -0
- package/dist/internal/announce.mjs.map +1 -0
- package/dist/internal/decimal.mjs +27 -0
- package/dist/internal/decimal.mjs.map +1 -0
- package/dist/internal/dev-warn.mjs +17 -0
- package/dist/internal/dev-warn.mjs.map +1 -0
- package/dist/internal/field-context.mjs.map +1 -1
- package/dist/internal/range-value.mjs +51 -0
- package/dist/internal/range-value.mjs.map +1 -0
- package/dist/internal/render-element.mjs +54 -0
- package/dist/internal/render-element.mjs.map +1 -0
- package/dist/internal/toolbar-context.mjs +17 -0
- package/dist/internal/toolbar-context.mjs.map +1 -0
- package/dist/internal/use-exit-animation.mjs +26 -6
- package/dist/internal/use-exit-animation.mjs.map +1 -1
- package/dist/internal/use-field-control.mjs +5 -3
- package/dist/internal/use-field-control.mjs.map +1 -1
- package/dist/internal/use-popover-popup.mjs +3 -2
- package/dist/internal/use-popover-popup.mjs.map +1 -1
- package/dist/internal/use-popover-trigger.mjs +1 -1
- package/dist/internal/use-render.mjs +11 -34
- package/dist/internal/use-render.mjs.map +1 -1
- package/dist/internal/use-roving-focus.mjs +255 -0
- package/dist/internal/use-roving-focus.mjs.map +1 -0
- package/dist/menu/menu-checkbox-item.mjs +2 -1
- package/dist/menu/menu-checkbox-item.mjs.map +1 -1
- package/dist/menu/menu-item.mjs +2 -1
- package/dist/menu/menu-item.mjs.map +1 -1
- package/dist/menu/menu-popup.d.mts.map +1 -1
- package/dist/menu/menu-popup.mjs +7 -2
- package/dist/menu/menu-popup.mjs.map +1 -1
- package/dist/menu/menu-radio-item.mjs +2 -1
- package/dist/menu/menu-radio-item.mjs.map +1 -1
- package/dist/menu/menu-root.mjs +1 -1
- package/dist/menu/menu-trigger.d.mts +2 -1
- package/dist/menu/menu-trigger.d.mts.map +1 -1
- package/dist/menu/menu-trigger.mjs +15 -4
- package/dist/menu/menu-trigger.mjs.map +1 -1
- package/dist/meter/index.d.mts +24 -0
- package/dist/meter/index.d.mts.map +1 -0
- package/dist/meter/index.mjs +23 -0
- package/dist/meter/index.mjs.map +1 -0
- package/dist/meter/meter-context.mjs +13 -0
- package/dist/meter/meter-context.mjs.map +1 -0
- package/dist/meter/meter-indicator.d.mts +19 -0
- package/dist/meter/meter-indicator.d.mts.map +1 -0
- package/dist/meter/meter-indicator.mjs +25 -0
- package/dist/meter/meter-indicator.mjs.map +1 -0
- package/dist/meter/meter-label.d.mts +19 -0
- package/dist/meter/meter-label.d.mts.map +1 -0
- package/dist/meter/meter-label.mjs +28 -0
- package/dist/meter/meter-label.mjs.map +1 -0
- package/dist/meter/meter-region.d.mts +4 -0
- package/dist/meter/meter-region.d.mts.map +1 -0
- package/dist/meter/meter-region.mjs +39 -0
- package/dist/meter/meter-region.mjs.map +1 -0
- package/dist/meter/meter-root.d.mts +71 -0
- package/dist/meter/meter-root.d.mts.map +1 -0
- package/dist/meter/meter-root.mjs +83 -0
- package/dist/meter/meter-root.mjs.map +1 -0
- package/dist/meter/meter-track.d.mts +19 -0
- package/dist/meter/meter-track.d.mts.map +1 -0
- package/dist/meter/meter-track.mjs +25 -0
- package/dist/meter/meter-track.mjs.map +1 -0
- package/dist/meter/meter-value.d.mts +20 -0
- package/dist/meter/meter-value.d.mts.map +1 -0
- package/dist/meter/meter-value.mjs +29 -0
- package/dist/meter/meter-value.mjs.map +1 -0
- package/dist/number-field/index.d.mts +26 -0
- package/dist/number-field/index.d.mts.map +1 -0
- package/dist/number-field/index.mjs +26 -0
- package/dist/number-field/index.mjs.map +1 -0
- package/dist/number-field/number-field-arithmetic.mjs +78 -0
- package/dist/number-field/number-field-arithmetic.mjs.map +1 -0
- package/dist/number-field/number-field-context.d.mts +2 -0
- package/dist/number-field/number-field-context.mjs +13 -0
- package/dist/number-field/number-field-context.mjs.map +1 -0
- package/dist/number-field/number-field-decrement.d.mts +16 -0
- package/dist/number-field/number-field-decrement.d.mts.map +1 -0
- package/dist/number-field/number-field-decrement.mjs +19 -0
- package/dist/number-field/number-field-decrement.mjs.map +1 -0
- package/dist/number-field/number-field-format.mjs +279 -0
- package/dist/number-field/number-field-format.mjs.map +1 -0
- package/dist/number-field/number-field-increment.d.mts +32 -0
- package/dist/number-field/number-field-increment.d.mts.map +1 -0
- package/dist/number-field/number-field-increment.mjs +68 -0
- package/dist/number-field/number-field-increment.mjs.map +1 -0
- package/dist/number-field/number-field-input.d.mts +29 -0
- package/dist/number-field/number-field-input.d.mts.map +1 -0
- package/dist/number-field/number-field-input.mjs +163 -0
- package/dist/number-field/number-field-input.mjs.map +1 -0
- package/dist/number-field/number-field-label.d.mts +23 -0
- package/dist/number-field/number-field-label.d.mts.map +1 -0
- package/dist/number-field/number-field-label.mjs +47 -0
- package/dist/number-field/number-field-label.mjs.map +1 -0
- package/dist/number-field/number-field-root.d.mts +108 -0
- package/dist/number-field/number-field-root.d.mts.map +1 -0
- package/dist/number-field/number-field-root.mjs +523 -0
- package/dist/number-field/number-field-root.mjs.map +1 -0
- package/dist/number-field/use-step-button.mjs +107 -0
- package/dist/number-field/use-step-button.mjs.map +1 -0
- package/dist/popover/popover-close.mjs +2 -1
- package/dist/popover/popover-close.mjs.map +1 -1
- package/dist/popover/popover-popup.d.mts.map +1 -1
- package/dist/popover/popover-popup.mjs +7 -2
- package/dist/popover/popover-popup.mjs.map +1 -1
- package/dist/popover/popover-root.mjs +1 -1
- package/dist/progress/index.d.mts +24 -0
- package/dist/progress/index.d.mts.map +1 -0
- package/dist/progress/index.mjs +24 -0
- package/dist/progress/index.mjs.map +1 -0
- package/dist/progress/progress-context.mjs +13 -0
- package/dist/progress/progress-context.mjs.map +1 -0
- package/dist/progress/progress-indicator.d.mts +19 -0
- package/dist/progress/progress-indicator.d.mts.map +1 -0
- package/dist/progress/progress-indicator.mjs +29 -0
- package/dist/progress/progress-indicator.mjs.map +1 -0
- package/dist/progress/progress-label.d.mts +19 -0
- package/dist/progress/progress-label.d.mts.map +1 -0
- package/dist/progress/progress-label.mjs +30 -0
- package/dist/progress/progress-label.mjs.map +1 -0
- package/dist/progress/progress-root.d.mts +68 -0
- package/dist/progress/progress-root.d.mts.map +1 -0
- package/dist/progress/progress-root.mjs +80 -0
- package/dist/progress/progress-root.mjs.map +1 -0
- package/dist/progress/progress-track.d.mts +19 -0
- package/dist/progress/progress-track.d.mts.map +1 -0
- package/dist/progress/progress-track.mjs +29 -0
- package/dist/progress/progress-track.mjs.map +1 -0
- package/dist/progress/progress-value.d.mts +20 -0
- package/dist/progress/progress-value.d.mts.map +1 -0
- package/dist/progress/progress-value.mjs +33 -0
- package/dist/progress/progress-value.mjs.map +1 -0
- package/dist/slider/slider-arithmetic.mjs +1 -17
- package/dist/slider/slider-arithmetic.mjs.map +1 -1
- package/dist/slider/slider-root.mjs +3 -2
- package/dist/slider/slider-root.mjs.map +1 -1
- package/dist/slider/slider-thumb.mjs +2 -1
- package/dist/slider/slider-thumb.mjs.map +1 -1
- package/dist/slider/slider-track.mjs +2 -1
- package/dist/slider/slider-track.mjs.map +1 -1
- package/dist/tabs/tabs-indicator.mjs +2 -1
- package/dist/tabs/tabs-indicator.mjs.map +1 -1
- package/dist/tabs/tabs-tab.mjs +2 -1
- package/dist/tabs/tabs-tab.mjs.map +1 -1
- package/dist/toast/toast-action.mjs +2 -1
- package/dist/toast/toast-action.mjs.map +1 -1
- package/dist/toast/toast-close.mjs +2 -1
- package/dist/toast/toast-close.mjs.map +1 -1
- package/dist/toast/toast-root.mjs +3 -2
- package/dist/toast/toast-root.mjs.map +1 -1
- package/dist/toast/toast-viewport.d.mts.map +1 -1
- package/dist/toast/toast-viewport.mjs +30 -7
- package/dist/toast/toast-viewport.mjs.map +1 -1
- package/dist/toast/toast.d.mts.map +1 -1
- package/dist/toast/toast.mjs +5 -1
- package/dist/toast/toast.mjs.map +1 -1
- package/dist/toggle/toggle.mjs +2 -1
- package/dist/toggle/toggle.mjs.map +1 -1
- package/dist/toggle-group/index.d.mts +16 -0
- package/dist/toggle-group/index.d.mts.map +1 -0
- package/dist/toggle-group/index.mjs +15 -0
- package/dist/toggle-group/index.mjs.map +1 -0
- package/dist/toggle-group/toggle-group-context.d.mts +4 -0
- package/dist/toggle-group/toggle-group-context.d.mts.map +1 -0
- package/dist/toggle-group/toggle-group-context.mjs +13 -0
- package/dist/toggle-group/toggle-group-context.mjs.map +1 -0
- package/dist/toggle-group/toggle-group-item.d.mts +47 -0
- package/dist/toggle-group/toggle-group-item.d.mts.map +1 -0
- package/dist/toggle-group/toggle-group-item.mjs +63 -0
- package/dist/toggle-group/toggle-group-item.mjs.map +1 -0
- package/dist/toggle-group/toggle-group-root.d.mts +95 -0
- package/dist/toggle-group/toggle-group-root.d.mts.map +1 -0
- package/dist/toggle-group/toggle-group-root.mjs +211 -0
- package/dist/toggle-group/toggle-group-root.mjs.map +1 -0
- package/dist/toolbar/index.d.mts +18 -0
- package/dist/toolbar/index.d.mts.map +1 -0
- package/dist/toolbar/index.mjs +18 -0
- package/dist/toolbar/index.mjs.map +1 -0
- package/dist/toolbar/toolbar-button.d.mts +42 -0
- package/dist/toolbar/toolbar-button.d.mts.map +1 -0
- package/dist/toolbar/toolbar-button.mjs +50 -0
- package/dist/toolbar/toolbar-button.mjs.map +1 -0
- package/dist/toolbar/toolbar-root.d.mts +38 -0
- package/dist/toolbar/toolbar-root.d.mts.map +1 -0
- package/dist/toolbar/toolbar-root.mjs +66 -0
- package/dist/toolbar/toolbar-root.mjs.map +1 -0
- package/dist/toolbar/toolbar-separator.d.mts +25 -0
- package/dist/toolbar/toolbar-separator.d.mts.map +1 -0
- package/dist/toolbar/toolbar-separator.mjs +31 -0
- package/dist/toolbar/toolbar-separator.mjs.map +1 -0
- package/dist/tooltip/tooltip-popup.mjs +2 -1
- package/dist/tooltip/tooltip-popup.mjs.map +1 -1
- package/dist/tooltip/tooltip-root.mjs +16 -2
- package/dist/tooltip/tooltip-root.mjs.map +1 -1
- package/dist/tooltip/tooltip-trigger.mjs +2 -1
- package/dist/tooltip/tooltip-trigger.mjs.map +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { mergeRenderRef, renderElement, resolveStateValue } from "../internal/render-element.mjs";
|
|
2
|
+
import { warnOnce } from "../internal/dev-warn.mjs";
|
|
3
|
+
import { clamp, formatRangeValue, isDegenerate, percentage } from "../internal/range-value.mjs";
|
|
4
|
+
import { MeterContext } from "./meter-context.mjs";
|
|
5
|
+
import { meterRegion, resolveMeterBounds } from "./meter-region.mjs";
|
|
6
|
+
import { useId } from "react";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
//#region src/meter/meter-root.tsx
|
|
9
|
+
/**
|
|
10
|
+
* `Meter.Root`: a `div[role=meter]` with the ARIA values and `aria-valuetext` always,
|
|
11
|
+
* `data-region`, and `--meter-value` / `--meter-low` / `--meter-high` / `--meter-optimum` (the
|
|
12
|
+
* percentages) in `style`, where the consumer's entries win. Owned keys: `role`,
|
|
13
|
+
* `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, `aria-valuetext`, `aria-labelledby`
|
|
14
|
+
* (token-merged: the consumer's tokens, then `Meter.Label`'s id, always), `data-region`.
|
|
15
|
+
*/
|
|
16
|
+
function MeterRoot({ value, min = 0, max = 100, low, high, optimum, format, locale, getAriaValueText, className, style, children, render, ref, ...rest }) {
|
|
17
|
+
const id = useId();
|
|
18
|
+
let given = value;
|
|
19
|
+
if (!Number.isFinite(given)) {
|
|
20
|
+
warnOnce(`Meter.Root:non-finite:${given}`, `Meter.Root: \`value\` is ${given}, which is not a finite number; it is rendered as \`min\`.`);
|
|
21
|
+
given = min;
|
|
22
|
+
}
|
|
23
|
+
if (isDegenerate(min, max)) warnOnce(`Meter.Root:degenerate:${min}:${max}`, `Meter.Root: \`max\` (${max}) must be greater than \`min\` (${min}); the percentage is rendered as 0.`);
|
|
24
|
+
const clamped = clamp(given, min, max);
|
|
25
|
+
const bounds = resolveMeterBounds(min, max, low, high, optimum);
|
|
26
|
+
const formattedValue = formatRangeValue(clamped, min, max, format, locale);
|
|
27
|
+
const base = {
|
|
28
|
+
value: clamped,
|
|
29
|
+
min,
|
|
30
|
+
max,
|
|
31
|
+
...bounds,
|
|
32
|
+
percentage: percentage(clamped, min, max),
|
|
33
|
+
formattedValue,
|
|
34
|
+
valueText: formattedValue,
|
|
35
|
+
region: meterRegion(clamped, bounds.low, bounds.high, bounds.optimum)
|
|
36
|
+
};
|
|
37
|
+
const valueText = getAriaValueText === void 0 ? formattedValue : getAriaValueText(clamped, base) ?? null;
|
|
38
|
+
const state = valueText === base.valueText ? base : {
|
|
39
|
+
...base,
|
|
40
|
+
valueText
|
|
41
|
+
};
|
|
42
|
+
const variables = {
|
|
43
|
+
"--meter-value": `${state.percentage}%`,
|
|
44
|
+
"--meter-low": `${percentage(bounds.low, min, max)}%`,
|
|
45
|
+
"--meter-high": `${percentage(bounds.high, min, max)}%`,
|
|
46
|
+
"--meter-optimum": `${percentage(bounds.optimum, min, max)}%`
|
|
47
|
+
};
|
|
48
|
+
const libraryProps = {
|
|
49
|
+
role: "meter",
|
|
50
|
+
"aria-valuemin": min,
|
|
51
|
+
"aria-valuemax": max,
|
|
52
|
+
"aria-valuenow": clamped,
|
|
53
|
+
"aria-valuetext": valueText ?? void 0,
|
|
54
|
+
"aria-labelledby": `${id}-label`,
|
|
55
|
+
"data-region": state.region
|
|
56
|
+
};
|
|
57
|
+
const element = renderElement({
|
|
58
|
+
defaultElement: "div",
|
|
59
|
+
render,
|
|
60
|
+
state,
|
|
61
|
+
className,
|
|
62
|
+
style: (s) => ({
|
|
63
|
+
...variables,
|
|
64
|
+
...resolveStateValue(style, s)
|
|
65
|
+
}),
|
|
66
|
+
children,
|
|
67
|
+
props: rest,
|
|
68
|
+
libraryProps,
|
|
69
|
+
ref: mergeRenderRef(ref, render)
|
|
70
|
+
});
|
|
71
|
+
return /* @__PURE__ */ jsx(MeterContext, {
|
|
72
|
+
value: {
|
|
73
|
+
id,
|
|
74
|
+
state
|
|
75
|
+
},
|
|
76
|
+
children: element
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
MeterRoot.displayName = "MeterRoot";
|
|
80
|
+
//#endregion
|
|
81
|
+
export { MeterRoot };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=meter-root.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter-root.mjs","names":[],"sources":["../../src/meter/meter-root.tsx"],"sourcesContent":["// No \"use client\": `Meter.Root` uses only `useId`, arithmetic and `Intl`, and renders the client\n// context's provider (conventions 7.1's second exception). Rendered by a Server Component it runs\n// once, on the server (design/components/progress.md 8.1). It must not import from any\n// \"use client\" module except its context (checked by src/__tests__/directives.test.ts).\nimport { useId } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { warnOnce } from \"../internal/dev-warn.js\";\nimport { clamp, formatRangeValue, isDegenerate, percentage } from \"../internal/range-value.js\";\nimport { mergeRenderRef, renderElement, resolveStateValue } from \"../internal/render-element.js\";\nimport type { DataAttributes } from \"../internal/render-element.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { MeterContext } from \"./meter-context.js\";\nimport { meterRegion, resolveMeterBounds } from \"./meter-region.js\";\nimport type { MeterRegion } from \"./meter-region.js\";\n\nexport type { MeterRegion } from \"./meter-region.js\";\n\n/** The state every Meter part receives. */\nexport interface MeterState {\n /** Clamped to `[min, max]`. */\n readonly value: number;\n readonly min: number;\n readonly max: number;\n /** After HTML's defaults and clamping. */\n readonly low: number;\n readonly high: number;\n readonly optimum: number;\n /** A number 0–100. */\n readonly percentage: number;\n /** The `Intl.NumberFormat` text of the value (or of the fraction, for the percent style). */\n readonly formattedValue: string;\n /** What `aria-valuetext` carries: `getAriaValueText`'s result, else `formattedValue`. */\n readonly valueText: string | null;\n readonly region: MeterRegion;\n}\n\nexport interface MeterRootProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n | \"role\"\n | \"aria-valuemin\"\n | \"aria-valuemax\"\n | \"aria-valuenow\"\n | \"aria-valuetext\"\n | \"defaultValue\"\n | \"defaultChecked\"\n | \"className\"\n | \"style\"\n | \"children\"\n> {\n /** Required: a meter always has a value. Clamped to `[min, max]`. */\n value: number;\n /** @default 0 */\n min?: number;\n /** @default 100 */\n max?: number;\n /** Upper bound of the \"low\" part of the range. Default `min`. */\n low?: number;\n /** Lower bound of the \"high\" part of the range. Default `max`. */\n high?: number;\n /** The best value. Default the midpoint. Decides which part of the range is `optimum`. */\n optimum?: number;\n /**\n * `Intl.NumberFormat` options for `aria-valuetext` and `Meter.Value`. Default\n * `{ style: \"percent\" }`. A `percent` style formats the fraction of the range; any other style\n * formats the value. Serialisable: a Server Component may pass it.\n */\n format?: Intl.NumberFormatOptions;\n /**\n * BCP 47 tag for `Intl.NumberFormat`; default: the runtime's locale. Pass it when a client\n * tree is server-rendered.\n */\n locale?: string;\n /**\n * The `aria-valuetext` to render instead of `formattedValue`. Called with the clamped value\n * and the state as it would be without this prop. Return `null` to render no\n * `aria-valuetext`.\n */\n getAriaValueText?: (value: number, state: MeterState) => string | null | undefined;\n className?: StateValue<string | undefined, MeterState>;\n style?: StateValue<CSSProperties | undefined, MeterState>;\n children?: StateValue<ReactNode, MeterState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, MeterState>;\n}\n\n/**\n * `Meter.Root`: a `div[role=meter]` with the ARIA values and `aria-valuetext` always,\n * `data-region`, and `--meter-value` / `--meter-low` / `--meter-high` / `--meter-optimum` (the\n * percentages) in `style`, where the consumer's entries win. Owned keys: `role`,\n * `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, `aria-valuetext`, `aria-labelledby`\n * (token-merged: the consumer's tokens, then `Meter.Label`'s id, always), `data-region`.\n */\nexport function MeterRoot({\n value,\n min = 0,\n max = 100,\n low,\n high,\n optimum,\n format,\n locale,\n getAriaValueText,\n className,\n style,\n children,\n render,\n ref,\n ...rest\n}: MeterRootProps) {\n const id = useId();\n\n let given = value;\n if (!Number.isFinite(given)) {\n warnOnce(\n `Meter.Root:non-finite:${given}`,\n `Meter.Root: \\`value\\` is ${given}, which is not a finite number; it is rendered as \\`min\\`.`,\n );\n given = min;\n }\n if (isDegenerate(min, max)) {\n warnOnce(\n `Meter.Root:degenerate:${min}:${max}`,\n `Meter.Root: \\`max\\` (${max}) must be greater than \\`min\\` (${min}); the percentage is ` +\n \"rendered as 0.\",\n );\n }\n\n const clamped = clamp(given, min, max);\n const bounds = resolveMeterBounds(min, max, low, high, optimum);\n const formattedValue = formatRangeValue(clamped, min, max, format, locale);\n const base: MeterState = {\n value: clamped,\n min,\n max,\n ...bounds,\n percentage: percentage(clamped, min, max),\n formattedValue,\n valueText: formattedValue,\n region: meterRegion(clamped, bounds.low, bounds.high, bounds.optimum),\n };\n const valueText =\n getAriaValueText === undefined ? formattedValue : (getAriaValueText(clamped, base) ?? null);\n const state: MeterState = valueText === base.valueText ? base : { ...base, valueText };\n\n const variables = {\n \"--meter-value\": `${state.percentage}%`,\n \"--meter-low\": `${percentage(bounds.low, min, max)}%`,\n \"--meter-high\": `${percentage(bounds.high, min, max)}%`,\n \"--meter-optimum\": `${percentage(bounds.optimum, min, max)}%`,\n };\n\n const libraryProps: Partial<ComponentPropsWithRef<\"div\">> & DataAttributes = {\n role: \"meter\",\n \"aria-valuemin\": min,\n \"aria-valuemax\": max,\n \"aria-valuenow\": clamped,\n \"aria-valuetext\": valueText ?? undefined,\n \"aria-labelledby\": `${id}-label`,\n \"data-region\": state.region,\n };\n\n const element = renderElement<ComponentPropsWithRef<\"div\">, MeterState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n // The custom properties lose to the consumer's own entries (design 3.3).\n style: (s) => ({ ...(variables as CSSProperties), ...resolveStateValue(style, s) }),\n children,\n props: rest,\n libraryProps,\n // Passed through to the element, never read during render.\n // oxlint-disable-next-line react/refs\n ref: mergeRenderRef(ref, render),\n });\n\n return <MeterContext value={{ id, state }}>{element}</MeterContext>;\n}\n\nMeterRoot.displayName = \"MeterRoot\";\n"],"mappings":";;;;;;;;;;;;;;;AA2FA,SAAgB,UAAU,EACxB,OACA,MAAM,GACN,MAAM,KACN,KACA,MACA,SACA,QACA,QACA,kBACA,WACA,OACA,UACA,QACA,KACA,GAAG,QACc;CACjB,MAAM,KAAK,MAAM;CAEjB,IAAI,QAAQ;CACZ,IAAI,CAAC,OAAO,SAAS,KAAK,GAAG;EAC3B,SACE,yBAAyB,SACzB,4BAA4B,MAAM,2DACpC;EACA,QAAQ;CACV;CACA,IAAI,aAAa,KAAK,GAAG,GACvB,SACE,yBAAyB,IAAI,GAAG,OAChC,wBAAwB,IAAI,kCAAkC,IAAI,oCAEpE;CAGF,MAAM,UAAU,MAAM,OAAO,KAAK,GAAG;CACrC,MAAM,SAAS,mBAAmB,KAAK,KAAK,KAAK,MAAM,OAAO;CAC9D,MAAM,iBAAiB,iBAAiB,SAAS,KAAK,KAAK,QAAQ,MAAM;CACzE,MAAM,OAAmB;EACvB,OAAO;EACP;EACA;EACA,GAAG;EACH,YAAY,WAAW,SAAS,KAAK,GAAG;EACxC;EACA,WAAW;EACX,QAAQ,YAAY,SAAS,OAAO,KAAK,OAAO,MAAM,OAAO,OAAO;CACtE;CACA,MAAM,YACJ,qBAAqB,KAAA,IAAY,iBAAkB,iBAAiB,SAAS,IAAI,KAAK;CACxF,MAAM,QAAoB,cAAc,KAAK,YAAY,OAAO;EAAE,GAAG;EAAM;CAAU;CAErF,MAAM,YAAY;EAChB,iBAAiB,GAAG,MAAM,WAAW;EACrC,eAAe,GAAG,WAAW,OAAO,KAAK,KAAK,GAAG,EAAE;EACnD,gBAAgB,GAAG,WAAW,OAAO,MAAM,KAAK,GAAG,EAAE;EACrD,mBAAmB,GAAG,WAAW,OAAO,SAAS,KAAK,GAAG,EAAE;CAC7D;CAEA,MAAM,eAAuE;EAC3E,MAAM;EACN,iBAAiB;EACjB,iBAAiB;EACjB,iBAAiB;EACjB,kBAAkB,aAAa,KAAA;EAC/B,mBAAmB,GAAG,GAAG;EACzB,eAAe,MAAM;CACvB;CAEA,MAAM,UAAU,cAAwD;EACtE,gBAAgB;EAChB;EACA;EACA;EAEA,QAAQ,OAAO;GAAE,GAAI;GAA6B,GAAG,kBAAkB,OAAO,CAAC;EAAE;EACjF;EACA,OAAO;EACP;EAGA,KAAK,eAAe,KAAK,MAAM;CACjC,CAAC;CAED,OAAO,oBAAC,cAAD;EAAc,OAAO;GAAE;GAAI;EAAM;EAAI,UAAA;CAAsB,CAAA;AACpE;AAEA,UAAU,cAAc"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { RenderProp, StateValue } from "../internal/types.mjs";
|
|
2
|
+
import { MeterState } from "./meter-root.mjs";
|
|
3
|
+
import { CSSProperties, ComponentPropsWithRef, ReactNode } from "react";
|
|
4
|
+
//#region src/meter/meter-track.d.ts
|
|
5
|
+
export interface MeterTrackProps extends Omit<ComponentPropsWithRef<"div">, "className" | "style" | "children"> {
|
|
6
|
+
className?: StateValue<string | undefined, MeterState>;
|
|
7
|
+
style?: StateValue<CSSProperties | undefined, MeterState>;
|
|
8
|
+
children?: StateValue<ReactNode, MeterState>;
|
|
9
|
+
render?: RenderProp<ComponentPropsWithRef<"div">, MeterState>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* `Meter.Track`: the rail. It renders nothing but the state's data attributes. Owned keys: `data-region`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function MeterTrack({ className, style, children, render, ...rest }: MeterTrackProps): ReactNode;
|
|
15
|
+
export declare namespace MeterTrack {
|
|
16
|
+
var displayName: string;
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
//# sourceMappingURL=meter-track.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter-track.d.mts","names":[],"sources":["../../src/meter/meter-track.tsx"],"mappings":";;;;iBAOiB,wBAAwB,KACvC;EAGA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,8BAA8B;;;;;wBAMpC,aAAa,WAAW,OAAO,UAAU,WAAW,QAAQ,kBAAe;yBAA3E"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useRender } from "../internal/use-render.mjs";
|
|
3
|
+
import { useMeterContext } from "./meter-context.mjs";
|
|
4
|
+
//#region src/meter/meter-track.tsx
|
|
5
|
+
/**
|
|
6
|
+
* `Meter.Track`: the rail. It renders nothing but the state's data attributes. Owned keys: `data-region`.
|
|
7
|
+
*/
|
|
8
|
+
function MeterTrack({ className, style, children, render, ...rest }) {
|
|
9
|
+
const { state } = useMeterContext("Meter.Track");
|
|
10
|
+
return useRender({
|
|
11
|
+
defaultElement: "div",
|
|
12
|
+
render,
|
|
13
|
+
state,
|
|
14
|
+
className,
|
|
15
|
+
style,
|
|
16
|
+
children,
|
|
17
|
+
props: rest,
|
|
18
|
+
libraryProps: { "data-region": state.region }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
MeterTrack.displayName = "MeterTrack";
|
|
22
|
+
//#endregion
|
|
23
|
+
export { MeterTrack };
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=meter-track.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter-track.mjs","names":[],"sources":["../../src/meter/meter-track.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { useMeterContext } from \"./meter-context.js\";\nimport type { MeterState } from \"./meter-root.js\";\n\nexport interface MeterTrackProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"className\" | \"style\" | \"children\"\n> {\n className?: StateValue<string | undefined, MeterState>;\n style?: StateValue<CSSProperties | undefined, MeterState>;\n children?: StateValue<ReactNode, MeterState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, MeterState>;\n}\n\n/**\n * `Meter.Track`: the rail. It renders nothing but the state's data attributes. Owned keys: `data-region`.\n */\nexport function MeterTrack({ className, style, children, render, ...rest }: MeterTrackProps) {\n const { state } = useMeterContext(\"Meter.Track\");\n return useRender<ComponentPropsWithRef<\"div\">, MeterState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n \"data-region\": state.region,\n },\n });\n}\n\nMeterTrack.displayName = \"MeterTrack\";\n"],"mappings":";;;;;;;AAoBA,SAAgB,WAAW,EAAE,WAAW,OAAO,UAAU,QAAQ,GAAG,QAAyB;CAC3F,MAAM,EAAE,UAAU,gBAAgB,aAAa;CAC/C,OAAO,UAAoD;EACzD,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP,cAAc,EACZ,eAAe,MAAM,OACvB;CACF,CAAC;AACH;AAEA,WAAW,cAAc"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { RenderProp, StateValue } from "../internal/types.mjs";
|
|
2
|
+
import { MeterState } from "./meter-root.mjs";
|
|
3
|
+
import { CSSProperties, ComponentPropsWithRef, ReactNode } from "react";
|
|
4
|
+
//#region src/meter/meter-value.d.ts
|
|
5
|
+
export interface MeterValueProps extends Omit<ComponentPropsWithRef<"span">, "className" | "style" | "children"> {
|
|
6
|
+
className?: StateValue<string | undefined, MeterState>;
|
|
7
|
+
style?: StateValue<CSSProperties | undefined, MeterState>;
|
|
8
|
+
/** Default: `state.formattedValue`. */
|
|
9
|
+
children?: StateValue<ReactNode, MeterState>;
|
|
10
|
+
render?: RenderProp<ComponentPropsWithRef<"span">, MeterState>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* `Meter.Value`: shows `state.formattedValue` or `children(state)`. `aria-hidden="true"` is a default the consumer may override, not an owned key: the same text is in the Root's `aria-valuetext`. Owned keys: `data-region`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function MeterValue({ className, style, children, render, ...rest }: MeterValueProps): ReactNode;
|
|
16
|
+
export declare namespace MeterValue {
|
|
17
|
+
var displayName: string;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//# sourceMappingURL=meter-value.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter-value.d.mts","names":[],"sources":["../../src/meter/meter-value.tsx"],"mappings":";;;;iBAOiB,wBAAwB,KACvC;EAGA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;;EAE9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,+BAA+B;;;;;wBAMrC,aAAa,WAAW,OAAO,UAAU,WAAW,QAAQ,kBAAe;yBAA3E"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useRender } from "../internal/use-render.mjs";
|
|
3
|
+
import { useMeterContext } from "./meter-context.mjs";
|
|
4
|
+
//#region src/meter/meter-value.tsx
|
|
5
|
+
/**
|
|
6
|
+
* `Meter.Value`: shows `state.formattedValue` or `children(state)`. `aria-hidden="true"` is a default the consumer may override, not an owned key: the same text is in the Root's `aria-valuetext`. Owned keys: `data-region`.
|
|
7
|
+
*/
|
|
8
|
+
function MeterValue({ className, style, children, render, ...rest }) {
|
|
9
|
+
const { state } = useMeterContext("Meter.Value");
|
|
10
|
+
const props = {
|
|
11
|
+
...rest,
|
|
12
|
+
"aria-hidden": rest["aria-hidden"] ?? true
|
|
13
|
+
};
|
|
14
|
+
return useRender({
|
|
15
|
+
defaultElement: "span",
|
|
16
|
+
render,
|
|
17
|
+
state,
|
|
18
|
+
className,
|
|
19
|
+
style,
|
|
20
|
+
children: children ?? state.formattedValue,
|
|
21
|
+
props,
|
|
22
|
+
libraryProps: { "data-region": state.region }
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
MeterValue.displayName = "MeterValue";
|
|
26
|
+
//#endregion
|
|
27
|
+
export { MeterValue };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=meter-value.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter-value.mjs","names":[],"sources":["../../src/meter/meter-value.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { useMeterContext } from \"./meter-context.js\";\nimport type { MeterState } from \"./meter-root.js\";\n\nexport interface MeterValueProps extends Omit<\n ComponentPropsWithRef<\"span\">,\n \"className\" | \"style\" | \"children\"\n> {\n className?: StateValue<string | undefined, MeterState>;\n style?: StateValue<CSSProperties | undefined, MeterState>;\n /** Default: `state.formattedValue`. */\n children?: StateValue<ReactNode, MeterState>;\n render?: RenderProp<ComponentPropsWithRef<\"span\">, MeterState>;\n}\n\n/**\n * `Meter.Value`: shows `state.formattedValue` or `children(state)`. `aria-hidden=\"true\"` is a default the consumer may override, not an owned key: the same text is in the Root's `aria-valuetext`. Owned keys: `data-region`.\n */\nexport function MeterValue({ className, style, children, render, ...rest }: MeterValueProps) {\n const { state } = useMeterContext(\"Meter.Value\");\n const props = { ...rest, \"aria-hidden\": rest[\"aria-hidden\"] ?? true };\n return useRender<ComponentPropsWithRef<\"span\">, MeterState>({\n defaultElement: \"span\",\n render,\n state,\n className,\n style,\n children: children ?? state.formattedValue,\n props,\n libraryProps: {\n \"data-region\": state.region,\n },\n });\n}\n\nMeterValue.displayName = \"MeterValue\";\n"],"mappings":";;;;;;;AAqBA,SAAgB,WAAW,EAAE,WAAW,OAAO,UAAU,QAAQ,GAAG,QAAyB;CAC3F,MAAM,EAAE,UAAU,gBAAgB,aAAa;CAC/C,MAAM,QAAQ;EAAE,GAAG;EAAM,eAAe,KAAK,kBAAkB;CAAK;CACpE,OAAO,UAAqD;EAC1D,gBAAgB;EAChB;EACA;EACA;EACA;EACA,UAAU,YAAY,MAAM;EAC5B;EACA,cAAc,EACZ,eAAe,MAAM,OACvB;CACF,CAAC;AACH;AAEA,WAAW,cAAc"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import "../internal/platform-attributes.mjs";
|
|
2
|
+
import { NumberFieldRoot, NumberFieldRootProps, NumberFieldState, NumberFieldValueChangeDetails } from "./number-field-root.mjs";
|
|
3
|
+
import { NumberFieldButtonState, NumberFieldDecrementProps, NumberFieldIncrement, NumberFieldIncrementProps } from "./number-field-increment.mjs";
|
|
4
|
+
import { NumberFieldDecrement } from "./number-field-decrement.mjs";
|
|
5
|
+
import { NumberFieldInput, NumberFieldInputProps } from "./number-field-input.mjs";
|
|
6
|
+
import { NumberFieldLabel, NumberFieldLabelProps } from "./number-field-label.mjs";
|
|
7
|
+
//#region src/number-field/index.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* NumberField: a text field that holds a number. `Root` (`div[role=group]`, owns the state),
|
|
10
|
+
* `Label`, `Input` (the visible `input[type=text]` plus a visually hidden native
|
|
11
|
+
* `input[type=number]` that carries `name`, the constraints and the posted value), and the
|
|
12
|
+
* `Increment` / `Decrement` buttons (press-and-hold). The user types in their locale's notation
|
|
13
|
+
* (committed on blur or Enter) or steps with the keys and buttons. Two actions:
|
|
14
|
+
* `valueChangeAction` on every step (a transition; keep it cheap) and `valueCommitAction` once
|
|
15
|
+
* per gesture (the save; uncontrolled state is committed when it resolves).
|
|
16
|
+
*/
|
|
17
|
+
export declare const NumberField: {
|
|
18
|
+
readonly Root: typeof NumberFieldRoot;
|
|
19
|
+
readonly Label: typeof NumberFieldLabel;
|
|
20
|
+
readonly Input: typeof NumberFieldInput;
|
|
21
|
+
readonly Increment: typeof NumberFieldIncrement;
|
|
22
|
+
readonly Decrement: typeof NumberFieldDecrement;
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
export type { NumberFieldButtonState, NumberFieldDecrementProps, NumberFieldIncrementProps, NumberFieldInputProps, NumberFieldLabelProps, NumberFieldRootProps, NumberFieldState, NumberFieldValueChangeDetails };
|
|
26
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/number-field/index.ts"],"mappings":";;;;;;;;;;;;;;;;qBAkBa;WACX,aAAI;WACJ,cAAK;WACL,cAAK;WACL,kBAAS;WACT,kBAAS"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { NumberFieldIncrement } from "./number-field-increment.mjs";
|
|
2
|
+
import { NumberFieldDecrement } from "./number-field-decrement.mjs";
|
|
3
|
+
import { NumberFieldRoot } from "./number-field-root.mjs";
|
|
4
|
+
import { NumberFieldInput } from "./number-field-input.mjs";
|
|
5
|
+
import { NumberFieldLabel } from "./number-field-label.mjs";
|
|
6
|
+
//#region src/number-field/index.ts
|
|
7
|
+
/**
|
|
8
|
+
* NumberField: a text field that holds a number. `Root` (`div[role=group]`, owns the state),
|
|
9
|
+
* `Label`, `Input` (the visible `input[type=text]` plus a visually hidden native
|
|
10
|
+
* `input[type=number]` that carries `name`, the constraints and the posted value), and the
|
|
11
|
+
* `Increment` / `Decrement` buttons (press-and-hold). The user types in their locale's notation
|
|
12
|
+
* (committed on blur or Enter) or steps with the keys and buttons. Two actions:
|
|
13
|
+
* `valueChangeAction` on every step (a transition; keep it cheap) and `valueCommitAction` once
|
|
14
|
+
* per gesture (the save; uncontrolled state is committed when it resolves).
|
|
15
|
+
*/
|
|
16
|
+
const NumberField = {
|
|
17
|
+
Root: NumberFieldRoot,
|
|
18
|
+
Label: NumberFieldLabel,
|
|
19
|
+
Input: NumberFieldInput,
|
|
20
|
+
Increment: NumberFieldIncrement,
|
|
21
|
+
Decrement: NumberFieldDecrement
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
export { NumberField };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/number-field/index.ts"],"sourcesContent":["// Ships the platform attribute typings with this subpath (conventions 7.4). Type-only: the\n// built JS has no import of it.\nimport \"../internal/platform-attributes.js\";\nimport { NumberFieldDecrement } from \"./number-field-decrement.js\";\nimport { NumberFieldIncrement } from \"./number-field-increment.js\";\nimport { NumberFieldInput } from \"./number-field-input.js\";\nimport { NumberFieldLabel } from \"./number-field-label.js\";\nimport { NumberFieldRoot } from \"./number-field-root.js\";\n\n/**\n * NumberField: a text field that holds a number. `Root` (`div[role=group]`, owns the state),\n * `Label`, `Input` (the visible `input[type=text]` plus a visually hidden native\n * `input[type=number]` that carries `name`, the constraints and the posted value), and the\n * `Increment` / `Decrement` buttons (press-and-hold). The user types in their locale's notation\n * (committed on blur or Enter) or steps with the keys and buttons. Two actions:\n * `valueChangeAction` on every step (a transition; keep it cheap) and `valueCommitAction` once\n * per gesture (the save; uncontrolled state is committed when it resolves).\n */\nexport const NumberField = {\n Root: NumberFieldRoot,\n Label: NumberFieldLabel,\n Input: NumberFieldInput,\n Increment: NumberFieldIncrement,\n Decrement: NumberFieldDecrement,\n} as const;\n\nexport type {\n NumberFieldRootProps,\n NumberFieldState,\n NumberFieldValueChangeDetails,\n} from \"./number-field-root.js\";\nexport type { NumberFieldLabelProps } from \"./number-field-label.js\";\nexport type { NumberFieldInputProps } from \"./number-field-input.js\";\nexport type {\n NumberFieldButtonState,\n NumberFieldIncrementProps,\n NumberFieldDecrementProps,\n} from \"./number-field-increment.js\";\n"],"mappings":";;;;;;;;;;;;;;;AAkBA,MAAa,cAAc;CACzB,MAAM;CACN,OAAO;CACP,OAAO;CACP,WAAW;CACX,WAAW;AACb"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { decimalsOf, roundTo } from "../internal/decimal.mjs";
|
|
2
|
+
//#region src/number-field/number-field-arithmetic.ts
|
|
3
|
+
/** Tolerance for grid computations on binary floating point. */
|
|
4
|
+
const EPSILON = 1e-9;
|
|
5
|
+
function decimalsFor(bounds) {
|
|
6
|
+
return Math.max(decimalsOf(bounds.step), decimalsOf(bounds.min ?? 0));
|
|
7
|
+
}
|
|
8
|
+
/** The largest grid point `≤ max` (an unreachable `max` stays unreachable: `0..5` by `2` is `4`). */
|
|
9
|
+
function gridMax(max, base, step) {
|
|
10
|
+
return base + Math.floor((max - base) / step + EPSILON) * step;
|
|
11
|
+
}
|
|
12
|
+
function clampAndGuard(result, current, direction, bounds) {
|
|
13
|
+
const { min, max, step } = bounds;
|
|
14
|
+
const base = min ?? 0;
|
|
15
|
+
let next = result;
|
|
16
|
+
if (max !== void 0 && next > max + EPSILON) next = gridMax(max, base, step);
|
|
17
|
+
if (min !== void 0 && next < min - EPSILON) next = min;
|
|
18
|
+
next = roundTo(next, decimalsFor(bounds));
|
|
19
|
+
if (current !== null) {
|
|
20
|
+
if (direction > 0 && next < current) return current;
|
|
21
|
+
if (direction < 0 && next > current) return current;
|
|
22
|
+
}
|
|
23
|
+
return next;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* `current` stepped `n` times (`n > 0` up, `n < 0` down): empty counts as `0`; the step base is
|
|
27
|
+
* `min` (else `0`); an off-grid value snaps to the nearest grid point in the stepping direction,
|
|
28
|
+
* which counts as one step; the result is clamped to `[min, max]` on the grid and rounded to the
|
|
29
|
+
* decimals of `step` and `min`. When the clamp would move the value against the direction, the
|
|
30
|
+
* value is returned unchanged.
|
|
31
|
+
*/
|
|
32
|
+
function stepValue(current, n, bounds) {
|
|
33
|
+
const { min, step } = bounds;
|
|
34
|
+
const value = current ?? 0;
|
|
35
|
+
const base = min ?? 0;
|
|
36
|
+
const index = (value - base) / step;
|
|
37
|
+
const nearest = Math.round(index);
|
|
38
|
+
let target;
|
|
39
|
+
if (Math.abs(index - nearest) <= EPSILON * Math.max(1, Math.abs(index))) target = nearest + n;
|
|
40
|
+
else if (n > 0) target = Math.ceil(index) + (n - 1);
|
|
41
|
+
else target = Math.floor(index) + (n + 1);
|
|
42
|
+
return clampAndGuard(base + target * step, current, n, bounds);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* `current` moved by `delta` (PageUp / PageDown, Shift+Arrow: `± largeStep`). A whole number of
|
|
46
|
+
* steps is `stepValue(current, delta / step)`; otherwise the value moves by `delta` and snaps to
|
|
47
|
+
* the grid toward the start (never past `delta`), at least one step.
|
|
48
|
+
*/
|
|
49
|
+
function stepBy(current, delta, bounds) {
|
|
50
|
+
const { min, step } = bounds;
|
|
51
|
+
const ratio = delta / step;
|
|
52
|
+
if (Math.abs(ratio - Math.round(ratio)) <= EPSILON * Math.max(1, Math.abs(ratio))) return stepValue(current, Math.round(ratio), bounds);
|
|
53
|
+
const value = current ?? 0;
|
|
54
|
+
const base = min ?? 0;
|
|
55
|
+
const index = (value + delta - base) / step;
|
|
56
|
+
let target = delta > 0 ? Math.floor(index + EPSILON) : Math.ceil(index - EPSILON);
|
|
57
|
+
const start = (value - base) / step;
|
|
58
|
+
if (delta > 0 && target <= start + EPSILON) target = Math.floor(start + EPSILON) + 1;
|
|
59
|
+
if (delta < 0 && target >= start - EPSILON) target = Math.ceil(start - EPSILON) - 1;
|
|
60
|
+
return clampAndGuard(base + target * step, current, delta, bounds);
|
|
61
|
+
}
|
|
62
|
+
/** The bound a Home / End key moves to: `min`, or the largest grid point `≤ max`. */
|
|
63
|
+
function boundValue(which, bounds) {
|
|
64
|
+
const { min, max, step } = bounds;
|
|
65
|
+
if (which === "min") return min;
|
|
66
|
+
if (max === void 0) return void 0;
|
|
67
|
+
if (min !== void 0 && max < min) return min;
|
|
68
|
+
return roundTo(gridMax(max, min ?? 0, step), decimalsFor(bounds));
|
|
69
|
+
}
|
|
70
|
+
/** The next step in `direction` would change nothing (a button at its bound). Empty never is. */
|
|
71
|
+
function isAtBound(value, direction, bounds) {
|
|
72
|
+
if (value === null) return false;
|
|
73
|
+
return stepValue(value, direction, bounds) === value;
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
export { boundValue, isAtBound, stepBy, stepValue };
|
|
77
|
+
|
|
78
|
+
//# sourceMappingURL=number-field-arithmetic.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number-field-arithmetic.mjs","names":[],"sources":["../../src/number-field/number-field-arithmetic.ts"],"sourcesContent":["// Stepping arithmetic of NumberField (design/components/number-field.md 5.5). No directive: no\n// React, no client-only API. The spec's `stepUp(n)` / `stepDown(n)` (snap off-grid values in the\n// stepping direction, clamp into `[min, max]` on the grid), not the native UI stepping.\nimport { decimalsOf, roundTo } from \"../internal/decimal.js\";\n\nexport interface StepBounds {\n readonly min: number | undefined;\n readonly max: number | undefined;\n /** Positive. */\n readonly step: number;\n}\n\n/** Tolerance for grid computations on binary floating point. */\nconst EPSILON = 1e-9;\n\nfunction decimalsFor(bounds: StepBounds): number {\n return Math.max(decimalsOf(bounds.step), decimalsOf(bounds.min ?? 0));\n}\n\n/** The largest grid point `≤ max` (an unreachable `max` stays unreachable: `0..5` by `2` is `4`). */\nfunction gridMax(max: number, base: number, step: number): number {\n return base + Math.floor((max - base) / step + EPSILON) * step;\n}\n\nfunction clampAndGuard(\n result: number,\n current: number | null,\n direction: number,\n bounds: StepBounds,\n): number {\n const { min, max, step } = bounds;\n const base = min ?? 0;\n let next = result;\n if (max !== undefined && next > max + EPSILON) next = gridMax(max, base, step);\n if (min !== undefined && next < min - EPSILON) next = min;\n next = roundTo(next, decimalsFor(bounds));\n // 4. A step that would move against its direction changes nothing (an empty value has no\n // direction to keep: empty + ArrowDown with `min 3` is `3`, design 5.5).\n if (current !== null) {\n if (direction > 0 && next < current) return current;\n if (direction < 0 && next > current) return current;\n }\n return next;\n}\n\n/**\n * `current` stepped `n` times (`n > 0` up, `n < 0` down): empty counts as `0`; the step base is\n * `min` (else `0`); an off-grid value snaps to the nearest grid point in the stepping direction,\n * which counts as one step; the result is clamped to `[min, max]` on the grid and rounded to the\n * decimals of `step` and `min`. When the clamp would move the value against the direction, the\n * value is returned unchanged.\n */\nexport function stepValue(current: number | null, n: number, bounds: StepBounds): number {\n const { min, step } = bounds;\n const value = current ?? 0;\n const base = min ?? 0;\n const index = (value - base) / step;\n const nearest = Math.round(index);\n let target: number;\n if (Math.abs(index - nearest) <= EPSILON * Math.max(1, Math.abs(index))) {\n target = nearest + n;\n } else if (n > 0) {\n target = Math.ceil(index) + (n - 1);\n } else {\n target = Math.floor(index) + (n + 1);\n }\n return clampAndGuard(base + target * step, current, n, bounds);\n}\n\n/**\n * `current` moved by `delta` (PageUp / PageDown, Shift+Arrow: `± largeStep`). A whole number of\n * steps is `stepValue(current, delta / step)`; otherwise the value moves by `delta` and snaps to\n * the grid toward the start (never past `delta`), at least one step.\n */\nexport function stepBy(current: number | null, delta: number, bounds: StepBounds): number {\n const { min, step } = bounds;\n const ratio = delta / step;\n if (Math.abs(ratio - Math.round(ratio)) <= EPSILON * Math.max(1, Math.abs(ratio))) {\n return stepValue(current, Math.round(ratio), bounds);\n }\n const value = current ?? 0;\n const base = min ?? 0;\n const index = (value + delta - base) / step;\n let target = delta > 0 ? Math.floor(index + EPSILON) : Math.ceil(index - EPSILON);\n const start = (value - base) / step;\n if (delta > 0 && target <= start + EPSILON) target = Math.floor(start + EPSILON) + 1;\n if (delta < 0 && target >= start - EPSILON) target = Math.ceil(start - EPSILON) - 1;\n return clampAndGuard(base + target * step, current, delta, bounds);\n}\n\n/** The bound a Home / End key moves to: `min`, or the largest grid point `≤ max`. */\nexport function boundValue(which: \"min\" | \"max\", bounds: StepBounds): number | undefined {\n const { min, max, step } = bounds;\n if (which === \"min\") return min;\n if (max === undefined) return undefined;\n if (min !== undefined && max < min) return min;\n return roundTo(gridMax(max, min ?? 0, step), decimalsFor(bounds));\n}\n\n/** The next step in `direction` would change nothing (a button at its bound). Empty never is. */\nexport function isAtBound(value: number | null, direction: 1 | -1, bounds: StepBounds): boolean {\n if (value === null) return false;\n return stepValue(value, direction, bounds) === value;\n}\n"],"mappings":";;;AAaA,MAAM,UAAU;AAEhB,SAAS,YAAY,QAA4B;CAC/C,OAAO,KAAK,IAAI,WAAW,OAAO,IAAI,GAAG,WAAW,OAAO,OAAO,CAAC,CAAC;AACtE;;AAGA,SAAS,QAAQ,KAAa,MAAc,MAAsB;CAChE,OAAO,OAAO,KAAK,OAAO,MAAM,QAAQ,OAAO,OAAO,IAAI;AAC5D;AAEA,SAAS,cACP,QACA,SACA,WACA,QACQ;CACR,MAAM,EAAE,KAAK,KAAK,SAAS;CAC3B,MAAM,OAAO,OAAO;CACpB,IAAI,OAAO;CACX,IAAI,QAAQ,KAAA,KAAa,OAAO,MAAM,SAAS,OAAO,QAAQ,KAAK,MAAM,IAAI;CAC7E,IAAI,QAAQ,KAAA,KAAa,OAAO,MAAM,SAAS,OAAO;CACtD,OAAO,QAAQ,MAAM,YAAY,MAAM,CAAC;CAGxC,IAAI,YAAY,MAAM;EACpB,IAAI,YAAY,KAAK,OAAO,SAAS,OAAO;EAC5C,IAAI,YAAY,KAAK,OAAO,SAAS,OAAO;CAC9C;CACA,OAAO;AACT;;;;;;;;AASA,SAAgB,UAAU,SAAwB,GAAW,QAA4B;CACvF,MAAM,EAAE,KAAK,SAAS;CACtB,MAAM,QAAQ,WAAW;CACzB,MAAM,OAAO,OAAO;CACpB,MAAM,SAAS,QAAQ,QAAQ;CAC/B,MAAM,UAAU,KAAK,MAAM,KAAK;CAChC,IAAI;CACJ,IAAI,KAAK,IAAI,QAAQ,OAAO,KAAK,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,CAAC,GACpE,SAAS,UAAU;MACd,IAAI,IAAI,GACb,SAAS,KAAK,KAAK,KAAK,KAAK,IAAI;MAEjC,SAAS,KAAK,MAAM,KAAK,KAAK,IAAI;CAEpC,OAAO,cAAc,OAAO,SAAS,MAAM,SAAS,GAAG,MAAM;AAC/D;;;;;;AAOA,SAAgB,OAAO,SAAwB,OAAe,QAA4B;CACxF,MAAM,EAAE,KAAK,SAAS;CACtB,MAAM,QAAQ,QAAQ;CACtB,IAAI,KAAK,IAAI,QAAQ,KAAK,MAAM,KAAK,CAAC,KAAK,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,CAAC,GAC9E,OAAO,UAAU,SAAS,KAAK,MAAM,KAAK,GAAG,MAAM;CAErD,MAAM,QAAQ,WAAW;CACzB,MAAM,OAAO,OAAO;CACpB,MAAM,SAAS,QAAQ,QAAQ,QAAQ;CACvC,IAAI,SAAS,QAAQ,IAAI,KAAK,MAAM,QAAQ,OAAO,IAAI,KAAK,KAAK,QAAQ,OAAO;CAChF,MAAM,SAAS,QAAQ,QAAQ;CAC/B,IAAI,QAAQ,KAAK,UAAU,QAAQ,SAAS,SAAS,KAAK,MAAM,QAAQ,OAAO,IAAI;CACnF,IAAI,QAAQ,KAAK,UAAU,QAAQ,SAAS,SAAS,KAAK,KAAK,QAAQ,OAAO,IAAI;CAClF,OAAO,cAAc,OAAO,SAAS,MAAM,SAAS,OAAO,MAAM;AACnE;;AAGA,SAAgB,WAAW,OAAsB,QAAwC;CACvF,MAAM,EAAE,KAAK,KAAK,SAAS;CAC3B,IAAI,UAAU,OAAO,OAAO;CAC5B,IAAI,QAAQ,KAAA,GAAW,OAAO,KAAA;CAC9B,IAAI,QAAQ,KAAA,KAAa,MAAM,KAAK,OAAO;CAC3C,OAAO,QAAQ,QAAQ,KAAK,OAAO,GAAG,IAAI,GAAG,YAAY,MAAM,CAAC;AAClE;;AAGA,SAAgB,UAAU,OAAsB,WAAmB,QAA6B;CAC9F,IAAI,UAAU,MAAM,OAAO;CAC3B,OAAO,UAAU,OAAO,WAAW,MAAM,MAAM;AACjD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, use } from "react";
|
|
3
|
+
//#region src/number-field/number-field-context.ts
|
|
4
|
+
const NumberFieldContext = createContext(null);
|
|
5
|
+
function useNumberFieldContext(part) {
|
|
6
|
+
const context = use(NumberFieldContext);
|
|
7
|
+
if (context === null) throw new Error(`${part} must be rendered inside a NumberField.Root.`);
|
|
8
|
+
return context;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { NumberFieldContext, useNumberFieldContext };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=number-field-context.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number-field-context.mjs","names":[],"sources":["../../src/number-field/number-field-context.ts"],"sourcesContent":["\"use client\";\nimport { createContext, use } from \"react\";\nimport type { RefObject } from \"react\";\nimport type { StepBounds } from \"./number-field-arithmetic.js\";\nimport type { NumberFieldState } from \"./number-field-root.js\";\n\n/** A step button's direction. */\nexport type StepDirection = \"increment\" | \"decrement\";\n\n/** What produced a step or a commit (`details.source`). */\nexport type NumberFieldSource = \"input\" | \"increment\" | \"decrement\" | \"keyboard\" | \"wheel\" | null;\n\nexport interface NumberFieldContextValue {\n /** The state every part's function props receive. */\n readonly state: NumberFieldState;\n readonly bounds: StepBounds;\n /** What a button steps from: the parsed draft while one is open, else the shown value. */\n readonly stepBase: number | null;\n /** The Root is pending and the latest step or commit came from this source. */\n readonly pendingSource: NumberFieldSource;\n /** Ids: the visible input (the Root's, or a Field's control id) and the Label. */\n readonly inputId: string;\n readonly labelId: string;\n readonly name: string | undefined;\n readonly form: string | undefined;\n /** `inputmode=\"decimal\"` rather than `\"numeric\"` (8.5). */\n readonly allowsFractions: boolean;\n readonly stepOnWheel: boolean;\n /** The hidden input's `defaultValue`: the reset target as `String(number)` or `\"\"`. */\n readonly resetTargetText: string;\n /** Inside a `Field.Root`: `data-invalid` on the Root and the Input. */\n readonly fieldInvalid: boolean | undefined;\n readonly visibleRef: RefObject<HTMLInputElement | null>;\n readonly hiddenRef: RefObject<HTMLInputElement | null>;\n\n /** `input` on the visible input: the urgent draft. */\n readonly typed: (text: string) => void;\n /** `focusout` on the visible input: commit the draft (4.1). */\n readonly commitDraft: (event: Event) => void;\n /** `keydown` on the visible input (4.2). */\n readonly keyDown: (event: KeyboardEvent) => void;\n /** The native `wheel` listener on the visible input (4.4). */\n readonly wheel: (event: WheelEvent) => void;\n /** `pointerdown` on a button: the first step; `false` when no hold is open afterwards. */\n readonly beginHold: (direction: StepDirection, event: Event) => boolean;\n /** One tick of the hold; `false` when the hold ended (the bound was reached). */\n readonly tickHold: (direction: StepDirection, event: Event | undefined) => boolean;\n /** The hold's release (or cancel): one commit. */\n readonly endHold: (direction: StepDirection, event: Event) => void;\n /** A click with no pointer gesture (assistive technology): step + commit. */\n readonly activate: (direction: StepDirection, event: Event) => void;\n /** Mirrors the hidden input's `validationMessage` onto the visible input (6.2). */\n readonly mirror: () => void;\n /** Called by the Label from a layout effect; returns the unregistration. */\n readonly registerLabel: () => () => void;\n /** Called by the Input from its mount layout effect (late mount reconciliation). */\n readonly onInputMounted: () => void;\n}\n\nexport const NumberFieldContext = createContext<NumberFieldContextValue | null>(null);\n\nexport function useNumberFieldContext(part: string): NumberFieldContextValue {\n const context = use(NumberFieldContext);\n if (context === null) {\n throw new Error(`${part} must be rendered inside a NumberField.Root.`);\n }\n return context;\n}\n"],"mappings":";;;AA2DA,MAAa,qBAAqB,cAA8C,IAAI;AAEpF,SAAgB,sBAAsB,MAAuC;CAC3E,MAAM,UAAU,IAAI,kBAAkB;CACtC,IAAI,YAAY,MACd,MAAM,IAAI,MAAM,GAAG,KAAK,6CAA6C;CAEvE,OAAO;AACT"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NumberFieldDecrementProps } from "./number-field-increment.mjs";
|
|
2
|
+
//#region src/number-field/number-field-decrement.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Steps the value down: `button[type=button][tabindex=-1]` with `aria-controls` → the input and
|
|
5
|
+
* the default `aria-label` `"Decrease"`; one step at the press, then repeated while held, one
|
|
6
|
+
* commit at the release. Natively `disabled` when the next step would change nothing.
|
|
7
|
+
*
|
|
8
|
+
* Owned keys: `type`, `tabIndex`, `aria-controls`, `disabled`, `onPointerDown`, `onClick`,
|
|
9
|
+
* `onContextMenu`, `data-pending`, `data-disabled`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function NumberFieldDecrement(props: NumberFieldDecrementProps): import("react").ReactNode;
|
|
12
|
+
export declare namespace NumberFieldDecrement {
|
|
13
|
+
var displayName: string;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
//# sourceMappingURL=number-field-decrement.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number-field-decrement.d.mts","names":[],"sources":["../../src/number-field/number-field-decrement.tsx"],"mappings":";;;;;;;;;;wBAcgB,qBAAqB,OAAO,4CAAyB;yBAArD"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useStepButtonElement } from "./number-field-increment.mjs";
|
|
3
|
+
//#region src/number-field/number-field-decrement.tsx
|
|
4
|
+
/**
|
|
5
|
+
* Steps the value down: `button[type=button][tabindex=-1]` with `aria-controls` → the input and
|
|
6
|
+
* the default `aria-label` `"Decrease"`; one step at the press, then repeated while held, one
|
|
7
|
+
* commit at the release. Natively `disabled` when the next step would change nothing.
|
|
8
|
+
*
|
|
9
|
+
* Owned keys: `type`, `tabIndex`, `aria-controls`, `disabled`, `onPointerDown`, `onClick`,
|
|
10
|
+
* `onContextMenu`, `data-pending`, `data-disabled`.
|
|
11
|
+
*/
|
|
12
|
+
function NumberFieldDecrement(props) {
|
|
13
|
+
return useStepButtonElement("decrement", "NumberField.Decrement", props);
|
|
14
|
+
}
|
|
15
|
+
NumberFieldDecrement.displayName = "NumberFieldDecrement";
|
|
16
|
+
//#endregion
|
|
17
|
+
export { NumberFieldDecrement };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=number-field-decrement.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number-field-decrement.mjs","names":[],"sources":["../../src/number-field/number-field-decrement.tsx"],"sourcesContent":["\"use client\";\nimport { useStepButtonElement } from \"./number-field-increment.js\";\nimport type { NumberFieldDecrementProps } from \"./number-field-increment.js\";\n\nexport type { NumberFieldDecrementProps } from \"./number-field-increment.js\";\n\n/**\n * Steps the value down: `button[type=button][tabindex=-1]` with `aria-controls` → the input and\n * the default `aria-label` `\"Decrease\"`; one step at the press, then repeated while held, one\n * commit at the release. Natively `disabled` when the next step would change nothing.\n *\n * Owned keys: `type`, `tabIndex`, `aria-controls`, `disabled`, `onPointerDown`, `onClick`,\n * `onContextMenu`, `data-pending`, `data-disabled`.\n */\nexport function NumberFieldDecrement(props: NumberFieldDecrementProps) {\n return useStepButtonElement(\"decrement\", \"NumberField.Decrement\", props);\n}\n\nNumberFieldDecrement.displayName = \"NumberFieldDecrement\";\n"],"mappings":";;;;;;;;;;;AAcA,SAAgB,qBAAqB,OAAkC;CACrE,OAAO,qBAAqB,aAAa,yBAAyB,KAAK;AACzE;AAEA,qBAAqB,cAAc"}
|