@octanejs/base-ui 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +33 -0
  3. package/package.json +47 -0
  4. package/src/avatar.ts +308 -0
  5. package/src/checkbox-group.ts +183 -0
  6. package/src/checkbox.ts +578 -0
  7. package/src/field.ts +676 -0
  8. package/src/fieldset.ts +150 -0
  9. package/src/form.ts +183 -0
  10. package/src/index.ts +23 -0
  11. package/src/input.ts +9 -0
  12. package/src/internal.ts +42 -0
  13. package/src/merge-props.ts +2 -0
  14. package/src/meter.ts +204 -0
  15. package/src/number-field.ts +978 -0
  16. package/src/progress.ts +260 -0
  17. package/src/radio-group.ts +285 -0
  18. package/src/radio.ts +401 -0
  19. package/src/separator.ts +43 -0
  20. package/src/slider.ts +1617 -0
  21. package/src/switch.ts +374 -0
  22. package/src/toggle-group.ts +136 -0
  23. package/src/toggle.ts +145 -0
  24. package/src/use-render.ts +40 -0
  25. package/src/utils/CheckboxGroupContext.ts +30 -0
  26. package/src/utils/CompositeRootContext.ts +27 -0
  27. package/src/utils/DirectionContext.ts +15 -0
  28. package/src/utils/RadioGroupContext.ts +27 -0
  29. package/src/utils/ToggleGroupContext.ts +33 -0
  30. package/src/utils/addEventListener.ts +13 -0
  31. package/src/utils/areArraysEqual.ts +11 -0
  32. package/src/utils/clamp.ts +8 -0
  33. package/src/utils/composeRefs.ts +53 -0
  34. package/src/utils/composite/CompositeItem.ts +56 -0
  35. package/src/utils/composite/CompositeList.ts +190 -0
  36. package/src/utils/composite/CompositeListContext.ts +23 -0
  37. package/src/utils/composite/CompositeRoot.ts +132 -0
  38. package/src/utils/composite/keys.ts +179 -0
  39. package/src/utils/composite/list-utils.ts +78 -0
  40. package/src/utils/composite/useCompositeItem.ts +58 -0
  41. package/src/utils/composite/useCompositeListItem.ts +108 -0
  42. package/src/utils/composite/useCompositeRoot.ts +346 -0
  43. package/src/utils/contains.ts +22 -0
  44. package/src/utils/createChangeEventDetails.ts +81 -0
  45. package/src/utils/field/FieldItemContext.ts +13 -0
  46. package/src/utils/field/FieldRootContext.ts +98 -0
  47. package/src/utils/field/FormContext.ts +37 -0
  48. package/src/utils/field/LabelableContext.ts +32 -0
  49. package/src/utils/field/LabelableProvider.ts +108 -0
  50. package/src/utils/field/constants.ts +80 -0
  51. package/src/utils/field/getCombinedFieldValidityData.ts +16 -0
  52. package/src/utils/field/useAriaLabelledBy.ts +86 -0
  53. package/src/utils/field/useFieldControlRegistration.ts +179 -0
  54. package/src/utils/field/useFieldValidation.ts +311 -0
  55. package/src/utils/field/useLabel.ts +95 -0
  56. package/src/utils/field/useLabelableId.ts +99 -0
  57. package/src/utils/field/useRegisterFieldControl.ts +41 -0
  58. package/src/utils/formatNumber.ts +44 -0
  59. package/src/utils/getDefaultFormSubmitter.ts +19 -0
  60. package/src/utils/getElementRef.ts +13 -0
  61. package/src/utils/getStateAttributesProps.ts +34 -0
  62. package/src/utils/matchesFocusVisible.ts +16 -0
  63. package/src/utils/mergeObjects.ts +18 -0
  64. package/src/utils/mergeProps.ts +206 -0
  65. package/src/utils/noop.ts +4 -0
  66. package/src/utils/number/constants.ts +4 -0
  67. package/src/utils/number/parse.ts +227 -0
  68. package/src/utils/number/types.ts +25 -0
  69. package/src/utils/number/validate.ts +129 -0
  70. package/src/utils/owner.ts +9 -0
  71. package/src/utils/platform.ts +15 -0
  72. package/src/utils/resolveAriaLabelledBy.ts +11 -0
  73. package/src/utils/resolveClassName.ts +8 -0
  74. package/src/utils/resolveRef.ts +10 -0
  75. package/src/utils/resolveStyle.ts +10 -0
  76. package/src/utils/serializeValue.ts +15 -0
  77. package/src/utils/slider/asc.ts +4 -0
  78. package/src/utils/slider/getMidpoint.ts +14 -0
  79. package/src/utils/slider/getPushedThumbValues.ts +73 -0
  80. package/src/utils/slider/getSliderValue.ts +27 -0
  81. package/src/utils/slider/replaceArrayItemAtIndex.ts +8 -0
  82. package/src/utils/slider/resolveThumbCollision.ts +178 -0
  83. package/src/utils/slider/roundValueToStep.ts +22 -0
  84. package/src/utils/slider/validateMinimumDistance.ts +22 -0
  85. package/src/utils/slider/valueArrayToPercentages.ts +11 -0
  86. package/src/utils/stringifyLocale.ts +10 -0
  87. package/src/utils/useAnimationFrame.ts +59 -0
  88. package/src/utils/useAnimationsFinished.ts +100 -0
  89. package/src/utils/useBaseUiId.ts +19 -0
  90. package/src/utils/useButton.ts +217 -0
  91. package/src/utils/useCheckboxGroupParent.ts +113 -0
  92. package/src/utils/useControlled.ts +42 -0
  93. package/src/utils/useFocusableWhenDisabled.ts +84 -0
  94. package/src/utils/useForcedRerendering.ts +11 -0
  95. package/src/utils/useIsHydrating.ts +9 -0
  96. package/src/utils/useOpenChangeComplete.ts +46 -0
  97. package/src/utils/usePressAndHold.ts +35 -0
  98. package/src/utils/useRefWithInit.ts +23 -0
  99. package/src/utils/useRegisteredLabelId.ts +32 -0
  100. package/src/utils/useRenderElement.ts +165 -0
  101. package/src/utils/useStableCallback.ts +26 -0
  102. package/src/utils/useTimeout.ts +51 -0
  103. package/src/utils/useTransitionStatus.ts +115 -0
  104. package/src/utils/useValueAsRef.ts +25 -0
  105. package/src/utils/useValueChanged.ts +37 -0
  106. package/src/utils/valueToPercent.ts +4 -0
  107. package/src/utils/visuallyHidden.ts +24 -0
