aeon-ui-engine 1.5.1 → 1.8.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/CHANGELOG.md +112 -0
- package/package.json +2 -2
- package/packages/cli/node_modules/.bin/vitest +2 -2
- package/packages/core/node_modules/.bin/vitest +2 -2
- package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/panda/styled-system/styles.css +0 -4
- package/packages/primitives/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/react/dist/index.d.ts +29 -4
- package/packages/react/dist/index.js +85 -28
- package/packages/react/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/react/src/dialog/dialog.test.tsx +27 -0
- package/packages/react/src/dialog/index.tsx +11 -3
- package/packages/react/src/floating/FloatingPositioner.tsx +11 -1
- package/packages/react/src/menu/index.tsx +8 -3
- package/packages/react/src/popover/index.tsx +25 -4
- package/packages/react/src/slider/index.tsx +13 -6
- package/packages/schemas/dist/index.js +3 -1
- package/packages/schemas/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/schemas/prompts/statecharts.md +1 -1
- package/packages/schemas/registry/machines/slider.json +18 -1
- package/packages/tree/README.md +29 -0
- package/packages/tree/dist/index.d.ts +97 -0
- package/packages/tree/dist/index.js +107 -0
- package/packages/tree/node_modules/.bin/esbuild +21 -0
- package/packages/tree/node_modules/.bin/jiti +21 -0
- package/packages/tree/node_modules/.bin/tsc +21 -0
- package/packages/tree/node_modules/.bin/tsserver +21 -0
- package/packages/tree/node_modules/.bin/tsup +21 -0
- package/packages/tree/node_modules/.bin/tsup-node +21 -0
- package/packages/tree/node_modules/.bin/tsx +21 -0
- package/packages/tree/node_modules/.bin/vite +21 -0
- package/packages/tree/node_modules/.bin/vitest +21 -0
- package/packages/tree/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/tree/package.json +42 -0
- package/packages/tree/src/exclusive-action.test.ts +54 -0
- package/packages/tree/src/exclusive-action.ts +104 -0
- package/packages/tree/src/index.ts +7 -0
- package/packages/tree/src/interpret.tsx +45 -0
- package/packages/tree/src/totality.ts +20 -0
- package/packages/tree/src/widget.ts +31 -0
- package/packages/tree/tsconfig.json +8 -0
- package/packages/tree/vitest.config.ts +8 -0
- package/scripts/vite-aliases.mjs +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,117 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.8.0] - 2026-09-27
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`Popover.Positioner` / `FloatingPositioner` `anchorRef` + `matchAnchorWidth`.** Position a surface against the region it belongs to instead of the small trigger pinned to its edge, so a wide panel lands where the eye already is rather than being clamped back from the viewport edge.
|
|
8
|
+
- **Optional philosophy note:** [docs/ANALOGY.md](./docs/ANALOGY.md) — Aeon as metaphor for formless truth and sensory projection (not product doctrine). Linked from [PHILOSOPHY.md](./PHILOSOPHY.md) and the docs index.
|
|
9
|
+
- `@aeon-ui/tree` — widget trees. A widget is immutable data describing one face
|
|
10
|
+
of a chart, interpreted into DOM with `data-aeon-scope|part|state`. Flutter's
|
|
11
|
+
Widget and Element layers only: no RenderObject (layout stays CSS), no
|
|
12
|
+
`StatefulWidget` (the chart is the only state).
|
|
13
|
+
- `exclusiveActionWidget` — the shared face for "confirm, then await" regions
|
|
14
|
+
(`idle → confirming → busy → idle | failure`): pending label, disabled set,
|
|
15
|
+
failure copy and confirm slot are functions of the snapshot.
|
|
16
|
+
- `uncoveredStates(states, project)` — totality as a test: every state the chart
|
|
17
|
+
names must appear on the tree projected for it.
|
|
18
|
+
- `interpret(tree, hosts)` — element layer; hosts slot existing compounds
|
|
19
|
+
(`Prompt`, fields) into names the tree declares.
|
|
20
|
+
- Vite aliases, optimizeDeps exclude and tsconfig paths include `@aeon-ui/tree`.
|
|
21
|
+
|
|
22
|
+
## [1.7.7] - 2026-08-18
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **Three more primitives in the live section, chosen for chart complexity.** `Slider` is hierarchical — the snapshot reports `interaction.dragging`, so the chart lights the region *and* the leaf inside it while you drag, and an arrow key shows `SET_VALUE` writing context without leaving the state. `FileUpload` runs a real drag-and-drop lifecycle (`idle` / `dragging` / `rejected`), driveable by dragging a file or by sending the same events from buttons. `Toast` is the one chart that moves without you: `visible` carries a delayed `TIMEOUT` that retires it on the machine's own clock.
|
|
27
|
+
- **`useSliderContext`** exported from `@aeon-ui/react`, so a slider's live machine state (including the nested `interaction` value) is readable from inside `Slider.Root` — matching `useAsyncContext` / `useFieldContext` / `useStickyBarContext`.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **The live chart never highlighted a component's *initial* state.** A late SVG swap replaced the diagram after the highlight was painted, so the first snapshot silently went dark until you interacted. The explorer now repaints when the rendered chart changes.
|
|
32
|
+
- **`valid` also lit up `invalid`.** Node matching compared id substrings, so any state whose name contained another's was highlighted too. Matching is now segment-aware, and a region's `[*]` marker no longer inherits its parent's highlight.
|
|
33
|
+
- **Nested snapshot values are highlighted properly.** A dotted path such as `interaction.dragging` now lights every node on the path, since the ancestors are genuinely active.
|
|
34
|
+
- **Live charts are readable.** The explorer box was short enough that parallel and nested diagrams auto-fit to ~30%; it now has room, and the slider's nested region lays out left-to-right.
|
|
35
|
+
|
|
36
|
+
## [1.7.6] - 2026-08-18
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **Hero video: resolved an apparent contradiction in the narration.** "Nothing else exists" (about the async machine) could read as conflicting with the later child-machine states (broadcasting, seen, mined). The closed-world line is now scoped — "that's this machine's entire world" — and the hierarchy scene opens with the bridge: "Need more states? Charts nest." Transcript re-aligned (61s).
|
|
41
|
+
|
|
42
|
+
## [1.7.5] - 2026-08-18
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- **Hero video narration re-recorded for clarity.** "Statechart" is now fed to TTS as "state chart" so it isn't mispronounced (the on-screen subtitle keeps the correct spelling — `speak` overrides audio only, the same mechanism the closing URL already used). The parallel-regions explanation is rebuilt around three plain questions — "Has it been edited? Does it pass? Is it sending?" — and each chart lane now prints its question next to the region name, so the words and the diagram agree. Also clearer: "no in-between states" replaces "no half states", "your CSS targets that attribute" replaces "your styles key off it", and the child machine's middle state is spoken as "seen on the network" rather than a bare "seen". Transcript re-aligned (59s).
|
|
47
|
+
|
|
48
|
+
## [1.7.4] - 2026-08-18
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- **Hero video intro now shows the pain, and the ending is de-branded.** The opening scene names the actual symptoms of boolean soup — a mystery spinner with no context, two buttons wired to the same job (`Retry` / `Reload`), and "state is a mess" — instead of an abstract truth count. The narration no longer says the word "dirty" (the state node still shows it, since that's the real machine state). The hierarchy scene presents the nested send flow as "a real example / wallet · send" without naming the product. Transcript re-aligned (57s).
|
|
53
|
+
|
|
54
|
+
## [1.7.3] - 2026-08-18
|
|
55
|
+
|
|
56
|
+
### Changed
|
|
57
|
+
|
|
58
|
+
- **Hero video: plainer intro, glossed jargon, and a real hierarchical chart from a shipping product.** The intro now shows three plain `loading` / `empty` / `error` toggles with what each means in English, instead of prop-name jargon. The parallel-regions scene explains `dirty` outright ("dirty just means the user typed") and prints a plain-English gloss under the composite value. The machine catalog scene is replaced with the send flow from HandCash Desktop: the `sendPayment` chart (`editing → confirming → handoff`) with the `txLifecycle` child machine (`broadcasting → seenInMempool → mined`) nested inside `handoff` — the wallet surface and the breadcrumb (`handoff › broadcasting`) stay in lockstep with the chart. Also fixed a renderer bug where PIL ignored text alpha, so every text fade-in had been popping at full opacity (visible as floating state labels before their nodes appeared). Transcript re-aligned (55s).
|
|
59
|
+
|
|
60
|
+
## [1.7.2] - 2026-08-18
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
|
|
64
|
+
- **New hero video, built from scratch to teach statecharts — not one animated state.** Seven scenes: three booleans producing a screen with a spinner and an error at once; the async machine with every state named and every edge labelled with its event; an event with no matching edge blocked at the door; the field machine's three parallel regions all true at once beside the surface that projects them; `state → data-aeon-state → pixels`; and four components each running their own machine on their own clock. Real system typography (SF / SF Mono), a poster frame from the chart scene, faststart streaming, and re-aligned transcript cues (52s).
|
|
65
|
+
|
|
66
|
+
## [1.7.1] - 2026-08-18
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- **Hero video now shows the actual thesis.** Previously the statechart node swept on its own float while the phone face animated separately, so the chart and the screen never agreed. The centrepiece is now one coupled demo: the component face and the highlighted chart node are the same state, driven by a single step timeline, with the causing event (`FETCH`, `RESOLVE`, `REJECT`, `REFRESH`) labelled on the transition edge — the same idea as the live section. The closing card points at "See it live" instead of the removed builder; transcript cues re-aligned.
|
|
71
|
+
|
|
72
|
+
## [1.7.0] - 2026-08-18
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
|
|
76
|
+
- **Live section: real components beside the chart that tracks them.** Async, Button, Field, and Dialog run their shipped machines; the Mermaid chart highlights the state the component is actually in and names the event that caused the move. Field reports all three regions at once, because a field has no single state.
|
|
77
|
+
- Machine-accurate diagrams for `buttonLifecycleMachine`, `dialogMachine`, and `fieldMachine`; the async chart now shows its `REFRESH` and re-fetch edges. `aeonDiagrams.test.ts` fails if a chart misses a machine state or invents one.
|
|
78
|
+
|
|
79
|
+
### Fixed
|
|
80
|
+
|
|
81
|
+
- **A controlled `Dialog`, `Popover`, or `Menu` could never be opened by its own trigger.** `onOpenChange` reported the resolved prop, so an owner holding `open` never heard the machine's intent — the trigger, escape key, and outside click were all dead. Each root now reports the machine's own transition (and no longer fires on mount).
|
|
82
|
+
|
|
83
|
+
### Removed
|
|
84
|
+
|
|
85
|
+
- **The generative app builder is gone** — sandbox, chart→schema compiler, live composer, model client, cost meter, and the `/api/aeon-generate` and `/api/aeon-scrape` endpoints, plus the OpenRouter proxy plugin and its env keys (~6,000 lines). Landing nav, hero, and header CTAs point at the live section.
|
|
86
|
+
- The static "Determinism" diagram section, now that the live charts replace it. `expandActiveStates` lost its builder-specific token aliases.
|
|
87
|
+
|
|
88
|
+
## [1.6.1] - 2026-08-18
|
|
89
|
+
|
|
90
|
+
### Changed
|
|
91
|
+
|
|
92
|
+
- **Builder previews read like designs, not state dumps.** The first screen now leads with the site's own hero headline and description (scraped, never invented); other screens are titled by their state once. Actions promote one primary button and group the rest instead of stacking equal buttons. The app bar no longer repeats the state label the dock already shows.
|
|
93
|
+
- **Device selector is back** — phone / tablet / desktop toggle above the preview, with the desktop frame showing the scraped host in its address bar.
|
|
94
|
+
|
|
95
|
+
## [1.6.0] - 2026-08-18
|
|
96
|
+
|
|
97
|
+
### Added
|
|
98
|
+
|
|
99
|
+
- **Redesign any site from its URL** — paste a domain into the builder and `/api/aeon-scrape` reads the page for its name, description, nav, actions, logo, and `theme-color`. The brand becomes chrome (logo in the app bar, accent palette scoped to the preview) and the page content becomes the prompt. Private and non-HTTP hosts are refused.
|
|
100
|
+
- `builder/projectFaces.ts` — generic chart projection: states become screens, transitions become actions, `collect` demos add a field, `async` demos add an async region.
|
|
101
|
+
- Demo tests now run in CI (`pnpm test`), covering projection, scraping, palette derivation, and a server render of the builder output.
|
|
102
|
+
|
|
103
|
+
### Changed
|
|
104
|
+
|
|
105
|
+
- **App builder rebuilt around one input.** Prompt or URL, Build, preview. The starter chips, engine bar, cost meter, inspector tabs, device picker, and build loader are gone.
|
|
106
|
+
- **The model returns a chart and nothing else; the UI is projected from it.** Previously ~1,900 lines of prompt keyword matching decided that "match" meant a dating app, "faq" meant an accordion, and "pong" meant paddles, then filled screens with seeded catalogs, listings, and test stimuli. All of it is deleted, along with the preset app schemas it produced.
|
|
107
|
+
- Empty nav tabs are dropped instead of being filled with placeholder screens, and the offline fallback chart is deliberately shapeless rather than a product guess.
|
|
108
|
+
|
|
109
|
+
## [1.5.2] - 2026-08-18
|
|
110
|
+
|
|
111
|
+
### Changed
|
|
112
|
+
|
|
113
|
+
- **Landing page trimmed to essentials** — removed the pillars grid, the numbered setup box, and the full rule-file dump; the Cursor section is now one command plus a copy button. Hero shows a single install line, the why section keeps only the same-prompt contrast, specs show one chart, and the FAQ is four questions.
|
|
114
|
+
|
|
3
115
|
## [1.5.1] - 2026-08-18
|
|
4
116
|
|
|
5
117
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aeon-ui-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"dev": "pnpm --filter @aeon-ui/demo dev",
|
|
35
35
|
"examples": "pnpm --filter @aeon-ui/examples dev",
|
|
36
36
|
"typecheck": "pnpm -r run typecheck",
|
|
37
|
-
"test": "pnpm run test:headless",
|
|
37
|
+
"test": "pnpm run test:headless && pnpm --filter @aeon-ui/demo run test",
|
|
38
38
|
"test:headless": "pnpm run build:headless && pnpm --filter @aeon-ui/core --filter @aeon-ui/primitives --filter @aeon-ui/react run test",
|
|
39
39
|
"changeset": "changeset",
|
|
40
40
|
"version-packages": "changeset version",
|
|
@@ -10,9 +10,9 @@ case `uname` in
|
|
|
10
10
|
esac
|
|
11
11
|
|
|
12
12
|
if [ -z "$NODE_PATH" ]; then
|
|
13
|
-
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vitest@4.1.8_@types+node@22.19.19_jsdom@29.1.1_vite@8.0.16_@types+node@22.19.19_esbuild@0.
|
|
13
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vitest@4.1.8_@types+node@22.19.19_jsdom@29.1.1_vite@8.0.16_@types+node@22.19.19_esbuild@0.27.7_jiti@2.7.0_tsx@4.22.3_/node_modules/vitest/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vitest@4.1.8_@types+node@22.19.19_jsdom@29.1.1_vite@8.0.16_@types+node@22.19.19_esbuild@0.27.7_jiti@2.7.0_tsx@4.22.3_/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules"
|
|
14
14
|
else
|
|
15
|
-
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vitest@4.1.8_@types+node@22.19.19_jsdom@29.1.1_vite@8.0.16_@types+node@22.19.19_esbuild@0.
|
|
15
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vitest@4.1.8_@types+node@22.19.19_jsdom@29.1.1_vite@8.0.16_@types+node@22.19.19_esbuild@0.27.7_jiti@2.7.0_tsx@4.22.3_/node_modules/vitest/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vitest@4.1.8_@types+node@22.19.19_jsdom@29.1.1_vite@8.0.16_@types+node@22.19.19_esbuild@0.27.7_jiti@2.7.0_tsx@4.22.3_/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
16
|
fi
|
|
17
17
|
if [ -x "$basedir/node" ]; then
|
|
18
18
|
exec "$basedir/node" "$basedir/../vitest/vitest.mjs" "$@"
|
|
@@ -10,9 +10,9 @@ case `uname` in
|
|
|
10
10
|
esac
|
|
11
11
|
|
|
12
12
|
if [ -z "$NODE_PATH" ]; then
|
|
13
|
-
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vitest@4.1.8_@types+node@22.19.19_jsdom@29.1.1_vite@8.0.16_@types+node@22.19.19_esbuild@0.
|
|
13
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vitest@4.1.8_@types+node@22.19.19_jsdom@29.1.1_vite@8.0.16_@types+node@22.19.19_esbuild@0.28.0_jiti@2.7.0_tsx@4.22.3_/node_modules/vitest/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vitest@4.1.8_@types+node@22.19.19_jsdom@29.1.1_vite@8.0.16_@types+node@22.19.19_esbuild@0.28.0_jiti@2.7.0_tsx@4.22.3_/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules"
|
|
14
14
|
else
|
|
15
|
-
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vitest@4.1.8_@types+node@22.19.19_jsdom@29.1.1_vite@8.0.16_@types+node@22.19.19_esbuild@0.
|
|
15
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vitest@4.1.8_@types+node@22.19.19_jsdom@29.1.1_vite@8.0.16_@types+node@22.19.19_esbuild@0.28.0_jiti@2.7.0_tsx@4.22.3_/node_modules/vitest/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vitest@4.1.8_@types+node@22.19.19_jsdom@29.1.1_vite@8.0.16_@types+node@22.19.19_esbuild@0.28.0_jiti@2.7.0_tsx@4.22.3_/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
16
|
fi
|
|
17
17
|
if [ -x "$basedir/node" ]; then
|
|
18
18
|
exec "$basedir/node" "$basedir/../vitest/vitest.mjs" "$@"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"3.2.4","results":[[":src/machine.test.ts",{"duration":
|
|
1
|
+
{"version":"3.2.4","results":[[":src/machine.test.ts",{"duration":3.2387080000000026,"failed":false}],[":src/theme.test.ts",{"duration":3.8104999999999905,"failed":false}],[":../../apps/demo/src/aeonSchemaCompiler.test.ts",{"duration":7.737041999999974,"failed":false}],[":src/identicon.test.ts",{"duration":5.902916000000005,"failed":false}]]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"3.2.4","results":[[":src/machines/dialog.test.ts",{"duration":5.771292000000017,"failed":false}],[":src/machines/select.test.ts",{"duration":6.394416000000035,"failed":false}],[":src/__tests__/dialog.test.ts",{"duration":3.686000000000007,"failed":false}],[":src/__tests__/select.test.ts",{"duration":2.8540000000000134,"failed":false}],[":src/__tests__/floating-surface.test.ts",{"duration":13.
|
|
1
|
+
{"version":"3.2.4","results":[[":src/machines/dialog.test.ts",{"duration":5.771292000000017,"failed":false}],[":src/machines/select.test.ts",{"duration":6.394416000000035,"failed":false}],[":src/__tests__/dialog.test.ts",{"duration":3.686000000000007,"failed":false}],[":src/__tests__/select.test.ts",{"duration":2.8540000000000134,"failed":false}],[":src/__tests__/floating-surface.test.ts",{"duration":13.199792000000002,"failed":false}],[":src/__tests__/app-nav.test.ts",{"duration":9.69079099999999,"failed":false}],[":src/__tests__/file-upload.test.ts",{"duration":7.872375000000005,"failed":false}],[":src/__tests__/range-slider.test.ts",{"duration":8.245833000000005,"failed":false}]]}
|
|
@@ -194,6 +194,30 @@ declare const RadioGroup: {
|
|
|
194
194
|
ItemLabel: react.ForwardRefExoticComponent<HTMLAttributes<HTMLSpanElement> & react.RefAttributes<HTMLSpanElement>>;
|
|
195
195
|
};
|
|
196
196
|
|
|
197
|
+
/** Bridge executable statecharts to React — UI = f(snapshot). */
|
|
198
|
+
declare function useAeonMachine<M extends AnyStateMachine>(machine: M, options?: Parameters<typeof useMachine<M>>[1]): [xstate.StateFrom<M>, (event: xstate.EventFromLogic<M>) => void, xstate.Actor<M>];
|
|
199
|
+
|
|
200
|
+
interface SliderContextValue {
|
|
201
|
+
value: number;
|
|
202
|
+
min: number;
|
|
203
|
+
max: number;
|
|
204
|
+
step: number;
|
|
205
|
+
disabled: boolean;
|
|
206
|
+
dragging: boolean;
|
|
207
|
+
/** Nested region value from the machine: `interaction.idle` | `interaction.dragging`. */
|
|
208
|
+
interaction: 'idle' | 'dragging';
|
|
209
|
+
send: ReturnType<typeof useAeonMachine<typeof sliderMachine>>[1];
|
|
210
|
+
onValueChange?: (value: number) => void;
|
|
211
|
+
/** Snap, store and report a user-intended value. */
|
|
212
|
+
commit: (value: number) => void;
|
|
213
|
+
setValueFromClientX: (clientX: number) => void;
|
|
214
|
+
trackRef: React.RefObject<HTMLDivElement | null>;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Read the slider's live machine state from inside `Slider.Root`.
|
|
218
|
+
* `interaction` is the nested region value — the same thing the snapshot holds.
|
|
219
|
+
*/
|
|
220
|
+
declare function useSliderContext(): SliderContextValue;
|
|
197
221
|
interface SliderRootProps {
|
|
198
222
|
value?: number;
|
|
199
223
|
defaultValue?: number;
|
|
@@ -237,9 +261,6 @@ declare const RangeSlider: {
|
|
|
237
261
|
ValueText: react.ForwardRefExoticComponent<HTMLAttributes<HTMLSpanElement> & react.RefAttributes<HTMLSpanElement>>;
|
|
238
262
|
};
|
|
239
263
|
|
|
240
|
-
/** Bridge executable statecharts to React — UI = f(snapshot). */
|
|
241
|
-
declare function useAeonMachine<M extends AnyStateMachine>(machine: M, options?: Parameters<typeof useMachine<M>>[1]): [xstate.StateFrom<M>, (event: xstate.EventFromLogic<M>) => void, xstate.Actor<M>];
|
|
242
|
-
|
|
243
264
|
interface FileUploadContextValue {
|
|
244
265
|
phase: 'idle' | 'dragging' | 'rejected';
|
|
245
266
|
accept: string;
|
|
@@ -534,6 +555,10 @@ interface PopoverPositionerProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
534
555
|
/** Mount panel in document.body (default true). */
|
|
535
556
|
portalled?: boolean;
|
|
536
557
|
placement?: AnchorPlacement;
|
|
558
|
+
/** Align to this region rather than the trigger. See `FloatingPositioner`. */
|
|
559
|
+
anchorRef?: React.RefObject<HTMLElement | null>;
|
|
560
|
+
/** Size the panel to the anchor, so it spans the region it belongs to. */
|
|
561
|
+
matchAnchorWidth?: boolean;
|
|
537
562
|
/** Gap between trigger and panel (default 4). */
|
|
538
563
|
gutter?: number;
|
|
539
564
|
/** Stack level for the portalled surface (default 50). */
|
|
@@ -1186,4 +1211,4 @@ declare const Slot: react.ForwardRefExoticComponent<HTMLAttributes<HTMLElement>
|
|
|
1186
1211
|
children?: ReactNode;
|
|
1187
1212
|
} & react.RefAttributes<HTMLElement>>;
|
|
1188
1213
|
|
|
1189
|
-
export { Accordion, type AccordionItemContentProps, type AccordionItemProps, type AccordionItemTriggerProps, type AccordionRootProps, AppNav, type AppNavBreadcrumbProps, type AppNavRootProps, type AppNavSectionProps, AppShell, type AppShellRootProps, Async, AsyncActions, AsyncProvider, AsyncReadout, AsyncRoot, AsyncTrack, Avatar, type AvatarRootProps, type AvatarStatus, Bar, type BarRootProps, Button, type ButtonRootProps, type ButtonStatus, Checkbox, type CheckboxRootProps, Combobox, type ComboboxItemProps, type ComboboxRootProps, Composer, type ComposerRootProps, type ComposerState, Content, type ContentRootProps, Conversation, type ConversationItemProps, type ConversationItemState, type ConversationRootProps, Dialog, type DialogPortalProps, type DialogRootProps, Entry, type EntryListProps, type EntryRootProps, type EntryState, Field, type FieldControlProps, type FieldRootProps, type FieldTextareaProps, FileUpload, type FileUploadRootProps, Identity, type IdentityRootProps, ListRow, type ListRowRootProps, Menu, type MenuItemProps, type MenuPositionerProps, type MenuRootProps, MetricStrip, type MetricStripRootProps, Nav, type NavItemProps, type NavRootProps, Panel, type PanelContentProps, type PanelGroupProps, type PanelLabelProps, type PanelRootProps, type PanelTriggerProps, PinInput, type PinInputInputProps, type PinInputRootProps, Popover, type PopoverPositionerProps, type PopoverRootProps, type PresenceState, ProfileHeader, type ProfileHeaderRootProps, Progress, type ProgressRootProps, Prompt, type PromptRootProps, type PromptStatus, RadioGroup, type RadioGroupItemProps, type RadioGroupRootProps, RangeSlider, type RangeSliderRootProps, type RangeSliderThumbProps, Scroll, type ScrollRootProps, type ScrollViewportProps, Select, type SelectContentProps, type SelectItemProps, type SelectPositionerProps, type SelectRootProps, Separator, type SeparatorRootProps, Slider, type SliderRootProps, Slot, StatusBanner, type StatusBannerRootProps, type StatusBannerTone, StickyBar, type StickyBarRootProps, Switch, type SwitchRootProps, Tabs, type TabsContentProps, type TabsRootProps, type TabsTriggerProps, ThemeSwitcher, type ThemeSwitcherModeBtnProps, type ThemeSwitcherModesProps, type ThemeSwitcherRootProps, type ThemeSwitcherThemeSelectProps, Thread, type ThreadItemFace, type ThreadItemProps, type ThreadMessageState, type ThreadRootProps, Toast, ToastItem, type ToastPlacement, type ToastProviderProps, type ToastPublishOptions, type ToastRecord, type ToastRootProps, type ToastStore, type ToastViewportProps, Tooltip, type TooltipPositionerProps, type TooltipRootProps, asyncStatusToContentState, useAeonMachine, useAppNav, useAppNavMachine, useAppShellContext, useAsyncContext, useContentContext, useFieldContext, useFileUploadCtx as useFileUpload, useFitText, useLayoutMode, useScrollContext, useScrollState, useStickyBarContext, useToast, useToastStore };
|
|
1214
|
+
export { Accordion, type AccordionItemContentProps, type AccordionItemProps, type AccordionItemTriggerProps, type AccordionRootProps, AppNav, type AppNavBreadcrumbProps, type AppNavRootProps, type AppNavSectionProps, AppShell, type AppShellRootProps, Async, AsyncActions, AsyncProvider, AsyncReadout, AsyncRoot, AsyncTrack, Avatar, type AvatarRootProps, type AvatarStatus, Bar, type BarRootProps, Button, type ButtonRootProps, type ButtonStatus, Checkbox, type CheckboxRootProps, Combobox, type ComboboxItemProps, type ComboboxRootProps, Composer, type ComposerRootProps, type ComposerState, Content, type ContentRootProps, Conversation, type ConversationItemProps, type ConversationItemState, type ConversationRootProps, Dialog, type DialogPortalProps, type DialogRootProps, Entry, type EntryListProps, type EntryRootProps, type EntryState, Field, type FieldControlProps, type FieldRootProps, type FieldTextareaProps, FileUpload, type FileUploadRootProps, Identity, type IdentityRootProps, ListRow, type ListRowRootProps, Menu, type MenuItemProps, type MenuPositionerProps, type MenuRootProps, MetricStrip, type MetricStripRootProps, Nav, type NavItemProps, type NavRootProps, Panel, type PanelContentProps, type PanelGroupProps, type PanelLabelProps, type PanelRootProps, type PanelTriggerProps, PinInput, type PinInputInputProps, type PinInputRootProps, Popover, type PopoverPositionerProps, type PopoverRootProps, type PresenceState, ProfileHeader, type ProfileHeaderRootProps, Progress, type ProgressRootProps, Prompt, type PromptRootProps, type PromptStatus, RadioGroup, type RadioGroupItemProps, type RadioGroupRootProps, RangeSlider, type RangeSliderRootProps, type RangeSliderThumbProps, Scroll, type ScrollRootProps, type ScrollViewportProps, Select, type SelectContentProps, type SelectItemProps, type SelectPositionerProps, type SelectRootProps, Separator, type SeparatorRootProps, Slider, type SliderContextValue, type SliderRootProps, Slot, StatusBanner, type StatusBannerRootProps, type StatusBannerTone, StickyBar, type StickyBarRootProps, Switch, type SwitchRootProps, Tabs, type TabsContentProps, type TabsRootProps, type TabsTriggerProps, ThemeSwitcher, type ThemeSwitcherModeBtnProps, type ThemeSwitcherModesProps, type ThemeSwitcherRootProps, type ThemeSwitcherThemeSelectProps, Thread, type ThreadItemFace, type ThreadItemProps, type ThreadMessageState, type ThreadRootProps, Toast, ToastItem, type ToastPlacement, type ToastProviderProps, type ToastPublishOptions, type ToastRecord, type ToastRootProps, type ToastStore, type ToastViewportProps, Tooltip, type TooltipPositionerProps, type TooltipRootProps, asyncStatusToContentState, useAeonMachine, useAppNav, useAppNavMachine, useAppShellContext, useAsyncContext, useContentContext, useFieldContext, useFileUploadCtx as useFileUpload, useFitText, useLayoutMode, useScrollContext, useScrollState, useSliderContext, useStickyBarContext, useToast, useToastStore };
|
|
@@ -519,11 +519,15 @@ var Root4 = forwardRef5(function DialogRoot({ open, defaultOpen = false, onOpenC
|
|
|
519
519
|
const [snapshot, send] = useAeonMachine(dialogMachine, {
|
|
520
520
|
input: { open: defaultOpen }
|
|
521
521
|
});
|
|
522
|
-
const
|
|
522
|
+
const machineOpen = snapshot.context.open;
|
|
523
|
+
const resolvedOpen = open ?? machineOpen;
|
|
523
524
|
const contentRef = useRef(null);
|
|
525
|
+
const reportedOpen = useRef(machineOpen);
|
|
524
526
|
useEffect6(() => {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
+
if (reportedOpen.current === machineOpen) return;
|
|
528
|
+
reportedOpen.current = machineOpen;
|
|
529
|
+
onOpenChange?.(machineOpen);
|
|
530
|
+
}, [machineOpen, onOpenChange]);
|
|
527
531
|
useEffect6(() => {
|
|
528
532
|
if (open !== void 0) {
|
|
529
533
|
send({ type: "SET_OPEN", open });
|
|
@@ -1247,7 +1251,7 @@ import {
|
|
|
1247
1251
|
} from "react";
|
|
1248
1252
|
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1249
1253
|
var SliderCtx = createContext8(null);
|
|
1250
|
-
function
|
|
1254
|
+
function useSliderContext() {
|
|
1251
1255
|
const ctx = useContext8(SliderCtx);
|
|
1252
1256
|
if (!ctx) throw new Error("Slider parts must be used within Slider.Root");
|
|
1253
1257
|
return ctx;
|
|
@@ -1280,12 +1284,24 @@ var Root11 = forwardRef12(function SliderRoot({
|
|
|
1280
1284
|
});
|
|
1281
1285
|
const resolvedValue = value ?? snapshot.context.value;
|
|
1282
1286
|
const dragging = snapshot.matches({ interaction: "dragging" });
|
|
1283
|
-
useEffect9(() => {
|
|
1284
|
-
onValueChange?.(resolvedValue);
|
|
1285
|
-
}, [resolvedValue, onValueChange]);
|
|
1286
1287
|
useEffect9(() => {
|
|
1287
1288
|
if (value !== void 0) send({ type: "SET_VALUE", value });
|
|
1288
1289
|
}, [value, send]);
|
|
1290
|
+
useEffect9(() => {
|
|
1291
|
+
send({ type: "SET_BOUNDS", min, max, step });
|
|
1292
|
+
}, [min, max, step, send]);
|
|
1293
|
+
useEffect9(() => {
|
|
1294
|
+
send({ type: "SET_DISABLED", disabled });
|
|
1295
|
+
}, [disabled, send]);
|
|
1296
|
+
const commit = useCallback2(
|
|
1297
|
+
(next) => {
|
|
1298
|
+
if (disabled) return;
|
|
1299
|
+
const snapped = snapSliderValue(next, min, max, step);
|
|
1300
|
+
send({ type: "SET_VALUE", value: snapped });
|
|
1301
|
+
onValueChange?.(snapped);
|
|
1302
|
+
},
|
|
1303
|
+
[disabled, min, max, step, send, onValueChange]
|
|
1304
|
+
);
|
|
1289
1305
|
const setValueFromClientX = useCallback2(
|
|
1290
1306
|
(clientX) => {
|
|
1291
1307
|
const track = trackRef.current;
|
|
@@ -1293,11 +1309,9 @@ var Root11 = forwardRef12(function SliderRoot({
|
|
|
1293
1309
|
const rect = track.getBoundingClientRect();
|
|
1294
1310
|
if (rect.width <= 0) return;
|
|
1295
1311
|
const ratio = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));
|
|
1296
|
-
|
|
1297
|
-
send({ type: "SET_VALUE", value: next });
|
|
1298
|
-
if (value === void 0) onValueChange?.(next);
|
|
1312
|
+
commit(min + ratio * (max - min));
|
|
1299
1313
|
},
|
|
1300
|
-
[disabled, min, max
|
|
1314
|
+
[commit, disabled, min, max]
|
|
1301
1315
|
);
|
|
1302
1316
|
useEffect9(() => {
|
|
1303
1317
|
if (!dragging) return;
|
|
@@ -1318,12 +1332,25 @@ var Root11 = forwardRef12(function SliderRoot({
|
|
|
1318
1332
|
step,
|
|
1319
1333
|
disabled,
|
|
1320
1334
|
dragging,
|
|
1335
|
+
interaction: dragging ? "dragging" : "idle",
|
|
1321
1336
|
send,
|
|
1322
1337
|
onValueChange,
|
|
1338
|
+
commit,
|
|
1323
1339
|
setValueFromClientX,
|
|
1324
1340
|
trackRef
|
|
1325
1341
|
}),
|
|
1326
|
-
[
|
|
1342
|
+
[
|
|
1343
|
+
resolvedValue,
|
|
1344
|
+
min,
|
|
1345
|
+
max,
|
|
1346
|
+
step,
|
|
1347
|
+
disabled,
|
|
1348
|
+
dragging,
|
|
1349
|
+
send,
|
|
1350
|
+
onValueChange,
|
|
1351
|
+
commit,
|
|
1352
|
+
setValueFromClientX
|
|
1353
|
+
]
|
|
1327
1354
|
);
|
|
1328
1355
|
return /* @__PURE__ */ jsx11(SliderCtx.Provider, { value: ctx, children: /* @__PURE__ */ jsx11(
|
|
1329
1356
|
"div",
|
|
@@ -1342,7 +1369,7 @@ var Root11 = forwardRef12(function SliderRoot({
|
|
|
1342
1369
|
) });
|
|
1343
1370
|
});
|
|
1344
1371
|
var Track2 = forwardRef12(function SliderTrack({ onPointerDown, ...rest }, ref) {
|
|
1345
|
-
const { disabled, send, setValueFromClientX, trackRef } =
|
|
1372
|
+
const { disabled, send, setValueFromClientX, trackRef } = useSliderContext();
|
|
1346
1373
|
return /* @__PURE__ */ jsx11(
|
|
1347
1374
|
"div",
|
|
1348
1375
|
{
|
|
@@ -1368,7 +1395,7 @@ var Track2 = forwardRef12(function SliderTrack({ onPointerDown, ...rest }, ref)
|
|
|
1368
1395
|
);
|
|
1369
1396
|
});
|
|
1370
1397
|
var Range2 = forwardRef12(function SliderRange({ style, ...rest }, ref) {
|
|
1371
|
-
const { value, min, max } =
|
|
1398
|
+
const { value, min, max } = useSliderContext();
|
|
1372
1399
|
return /* @__PURE__ */ jsx11(
|
|
1373
1400
|
"div",
|
|
1374
1401
|
{
|
|
@@ -1380,7 +1407,7 @@ var Range2 = forwardRef12(function SliderRange({ style, ...rest }, ref) {
|
|
|
1380
1407
|
);
|
|
1381
1408
|
});
|
|
1382
1409
|
var Thumb2 = forwardRef12(function SliderThumb({ onKeyDown, onPointerDown, style, ...rest }, ref) {
|
|
1383
|
-
const { value, min, max, step, disabled, dragging, send, setValueFromClientX } =
|
|
1410
|
+
const { value, min, max, step, disabled, dragging, send, commit, setValueFromClientX } = useSliderContext();
|
|
1384
1411
|
return /* @__PURE__ */ jsx11(
|
|
1385
1412
|
"span",
|
|
1386
1413
|
{
|
|
@@ -1408,19 +1435,27 @@ var Thumb2 = forwardRef12(function SliderThumb({ onKeyDown, onPointerDown, style
|
|
|
1408
1435
|
if (disabled || e.defaultPrevented) return;
|
|
1409
1436
|
if (e.key === "ArrowRight" || e.key === "ArrowUp") {
|
|
1410
1437
|
e.preventDefault();
|
|
1411
|
-
|
|
1438
|
+
commit(value + step);
|
|
1412
1439
|
}
|
|
1413
1440
|
if (e.key === "ArrowLeft" || e.key === "ArrowDown") {
|
|
1414
1441
|
e.preventDefault();
|
|
1415
|
-
|
|
1442
|
+
commit(value - step);
|
|
1443
|
+
}
|
|
1444
|
+
if (e.key === "PageUp") {
|
|
1445
|
+
e.preventDefault();
|
|
1446
|
+
commit(value + (max - min) / 10);
|
|
1447
|
+
}
|
|
1448
|
+
if (e.key === "PageDown") {
|
|
1449
|
+
e.preventDefault();
|
|
1450
|
+
commit(value - (max - min) / 10);
|
|
1416
1451
|
}
|
|
1417
1452
|
if (e.key === "Home") {
|
|
1418
1453
|
e.preventDefault();
|
|
1419
|
-
|
|
1454
|
+
commit(min);
|
|
1420
1455
|
}
|
|
1421
1456
|
if (e.key === "End") {
|
|
1422
1457
|
e.preventDefault();
|
|
1423
|
-
|
|
1458
|
+
commit(max);
|
|
1424
1459
|
}
|
|
1425
1460
|
}
|
|
1426
1461
|
},
|
|
@@ -1430,7 +1465,7 @@ var Thumb2 = forwardRef12(function SliderThumb({ onKeyDown, onPointerDown, style
|
|
|
1430
1465
|
);
|
|
1431
1466
|
});
|
|
1432
1467
|
var ValueText = forwardRef12(function SliderValueText(props, ref) {
|
|
1433
|
-
const { value } =
|
|
1468
|
+
const { value } = useSliderContext();
|
|
1434
1469
|
return /* @__PURE__ */ jsx11("span", { ref, ...mergeProps(partOnlyAttrs3(sliderAnatomy.valueText), props), children: props.children ?? value });
|
|
1435
1470
|
});
|
|
1436
1471
|
var Slider = { Root: Root11, Track: Track2, Range: Range2, Thumb: Thumb2, ValueText };
|
|
@@ -2712,6 +2747,7 @@ var FloatingPositioner = forwardRef23(
|
|
|
2712
2747
|
part,
|
|
2713
2748
|
triggerRef,
|
|
2714
2749
|
positionerRef,
|
|
2750
|
+
anchorRef,
|
|
2715
2751
|
portalled = true,
|
|
2716
2752
|
placement = "bottom-start",
|
|
2717
2753
|
matchAnchorWidth = false,
|
|
@@ -2722,7 +2758,7 @@ var FloatingPositioner = forwardRef23(
|
|
|
2722
2758
|
...rest
|
|
2723
2759
|
}, ref) {
|
|
2724
2760
|
const mounted = usePortalledMount();
|
|
2725
|
-
useAnchorPosition(triggerRef, positionerRef, open && portalled, placement, {
|
|
2761
|
+
useAnchorPosition(anchorRef ?? triggerRef, positionerRef, open && portalled, placement, {
|
|
2726
2762
|
matchWidth: matchAnchorWidth,
|
|
2727
2763
|
gutter,
|
|
2728
2764
|
zIndex
|
|
@@ -3401,15 +3437,19 @@ var Root20 = forwardRef26(function PopoverRoot({ open, defaultOpen = false, onOp
|
|
|
3401
3437
|
const [snapshot, send] = useAeonMachine(popoverMachine3, {
|
|
3402
3438
|
input: { open: defaultOpen }
|
|
3403
3439
|
});
|
|
3404
|
-
const
|
|
3440
|
+
const machineOpen = snapshot.context.open;
|
|
3441
|
+
const resolvedOpen = open ?? machineOpen;
|
|
3405
3442
|
const rootRef = useRef9(null);
|
|
3406
3443
|
const triggerRef = useRef9(null);
|
|
3407
3444
|
const positionerRef = useRef9(null);
|
|
3408
3445
|
const triggerId = useId5();
|
|
3409
3446
|
const contentId = useId5();
|
|
3447
|
+
const reportedOpen = useRef9(machineOpen);
|
|
3410
3448
|
useEffect16(() => {
|
|
3411
|
-
|
|
3412
|
-
|
|
3449
|
+
if (reportedOpen.current === machineOpen) return;
|
|
3450
|
+
reportedOpen.current = machineOpen;
|
|
3451
|
+
onOpenChange?.(machineOpen);
|
|
3452
|
+
}, [machineOpen, onOpenChange]);
|
|
3413
3453
|
useEffect16(() => {
|
|
3414
3454
|
if (open !== void 0) send({ type: "SET_OPEN", open });
|
|
3415
3455
|
}, [open, send]);
|
|
@@ -3465,7 +3505,17 @@ var Trigger4 = forwardRef26(function PopoverTrigger({ onClick, ...rest }, ref) {
|
|
|
3465
3505
|
}
|
|
3466
3506
|
);
|
|
3467
3507
|
});
|
|
3468
|
-
var Positioner3 = forwardRef26(function PopoverPositioner({
|
|
3508
|
+
var Positioner3 = forwardRef26(function PopoverPositioner({
|
|
3509
|
+
portalled = true,
|
|
3510
|
+
placement = "bottom-start",
|
|
3511
|
+
anchorRef,
|
|
3512
|
+
matchAnchorWidth = false,
|
|
3513
|
+
gutter,
|
|
3514
|
+
zIndex,
|
|
3515
|
+
children,
|
|
3516
|
+
className,
|
|
3517
|
+
...rest
|
|
3518
|
+
}, ref) {
|
|
3469
3519
|
const { open, triggerRef, positionerRef } = usePopoverCtx();
|
|
3470
3520
|
return /* @__PURE__ */ jsx26(
|
|
3471
3521
|
FloatingPositioner,
|
|
@@ -3476,6 +3526,8 @@ var Positioner3 = forwardRef26(function PopoverPositioner({ portalled = true, pl
|
|
|
3476
3526
|
part: popoverAnatomy.positioner,
|
|
3477
3527
|
triggerRef,
|
|
3478
3528
|
positionerRef,
|
|
3529
|
+
anchorRef,
|
|
3530
|
+
matchAnchorWidth,
|
|
3479
3531
|
portalled,
|
|
3480
3532
|
placement,
|
|
3481
3533
|
gutter,
|
|
@@ -3562,15 +3614,19 @@ function useMenuCtx() {
|
|
|
3562
3614
|
}
|
|
3563
3615
|
var Root21 = forwardRef27(function MenuRoot({ open, defaultOpen = false, onOpenChange, children, className, ...rest }, ref) {
|
|
3564
3616
|
const [snapshot, send] = useAeonMachine(popoverMachine4, { input: { open: defaultOpen } });
|
|
3565
|
-
const
|
|
3617
|
+
const machineOpen = snapshot.context.open;
|
|
3618
|
+
const resolvedOpen = open ?? machineOpen;
|
|
3566
3619
|
const rootRef = useRef10(null);
|
|
3567
3620
|
const triggerRef = useRef10(null);
|
|
3568
3621
|
const positionerRef = useRef10(null);
|
|
3569
3622
|
const triggerId = useId6();
|
|
3570
3623
|
const menuId = useId6();
|
|
3624
|
+
const reportedOpen = useRef10(machineOpen);
|
|
3571
3625
|
useEffect17(() => {
|
|
3572
|
-
|
|
3573
|
-
|
|
3626
|
+
if (reportedOpen.current === machineOpen) return;
|
|
3627
|
+
reportedOpen.current = machineOpen;
|
|
3628
|
+
onOpenChange?.(machineOpen);
|
|
3629
|
+
}, [machineOpen, onOpenChange]);
|
|
3574
3630
|
useEffect17(() => {
|
|
3575
3631
|
if (open !== void 0) send({ type: "SET_OPEN", open });
|
|
3576
3632
|
}, [open, send]);
|
|
@@ -5932,6 +5988,7 @@ export {
|
|
|
5932
5988
|
useLayoutMode,
|
|
5933
5989
|
useScrollContext,
|
|
5934
5990
|
useScrollState,
|
|
5991
|
+
useSliderContext,
|
|
5935
5992
|
useStickyBarContext,
|
|
5936
5993
|
useToast,
|
|
5937
5994
|
useToastStore
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"3.2.4","results":[[":src/select/select.test.tsx",{"duration":
|
|
1
|
+
{"version":"3.2.4","results":[[":src/select/select.test.tsx",{"duration":196.07670899999994,"failed":false}],[":src/dialog/dialog.test.tsx",{"duration":181.65270800000008,"failed":false}],[":src/avatar/avatar.test.tsx",{"duration":117.92833299999984,"failed":false}],[":src/tooltip/tooltip.test.tsx",{"duration":210.20837500000016,"failed":false}],[":src/toast/toast.test.tsx",{"duration":134.57233399999996,"failed":false}]]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react'
|
|
2
2
|
import userEvent from '@testing-library/user-event'
|
|
3
|
+
import { useState } from 'react'
|
|
3
4
|
import { describe, expect, it, vi } from 'vitest'
|
|
4
5
|
import { Dialog } from '../dialog/index.js'
|
|
5
6
|
|
|
@@ -47,4 +48,30 @@ describe('Dialog', () => {
|
|
|
47
48
|
unmount()
|
|
48
49
|
expect(document.body.style.overflow).not.toBe('hidden')
|
|
49
50
|
})
|
|
51
|
+
|
|
52
|
+
it('reports the trigger to a controlled owner', async () => {
|
|
53
|
+
const user = userEvent.setup()
|
|
54
|
+
|
|
55
|
+
function Controlled() {
|
|
56
|
+
const [open, setOpen] = useState(false)
|
|
57
|
+
return (
|
|
58
|
+
<Dialog.Root open={open} onOpenChange={setOpen}>
|
|
59
|
+
<Dialog.Trigger>Open</Dialog.Trigger>
|
|
60
|
+
<Dialog.Portal portalled={false}>
|
|
61
|
+
<Dialog.Content>
|
|
62
|
+
<Dialog.Title>Title</Dialog.Title>
|
|
63
|
+
<Dialog.CloseTrigger>Close</Dialog.CloseTrigger>
|
|
64
|
+
</Dialog.Content>
|
|
65
|
+
</Dialog.Portal>
|
|
66
|
+
</Dialog.Root>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
render(<Controlled />)
|
|
71
|
+
await user.click(screen.getByRole('button', { name: 'Open' }))
|
|
72
|
+
expect(screen.getByRole('dialog')).toBeInTheDocument()
|
|
73
|
+
|
|
74
|
+
await user.click(screen.getByRole('button', { name: 'Close dialog' }))
|
|
75
|
+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
|
|
76
|
+
})
|
|
50
77
|
})
|
|
@@ -52,12 +52,20 @@ const Root = forwardRef<HTMLDivElement, DialogRootProps>(function DialogRoot(
|
|
|
52
52
|
input: { open: defaultOpen },
|
|
53
53
|
})
|
|
54
54
|
|
|
55
|
-
const
|
|
55
|
+
const machineOpen = snapshot.context.open
|
|
56
|
+
const resolvedOpen = open ?? machineOpen
|
|
56
57
|
const contentRef = useRef<HTMLDivElement>(null)
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Report the machine's intent, not the resolved value: a controlled owner
|
|
61
|
+
* holding `open` would otherwise never hear about its own trigger or escape.
|
|
62
|
+
*/
|
|
63
|
+
const reportedOpen = useRef(machineOpen)
|
|
58
64
|
useEffect(() => {
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
if (reportedOpen.current === machineOpen) return
|
|
66
|
+
reportedOpen.current = machineOpen
|
|
67
|
+
onOpenChange?.(machineOpen)
|
|
68
|
+
}, [machineOpen, onOpenChange])
|
|
61
69
|
|
|
62
70
|
useEffect(() => {
|
|
63
71
|
if (open !== undefined) {
|
|
@@ -10,6 +10,15 @@ export interface FloatingPositionerProps extends HTMLAttributes<HTMLDivElement>
|
|
|
10
10
|
part: string
|
|
11
11
|
triggerRef: RefObject<HTMLElement | null>
|
|
12
12
|
positionerRef: RefObject<HTMLDivElement | null>
|
|
13
|
+
/**
|
|
14
|
+
* Position against this element instead of the trigger.
|
|
15
|
+
*
|
|
16
|
+
* A trigger is often a small icon button pinned to one edge of a much larger
|
|
17
|
+
* region. Aligning to the button then throws a wide surface off-screen, and
|
|
18
|
+
* clamping only slides it back to the viewport edge. Anchoring to the region
|
|
19
|
+
* the surface belongs to puts it where the eye already is.
|
|
20
|
+
*/
|
|
21
|
+
anchorRef?: RefObject<HTMLElement | null>
|
|
13
22
|
portalled?: boolean
|
|
14
23
|
placement?: AnchorPlacement
|
|
15
24
|
matchAnchorWidth?: boolean
|
|
@@ -28,6 +37,7 @@ export const FloatingPositioner = forwardRef<HTMLDivElement, FloatingPositionerP
|
|
|
28
37
|
part,
|
|
29
38
|
triggerRef,
|
|
30
39
|
positionerRef,
|
|
40
|
+
anchorRef,
|
|
31
41
|
portalled = true,
|
|
32
42
|
placement = 'bottom-start',
|
|
33
43
|
matchAnchorWidth = false,
|
|
@@ -40,7 +50,7 @@ export const FloatingPositioner = forwardRef<HTMLDivElement, FloatingPositionerP
|
|
|
40
50
|
ref,
|
|
41
51
|
) {
|
|
42
52
|
const mounted = usePortalledMount()
|
|
43
|
-
useAnchorPosition(triggerRef, positionerRef, open && portalled, placement, {
|
|
53
|
+
useAnchorPosition(anchorRef ?? triggerRef, positionerRef, open && portalled, placement, {
|
|
44
54
|
matchWidth: matchAnchorWidth,
|
|
45
55
|
gutter,
|
|
46
56
|
zIndex,
|