@@ -0,0 +1,165 @@
1
+ // Ported from .base-ui/packages/react/src/internals/useRenderElement.tsx (v1.6.0).
2
+ //
3
+ // The single composition engine every Base UI part routes through. It resolves the
4
+ // component's `render` (a JSX element OR `(props, state) => element`) + `className`
5
+ // (string|fn) + `style` (object|fn) + `state`→`data-*`, merges them with the
6
+ // behavior/intrinsic props, composes refs, and produces the final element via
7
+ // octane's `cloneElement`/`createElement`.
8
+ //
9
+ // octane specifics: no rules of hooks, so the ref-merge hook is called
10
+ // unconditionally (Base UI's `typeof document` + conditional-hook dance is dropped);
11
+ // `render`-as-function returns octane JSX directly; ref is a plain prop.
12
+ //
13
+ // SLOT: this is a plain-`.ts` hook consumed from source (excluded from auto-slotting),
14
+ // so it forwards the caller's slot. The component passes `subSlot(componentSlot, 're')`
15
+ // as the trailing arg.
16
+ import { createElement, cloneElement } from 'octane';
17
+
18
+ import { splitSlot, subSlot, S } from '../internal';
19
+ import { useComposedRefs } from './composeRefs';
20
+ import { getElementRef } from './getElementRef';
21
+ import { getStateAttributesProps, type StateAttributesMapping } from './getStateAttributesProps';
22
+ import { mergeObjects } from './mergeObjects';
23
+ import { resolveClassName } from './resolveClassName';
24
+ import { resolveStyle } from './resolveStyle';
25
+ import { mergeProps, mergePropsN, mergeClassNames } from './mergeProps';
26
+
27
+ const EMPTY_OBJECT: Record<string, any> = {};
28
+
29
+ export type RenderProp<State> =
30
+ | ((props: Record<string, any>, state: State) => any)
31
+ | any /* element descriptor */;
32
+
33
+ export interface UseRenderElementComponentProps<State> {
34
+ className?: string | ((state: State) => string | undefined);
35
+ render?: RenderProp<State>;
36
+ style?: Record<string, any> | ((state: State) => Record<string, any> | undefined);
37
+ }
38
+
39
+ export interface UseRenderElementParameters<State> {
40
+ enabled?: boolean;
41
+ ref?: any;
42
+ state?: State;
43
+ props?: Record<string, any> | Array<Record<string, any> | undefined | ((p: any) => any)>;
44
+ stateAttributesMapping?: StateAttributesMapping<State>;
45
+ }
46
+
47
+ export function useRenderElement<State extends Record<string, any>>(
48
+ element: string | undefined,
49
+ componentProps: UseRenderElementComponentProps<State>,
50
+ params: UseRenderElementParameters<State> = {},
51
+ ...slotArgs: any[]
52
+ ): any {
53
+ const [, slotArg] = splitSlot(['_', ...slotArgs]);
54
+ const slot = slotArg ?? S('useRenderElement');
55
+
56
+ const renderProp = componentProps.render;
57
+ const outProps = useRenderElementProps(componentProps, params, subSlot(slot, 'rp'));
58
+
59
+ if (params.enabled === false) {
60
+ return null;
61
+ }
62
+
63
+ const state = params.state ?? (EMPTY_OBJECT as State);
64
+ return evaluateRenderProp(element, renderProp, outProps, state);
65
+ }
66
+
67
+ function useRenderElementProps<State extends Record<string, any>>(
68
+ componentProps: UseRenderElementComponentProps<State>,
69
+ params: UseRenderElementParameters<State>,
70
+ slot: symbol | undefined,
71
+ ): Record<string, any> {
72
+ const { className: classNameProp, style: styleProp, render: renderProp } = componentProps;
73
+ const {
74
+ state = EMPTY_OBJECT as State,
75
+ ref,
76
+ props,
77
+ stateAttributesMapping,
78
+ enabled = true,
79
+ } = params;
80
+
81
+ const className = enabled ? resolveClassName(classNameProp, state) : undefined;
82
+ const style = enabled ? resolveStyle(styleProp, state) : undefined;
83
+ const stateProps = enabled
84
+ ? getStateAttributesProps(state, stateAttributesMapping)
85
+ : EMPTY_OBJECT;
86
+
87
+ const resolvedProps = enabled && props ? resolveRenderFunctionProps(props) : undefined;
88
+
89
+ // When enabled, always a FRESH mutable object (never the shared EMPTY_OBJECT — we set
90
+ // ref/className/style below). Matches Base UI's `?? {}`.
91
+ const outProps: Record<string, any> = enabled
92
+ ? (mergeObjects(stateProps, resolvedProps) ?? {})
93
+ : EMPTY_OBJECT;
94
+
95
+ // Compose the intrinsic-prop ref, the render-element's own ref, and the forwarded
96
+ // ref into one. octane has no rules of hooks, so this is unconditional; the
97
+ // composed-refs hook is memoized under its own sub-slot.
98
+ const renderRef = getElementRef(renderProp);
99
+ const refs = Array.isArray(ref)
100
+ ? [outProps.ref, renderRef, ...ref]
101
+ : [outProps.ref, renderRef, ref];
102
+ const composedRef = useComposedRefs(...refs, subSlot(slot, 'refs'));
103
+
104
+ // When disabled, `outProps` IS the shared EMPTY_OBJECT — assigning `.ref` (or
105
+ // className/style) would mutate it and leak (e.g. a later component reading default
106
+ // state via getStateAttributesProps would render `data-ref="<fn>"`). Return it
107
+ // untouched; the composed-refs hook still ran above for slot stability.
108
+ if (!enabled) {
109
+ return EMPTY_OBJECT;
110
+ }
111
+
112
+ outProps.ref = composedRef;
113
+
114
+ if (className !== undefined) {
115
+ outProps.className = mergeClassNames(outProps.className, className);
116
+ }
117
+ if (style !== undefined) {
118
+ outProps.style = mergeObjects(outProps.style, style);
119
+ }
120
+
121
+ return outProps;
122
+ }
123
+
124
+ function evaluateRenderProp<State>(
125
+ element: string | undefined,
126
+ render: RenderProp<State> | undefined,
127
+ props: Record<string, any>,
128
+ state: State,
129
+ ): any {
130
+ if (render) {
131
+ if (typeof render === 'function') {
132
+ return render(props, state);
133
+ }
134
+ // Element descriptor: merge the behavior props UNDER the element's own props
135
+ // (external wins), thread the composed ref, and clone.
136
+ const mergedProps = mergeProps(props, (render as any).props);
137
+ mergedProps.ref = props.ref;
138
+ return cloneElement(render, mergedProps);
139
+ }
140
+ if (element) {
141
+ return renderTag(element, props);
142
+ }
143
+ throw new Error('Base UI: render element or function are not defined.');
144
+ }
145
+
146
+ // A single (non-array) `props` goes through `mergeProps(undefined, props)` so its event
147
+ // handlers are wrapped (preventable) like an array's would be via `mergePropsN`.
148
+ function resolveRenderFunctionProps(
149
+ props: NonNullable<UseRenderElementParameters<any>['props']>,
150
+ ): Record<string, any> {
151
+ if (Array.isArray(props)) {
152
+ return mergePropsN(props);
153
+ }
154
+ return mergeProps(undefined, props);
155
+ }
156
+
157
+ function renderTag(tag: string, props: Record<string, any>): any {
158
+ if (tag === 'button') {
159
+ return createElement('button', { type: 'button', ...props });
160
+ }
161
+ if (tag === 'img') {
162
+ return createElement('img', { alt: '', ...props });
163
+ }
164
+ return createElement(tag, props);
165
+ }
@@ -0,0 +1,26 @@
1
+ // Ported from .base-ui/packages/utils/src/useStableCallback.ts. Returns a stable
2
+ // (identity-constant) trampoline that always calls the LATEST callback — safe to pass as
3
+ // a memo/effect dependency without re-triggering. Base UI uses `useInsertionEffect` to
4
+ // swap the callback; octane has no insertion effect, so (like @octanejs/floating-ui's
5
+ // useEffectEvent) we refresh the ref synchronously each render. The dev "called during
6
+ // render" guard is dropped (dev-only surface).
7
+ //
8
+ // SLOT: plain-`.ts` hook; the trailing arg is the caller's slot.
9
+ import { useCallback, useRef } from 'octane';
10
+
11
+ import { S, splitSlot, subSlot } from '../internal';
12
+
13
+ export function useStableCallback<T extends (...args: any[]) => any>(...args: any[]): T {
14
+ const [user, slotArg] = splitSlot(args);
15
+ const slot = slotArg ?? S('useStableCallback');
16
+ const callback = user[0] as T | undefined;
17
+
18
+ const ref = useRef<T | undefined>(undefined, subSlot(slot, 'ref'));
19
+ ref.current = callback;
20
+
21
+ return useCallback(
22
+ ((...a: any[]) => (ref.current == null ? undefined : ref.current(...a))) as T,
23
+ [],
24
+ subSlot(slot, 'cb'),
25
+ );
26
+ }
@@ -0,0 +1,51 @@
1
+ // Ported from .base-ui/packages/utils/src/useTimeout.ts. A `setTimeout` with automatic
2
+ // cleanup on unmount. `useOnMount` → an octane effect with empty deps whose cleanup runs on
3
+ // unmount.
4
+ //
5
+ // SLOT: `useTimeout` is a plain-`.ts` hook; the trailing arg is the caller's slot.
6
+ import { useEffect } from 'octane';
7
+
8
+ import { S, splitSlot, subSlot } from '../internal';
9
+ import { useRefWithInit } from './useRefWithInit';
10
+
11
+ const EMPTY = 0;
12
+
13
+ export class Timeout {
14
+ static create(): Timeout {
15
+ return new Timeout();
16
+ }
17
+
18
+ currentId: number = EMPTY;
19
+
20
+ /** Executes `fn` after `delay`, clearing any previously scheduled call. */
21
+ start(delay: number, fn: Function): void {
22
+ this.clear();
23
+ this.currentId = setTimeout(() => {
24
+ this.currentId = EMPTY;
25
+ fn();
26
+ }, delay) as unknown as number;
27
+ }
28
+
29
+ isStarted(): boolean {
30
+ return this.currentId !== EMPTY;
31
+ }
32
+
33
+ clear = (): void => {
34
+ if (this.currentId !== EMPTY) {
35
+ clearTimeout(this.currentId);
36
+ this.currentId = EMPTY;
37
+ }
38
+ };
39
+
40
+ disposeEffect = (): (() => void) => {
41
+ return this.clear;
42
+ };
43
+ }
44
+
45
+ export function useTimeout(...args: any[]): Timeout {
46
+ const [, slotArg] = splitSlot(['_', ...args]);
47
+ const slot = slotArg ?? S('useTimeout');
48
+ const timeout = useRefWithInit<Timeout>(Timeout.create, subSlot(slot, 'to')).current;
49
+ useEffect(timeout.disposeEffect, [], subSlot(slot, 'e:mount'));
50
+ return timeout;
51
+ }
@@ -0,0 +1,115 @@
1
+ // Ported from .base-ui/packages/react/src/internals/useTransitionStatus.ts +
2
+ // internals/stateAttributesMapping.ts (transitionStatusMapping). Drives the CSS enter/exit
3
+ // animation status ('starting' | 'ending' | 'idle' | undefined) and the `mounted` flag.
4
+ // `useIsoLayoutEffect` → octane `useLayoutEffect`; the render-phase `setState` calls are kept
5
+ // as-is (octane supports bounded render-phase updates, like React).
6
+ //
7
+ // SLOT: `useTransitionStatus` is a plain-`.ts` hook; the trailing arg is the caller's slot.
8
+ import { useLayoutEffect, useState } from 'octane';
9
+
10
+ import { S, splitSlot, subSlot } from '../internal';
11
+ import { AnimationFrame } from './useAnimationFrame';
12
+ import type { StateAttributesMapping } from './getStateAttributesProps';
13
+
14
+ export type TransitionStatus = 'starting' | 'ending' | 'idle' | undefined;
15
+
16
+ const STARTING_HOOK = { 'data-starting-style': '' };
17
+ const ENDING_HOOK = { 'data-ending-style': '' };
18
+
19
+ export const transitionStatusMapping: StateAttributesMapping<{
20
+ transitionStatus: TransitionStatus;
21
+ }> = {
22
+ transitionStatus(value: TransitionStatus): Record<string, string> | null {
23
+ if (value === 'starting') {
24
+ return STARTING_HOOK;
25
+ }
26
+ if (value === 'ending') {
27
+ return ENDING_HOOK;
28
+ }
29
+ return null;
30
+ },
31
+ };
32
+
33
+ export function useTransitionStatus(...args: any[]): {
34
+ mounted: boolean;
35
+ setMounted: (next: boolean | ((prev: boolean) => boolean)) => void;
36
+ transitionStatus: TransitionStatus;
37
+ } {
38
+ const [user, slotArg] = splitSlot(args);
39
+ const slot = slotArg ?? S('useTransitionStatus');
40
+ const open = user[0] as boolean;
41
+ const enableIdleState = (user[1] as boolean | undefined) ?? false;
42
+ const deferEndingState = (user[2] as boolean | undefined) ?? false;
43
+
44
+ const [transitionStatus, setTransitionStatus] = useState<TransitionStatus>(
45
+ open && enableIdleState ? 'idle' : undefined,
46
+ subSlot(slot, 'ts'),
47
+ );
48
+ const [mounted, setMounted] = useState(open, subSlot(slot, 'mounted'));
49
+
50
+ if (open && !mounted) {
51
+ setMounted(true);
52
+ setTransitionStatus('starting');
53
+ }
54
+
55
+ if (!open && mounted && transitionStatus !== 'ending' && !deferEndingState) {
56
+ setTransitionStatus('ending');
57
+ }
58
+
59
+ if (!open && !mounted && transitionStatus === 'ending') {
60
+ setTransitionStatus(undefined);
61
+ }
62
+
63
+ useLayoutEffect(
64
+ () => {
65
+ if (!open && mounted && transitionStatus !== 'ending' && deferEndingState) {
66
+ const frame = AnimationFrame.request(() => {
67
+ setTransitionStatus('ending');
68
+ });
69
+ return () => {
70
+ AnimationFrame.cancel(frame);
71
+ };
72
+ }
73
+ return undefined;
74
+ },
75
+ [open, mounted, transitionStatus, deferEndingState],
76
+ subSlot(slot, 'e:defer'),
77
+ );
78
+
79
+ useLayoutEffect(
80
+ () => {
81
+ if (!open || enableIdleState) {
82
+ return undefined;
83
+ }
84
+ const frame = AnimationFrame.request(() => {
85
+ setTransitionStatus(undefined);
86
+ });
87
+ return () => {
88
+ AnimationFrame.cancel(frame);
89
+ };
90
+ },
91
+ [enableIdleState, open],
92
+ subSlot(slot, 'e:clear'),
93
+ );
94
+
95
+ useLayoutEffect(
96
+ () => {
97
+ if (!open || !enableIdleState) {
98
+ return undefined;
99
+ }
100
+ if (open && mounted && transitionStatus !== 'idle') {
101
+ setTransitionStatus('starting');
102
+ }
103
+ const frame = AnimationFrame.request(() => {
104
+ setTransitionStatus('idle');
105
+ });
106
+ return () => {
107
+ AnimationFrame.cancel(frame);
108
+ };
109
+ },
110
+ [enableIdleState, open, mounted, transitionStatus],
111
+ subSlot(slot, 'e:idle'),
112
+ );
113
+
114
+ return { mounted, setMounted, transitionStatus };
115
+ }
@@ -0,0 +1,25 @@
1
+ // Ported from .base-ui/packages/utils/src/useValueAsRef.ts — untracks a value into a ref
2
+ // so an effect can read the latest without re-running on change. Base UI's `.next`/`.effect`
3
+ // indirection collapses to setting `ref.current = value` in a layout effect that runs every
4
+ // render — the same observable behavior, and the same shape as @octanejs/floating-ui's
5
+ // `useLatestRef`.
6
+ //
7
+ // SLOT: plain-`.ts` hook; the trailing arg is the caller's slot.
8
+ import { useLayoutEffect, useRef } from 'octane';
9
+
10
+ import { S, splitSlot, subSlot } from '../internal';
11
+
12
+ export function useValueAsRef<T>(...args: any[]): { current: T } {
13
+ const [user, slotArg] = splitSlot(args);
14
+ const slot = slotArg ?? S('useValueAsRef');
15
+ const value = user[0] as T;
16
+ const ref = useRef(value, subSlot(slot, 'ref'));
17
+ useLayoutEffect(
18
+ () => {
19
+ ref.current = value;
20
+ },
21
+ undefined,
22
+ subSlot(slot, 'eff'),
23
+ );
24
+ return ref;
25
+ }
@@ -0,0 +1,37 @@
1
+ // Ported from .base-ui/packages/react/src/internals/useValueChanged.ts. Calls `onChange`
2
+ // (with the previous value) in a layout effect whenever `value` changes.
3
+ //
4
+ // SLOT: plain-`.ts` hook; the trailing arg is the caller's slot.
5
+ import { useLayoutEffect, useRef } from 'octane';
6
+
7
+ import { S, splitSlot, subSlot } from '../internal';
8
+ import { useStableCallback } from './useStableCallback';
9
+
10
+ export function useValueChanged<T>(...args: any[]): void {
11
+ const [user, slotArg] = splitSlot(args);
12
+ const slot = slotArg ?? S('useValueChanged');
13
+ const value = user[0] as T;
14
+ const onChange = user[1] as (previousValue: T) => void;
15
+
16
+ const valueRef = useRef(value, subSlot(slot, 'ref'));
17
+ const onChangeCallback = useStableCallback(onChange, subSlot(slot, 'cb'));
18
+
19
+ useLayoutEffect(
20
+ () => {
21
+ if (valueRef.current === value) {
22
+ return;
23
+ }
24
+ onChangeCallback(valueRef.current);
25
+ },
26
+ [value, onChangeCallback],
27
+ subSlot(slot, 'e:change'),
28
+ );
29
+
30
+ useLayoutEffect(
31
+ () => {
32
+ valueRef.current = value;
33
+ },
34
+ [value],
35
+ subSlot(slot, 'e:set'),
36
+ );
37
+ }
@@ -0,0 +1,4 @@
1
+ // Ported verbatim from .base-ui/packages/react/src/utils/valueToPercent.ts.
2
+ export function valueToPercent(value: number, min: number, max: number): number {
3
+ return ((value - min) * 100) / (max - min);
4
+ }
@@ -0,0 +1,24 @@
1
+ // Ported from .base-ui/packages/utils/src/visuallyHidden.ts — the screen-reader-only
2
+ // style object (a plain CSS-in-JS object; octane serializes it at the apply site).
3
+ const visuallyHiddenBase: Record<string, any> = {
4
+ clipPath: 'inset(50%)',
5
+ overflow: 'hidden',
6
+ whiteSpace: 'nowrap',
7
+ border: 0,
8
+ padding: 0,
9
+ width: 1,
10
+ height: 1,
11
+ margin: -1,
12
+ };
13
+
14
+ export const visuallyHidden: Record<string, any> = {
15
+ ...visuallyHiddenBase,
16
+ position: 'fixed',
17
+ top: 0,
18
+ left: 0,
19
+ };
20
+
21
+ export const visuallyHiddenInput: Record<string, any> = {
22
+ ...visuallyHiddenBase,
23
+ position: 'absolute',
24
+ };