dialkit 1.3.0 → 1.4.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.
Files changed (71) hide show
  1. package/README.md +433 -3
  2. package/dist/icons.d.ts +4 -1
  3. package/dist/icons.js +13 -0
  4. package/dist/icons.js.map +1 -1
  5. package/dist/index.cjs +3118 -480
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +274 -6
  8. package/dist/index.d.ts +274 -6
  9. package/dist/index.js +3050 -417
  10. package/dist/index.js.map +1 -1
  11. package/dist/solid/index.d.ts +225 -3
  12. package/dist/solid/index.js +5697 -2508
  13. package/dist/solid/index.js.map +1 -1
  14. package/dist/store/index.cjs +57 -16
  15. package/dist/store/index.cjs.map +1 -1
  16. package/dist/store/index.d.cts +14 -2
  17. package/dist/store/index.d.ts +14 -2
  18. package/dist/store/index.js +52 -16
  19. package/dist/store/index.js.map +1 -1
  20. package/dist/styles.css +704 -0
  21. package/dist/svelte/components/ControlRenderer.svelte +5 -2
  22. package/dist/svelte/components/ControlRenderer.svelte.d.ts +2 -0
  23. package/dist/svelte/components/ControlRenderer.svelte.d.ts.map +1 -1
  24. package/dist/svelte/components/DialRoot.svelte +43 -6
  25. package/dist/svelte/components/DialRoot.svelte.d.ts.map +1 -1
  26. package/dist/svelte/components/Panel.svelte +7 -1
  27. package/dist/svelte/components/Panel.svelte.d.ts +2 -0
  28. package/dist/svelte/components/Panel.svelte.d.ts.map +1 -1
  29. package/dist/svelte/components/Timeline/ClipPopover.svelte +206 -0
  30. package/dist/svelte/components/Timeline/ClipPopover.svelte.d.ts +26 -0
  31. package/dist/svelte/components/Timeline/ClipPopover.svelte.d.ts.map +1 -0
  32. package/dist/svelte/components/Timeline/DialTimeline.svelte +76 -0
  33. package/dist/svelte/components/Timeline/DialTimeline.svelte.d.ts +13 -0
  34. package/dist/svelte/components/Timeline/DialTimeline.svelte.d.ts.map +1 -0
  35. package/dist/svelte/components/Timeline/TimelineClip.svelte +233 -0
  36. package/dist/svelte/components/Timeline/TimelineClip.svelte.d.ts +24 -0
  37. package/dist/svelte/components/Timeline/TimelineClip.svelte.d.ts.map +1 -0
  38. package/dist/svelte/components/Timeline/TimelineSection.svelte +756 -0
  39. package/dist/svelte/components/Timeline/TimelineSection.svelte.d.ts +12 -0
  40. package/dist/svelte/components/Timeline/TimelineSection.svelte.d.ts.map +1 -0
  41. package/dist/svelte/components/Timeline/TimelineToggleButton.svelte +25 -0
  42. package/dist/svelte/components/Timeline/TimelineToggleButton.svelte.d.ts +4 -0
  43. package/dist/svelte/components/Timeline/TimelineToggleButton.svelte.d.ts.map +1 -0
  44. package/dist/svelte/components/TransitionControl.svelte +26 -11
  45. package/dist/svelte/components/TransitionControl.svelte.d.ts +9 -0
  46. package/dist/svelte/components/TransitionControl.svelte.d.ts.map +1 -1
  47. package/dist/svelte/createDialTimeline.svelte.d.ts +4 -0
  48. package/dist/svelte/createDialTimeline.svelte.d.ts.map +1 -0
  49. package/dist/svelte/createDialTimeline.svelte.js +73 -0
  50. package/dist/svelte/index.d.ts +4 -0
  51. package/dist/svelte/index.d.ts.map +1 -1
  52. package/dist/svelte/index.js +3 -0
  53. package/dist/svelte/theme-css.d.ts +1 -1
  54. package/dist/svelte/theme-css.d.ts.map +1 -1
  55. package/dist/svelte/theme-css.js +704 -0
  56. package/dist/timeline/index.cjs +1288 -0
  57. package/dist/timeline/index.cjs.map +1 -0
  58. package/dist/timeline/index.d.cts +443 -0
  59. package/dist/timeline/index.d.ts +443 -0
  60. package/dist/timeline/index.js +1233 -0
  61. package/dist/timeline/index.js.map +1 -0
  62. package/dist/vue/index.d.ts +273 -7
  63. package/dist/vue/index.js +2867 -361
  64. package/dist/vue/index.js.map +1 -1
  65. package/package.json +23 -13
  66. package/dist/solid/index.cjs +0 -3536
  67. package/dist/solid/index.cjs.map +0 -1
  68. package/dist/solid/index.d.cts +0 -295
  69. package/dist/vue/index.cjs +0 -3497
  70. package/dist/vue/index.cjs.map +0 -1
  71. package/dist/vue/index.d.cts +0 -722
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # dialkit v1.2.0
1
+ # dialkit v1.4.0
2
2
 
3
3
  <img src="https://joshpuckett.me/images/dialkit.png" width="100%" />
4
4
 
5
- Real-time parameter tweaking for React, Solid, Svelte, and Vue, created by Josh Puckett.
5
+ Real-time parameter tweaking for React, Solid, Svelte, and Vue, created by Josh Puckett. Now with a scrubbable animation [Timeline](#timeline).
6
6
 
7
7
  To learn more about how I use DialKit, and approach design in general, feel free to check out [Interface Craft](http://interfacecraft.dev/).
8
8
 
@@ -386,6 +386,7 @@ const values = useDialKit('Controls', {
386
386
  | `position` | `'top-right' \| 'top-left' \| 'bottom-right' \| 'bottom-left'` | `'top-right'` |
387
387
  | `defaultOpen` | `boolean` | `true` |
388
388
  | `mode` | `'popover' \| 'inline'` | `'popover'` |
389
+ | `theme` | `'system' \| 'light' \| 'dark'` | `'system'` |
389
390
  | `productionEnabled` | `boolean` | `false` in production, `true` otherwise |
390
391
  | `onOpenChange` | `(open: boolean) => void` | `undefined` |
391
392
 
@@ -621,6 +622,350 @@ function PhotoStack() {
621
622
 
622
623
  ---
623
624
 
625
+ ## Timeline
626
+
627
+ DialKit Timeline lets you define an animation in code, preview it, and tune its timing, values, and curves in a visual timeline.
628
+
629
+ The animation's structure stays in code. The timeline editor adjusts its properties without changing which clips loop, which clips form sequences, or how your application combines them.
630
+
631
+ Timeline is available in React, Solid, Svelte, and Vue. Every adapter uses the same framework-neutral timeline core and store, while lifecycle and rendering stay native to its framework.
632
+
633
+ ```tsx
634
+ import { useDialTimeline, DialTimeline } from 'dialkit';
635
+ import 'dialkit/styles.css';
636
+
637
+ function Hero() {
638
+ const hero = useDialTimeline(
639
+ 'Hero',
640
+ {
641
+ entrance: {
642
+ at: 0,
643
+ duration: 0.6,
644
+ from: { y: 32, opacity: 0 },
645
+ to: { y: 0, opacity: 1 },
646
+ transition: { type: 'spring', bounce: 0.2 },
647
+ },
648
+ idle: {
649
+ at: 0.8,
650
+ loop: true,
651
+ from: { y: 0 },
652
+ steps: [
653
+ { duration: 1, to: { y: -6 } },
654
+ { duration: 1, to: { y: 0 } },
655
+ ],
656
+ },
657
+ },
658
+ { loop: { from: 0.8 } }
659
+ );
660
+
661
+ const entrance = hero.entrance.current;
662
+ const idle = hero.idle.current;
663
+
664
+ return (
665
+ <>
666
+ <h1 style={{
667
+ opacity: entrance.opacity,
668
+ transform: `translateY(${entrance.y + idle.y}px)`,
669
+ }}>
670
+ Ship the moment.
671
+ </h1>
672
+ <DialTimeline />
673
+ </>
674
+ );
675
+ }
676
+ ```
677
+
678
+ Each named entry is a clip and appears as one row in the timeline. During authoring, bind its `current` values to your UI so the element always matches the playhead, whether the timeline is playing, paused, or being scrubbed. `current` is DialKit's deterministic preview of the configured curve; after tuning, Copy the settings into your app's real animation and remove the timeline hook.
679
+
680
+ The framework entry points expose the same config, values, transport, and `<DialTimeline />` dock:
681
+
682
+ | Framework | Import | Timeline function | Read returned values |
683
+ |-----------|--------|-------------------|----------------------|
684
+ | React | `dialkit` | `useDialTimeline` | `timeline.card.current` |
685
+ | Solid | `dialkit/solid` | `createDialTimeline` | `timeline().card.current` |
686
+ | Svelte 5 | `dialkit/svelte` | `createDialTimeline` | `timeline.card.current` |
687
+ | Vue 3 | `dialkit/vue` | `useDialTimeline` | `timeline.value.card.current` in script; auto-unwrapped in templates |
688
+
689
+ ### Timeline function
690
+
691
+ ```tsx
692
+ const tl = useDialTimeline(name, config, options?)
693
+ // Solid/Svelte: createDialTimeline(name, config, options?)
694
+ ```
695
+
696
+ | Param | Type | Description |
697
+ |-------|------|-------------|
698
+ | `name` | `string` | Timeline title displayed in the dock |
699
+ | `config` | `TimelineConfig` | Clip definitions plus an optional top-level `duration` |
700
+ | `options.id` | `string` | Stable logical id, same semantics as `useDialKit` |
701
+ | `options.persist` | `DialKitPersistOptions` | Persist timing edits to browser storage |
702
+ | `options.autoplay` | `boolean` | Start playing on mount. Default `true` |
703
+ | `options.loop` | `boolean \| { from: number }` | Wrap the playhead when it reaches the end (see [Looping](#looping)) |
704
+
705
+ Clip timing lives in the same store as panel values, so presets, persistence, reset, and Copy all work on timing data with no extra wiring.
706
+
707
+ The returned object combines the transport with one entry per clip:
708
+
709
+ ```tsx
710
+ tl.time // playhead in seconds
711
+ tl.playing // boolean
712
+ tl.duration // timeline length in seconds
713
+ tl.play() // resume (restarts if parked at the end)
714
+ tl.pause()
715
+ tl.replay() // seek to 0 and play
716
+ tl.seek(1.2) // move the playhead (pins the deterministic first-pass state)
717
+
718
+ tl.headline // TimelineClipValues for the "headline" clip
719
+ ```
720
+
721
+ `time`, `playing`, `duration`, `play`, `pause`, `replay`, and `seek` are reserved — a clip with one of those names is skipped with a console warning.
722
+
723
+ ### Clips
724
+
725
+ - Use `from` and `to` for one animation from one state to another.
726
+ - Use `steps` for several states played sequentially.
727
+ - Use `props` when properties need independent timing.
728
+ - Use separate named clips for separate behaviors, even when they affect the same element.
729
+
730
+ DialKit does not inspect your rendered elements or automatically combine animations. Your application decides how the values from different clips are used.
731
+
732
+ Every clip requires `at`, which sets its start time:
733
+
734
+ ```tsx
735
+ card: {
736
+ at: 0.45, // start time in seconds
737
+ duration: 0.7, // bar length in seconds
738
+ from: { y: 44, scale: 0.95, opacity: 0 }, // any DialKit leaf values
739
+ to: { y: 0, scale: 1, opacity: 1 },
740
+ transition: { type: 'spring', bounce: 0.25 },
741
+ }
742
+ ```
743
+
744
+ - **`from` / `to`** accept any normal DialKit leaf values — numbers, hex colors — and become editable controls in the clip's popover. Bare numbers get property-aware slider ranges (`x`/`y` ±100, `rotate` ±180, `scale` 0–2, `opacity` 0–1, and so on), expanded to include your actual endpoints.
745
+ - **`transition`** is a spring or easing config, exactly as in the panel's spring editor. Clips with `from`/`to` and no `transition` animate with a default spring (`{ type: 'spring', bounce: 0.2 }`).
746
+ - **The bar owns the duration.** Time-based springs and easings stretch to the bar: resize the clip and the curve retimes. Physics springs (`stiffness`/`damping`/`mass`) work the other way — the duration is *derived* from their settle time, the bar shows `~0.62s`, and it can't be resized (change the physics instead).
747
+ - **`duration` may be omitted** — it defaults to the easing's duration or the spring's settle time; a `from`/`to` clip with no `transition` gets the default spring's settle time.
748
+
749
+ Config mistakes warn in the console instead of failing silently: an entry missing `at`, conflicting clip shapes (`steps` + `to`, `props` + `from`), or a sequence property with no starting value.
750
+
751
+ A clip with only `at` (and optionally `duration`) is a **marker**: it carries timing state (`started`, `active`, `progress`) but no values. Useful for driving custom effects off `progress`:
752
+
753
+ ```tsx
754
+ shine: { at: 2.7, duration: 0.7 },
755
+
756
+ // later
757
+ <div style={{ transform: `translateX(${-150 + tl.shine.progress * 400}%)` }} />
758
+ ```
759
+
760
+ ### Reading clip state
761
+
762
+ Each clip on the returned object is a `TimelineClipValues`:
763
+
764
+ | Field | Type | Description |
765
+ |-------|------|-------------|
766
+ | `at` | `number` | Clip start in seconds — live, reflects dock edits |
767
+ | `duration` | `number` | Effective duration — the bar length |
768
+ | `loop` | `'off' \| 'repeat'` | Effective loop mode |
769
+ | `started` | `boolean` | Playhead is at or past the clip start |
770
+ | `active` | `boolean` | Playhead is inside the clip (any cycle, for looping clips) |
771
+ | `done` | `boolean` | Playhead is past the clip end (past the timeline end, for looping clips) |
772
+ | `progress` | `number` | 0–1 position within the clip — a sawtooth per cycle for looping clips |
773
+ | `step` | `number` | Index of the leg under the playhead (sequence clips only) |
774
+ | `from` / `to` | `object` | Resolved endpoint values (`to` is the final merged state for sequences) |
775
+ | `animate` | `object` | `to` once the clip has started, `from` before |
776
+ | `transition` | `TransitionConfig` | Motion-ready curve, duration driven by the bar (single-curve clips only) |
777
+ | `css` | `TimelineClipCss` | `transitionDuration` + `transitionTimingFunction` (single-curve clips only) |
778
+ | `current` | `object` | Values interpolated through the clip's curves at the playhead |
779
+
780
+ ### Recommended workflow: preview, copy, replace
781
+
782
+ The intended workflow is:
783
+
784
+ 1. **Author with `current`.** Bind `clip.current` directly to the element while tuning. DialKit deterministically samples the configured spring or easing curve, which makes every intermediate state scrubbable.
785
+ 2. **Copy the tuned settings.** Copy exports the timings, values, and transition parameters you arrived at in the dock.
786
+ 3. **Move those settings into the real app animation.** Apply the copied transition to your normal Motion animation (or your production animation system), then remove `useDialTimeline`/`createDialTimeline` and `<DialTimeline />`. Motion now runs its real spring at runtime.
787
+
788
+ Hiding or removing only `<DialTimeline />` hides the editor UI; it does **not** change how the animation is rendered. As long as the component is reading `clip.current`, DialKit's sampled values are still driving it.
789
+
790
+ The sampler uses the damped-spring equation with the configured stiffness, damping, and mass, plus a Motion-compatible mapping for `visualDuration` and `bounce`. It is designed to closely preview the final motion, but it is not a guarantee of frame-for-frame identity with Motion's runtime implementation.
791
+
792
+ There are three ways to bind a clip while authoring:
793
+
794
+ **1. `current` — recommended for tuning.** Bind styles directly; the element sits at DialKit's sampled state at all times, giving you true scrubbing. DialKit remains the renderer until you replace this binding with your production animation.
795
+
796
+ ```tsx
797
+ <div style={{
798
+ opacity: tl.card.current.opacity,
799
+ transform: `translateY(${tl.card.current.y}px) scale(${tl.card.current.scale})`,
800
+ }} />
801
+ ```
802
+
803
+ **2. `animate` + `transition` — real Motion during authoring.** The clip start flips `animate` from `from` to `to`, so Motion runs the actual curve. The tradeoff is that timeline scrubbing snaps between endpoints instead of showing intermediate states.
804
+
805
+ ```tsx
806
+ <motion.div animate={tl.card.animate} transition={tl.card.transition} />
807
+ ```
808
+
809
+ **3. `animate` + `css` — native CSS during authoring.** Same endpoint flip, using a CSS transition. Easing curves map exactly; springs are approximated with an overshoot bezier, and scrubbing still snaps between endpoints.
810
+
811
+ ```tsx
812
+ <div style={{
813
+ opacity: tl.card.animate.opacity,
814
+ transform: `translateY(${tl.card.animate.y}px)`,
815
+ transitionProperty: 'transform, opacity',
816
+ ...tl.card.css,
817
+ }} />
818
+ ```
819
+
820
+ ### Sequences — `steps`
821
+
822
+ A clip can explicitly chain several sequential legs with a `steps` array. Each array item becomes one segment on the same row; drag a boundary to retime a leg, or click a segment to edit its target values and curve (the first leg's popover also shows the clip's `from`). DialKit does not infer steps from the DOM element consuming the values, from the number of animated properties, or from multiple clips affecting the same element.
823
+
824
+ ```tsx
825
+ path: {
826
+ at: 0,
827
+ from: { x: -70, y: 0 },
828
+ transition: { type: 'easing', duration: 0.8, ease: [0.65, 0, 0.35, 1] },
829
+ steps: [
830
+ { duration: 0.8, to: { x: 0, y: 36 } },
831
+ { duration: 0.8, to: { x: 70, y: 0 } },
832
+ { duration: 0.8, to: { x: -70 } }, // y untouched — holds at 0
833
+ ],
834
+ },
835
+ ```
836
+
837
+ - **The hold rule:** each leg animates only the properties named in its `to`; everything else holds its value from the previous leg. In the example, leg 3 moves `x` while `y` stays where leg 2 left it.
838
+ - Use separate named clips when an element has separate behaviors, such as a one-shot entrance plus an idle animation. They remain separate rows even when the element combines both outputs.
839
+ - **Declare every animated property in `from`.** The sequence's `current` merges all legs from that starting state — a property with no initial value has nothing to hold or interpolate from before its first leg.
840
+ - Each leg takes its own `transition`; legs without one inherit the clip's `transition` (or the default spring).
841
+ - The clip's duration is the sum of its legs — there's no separate duration to edit.
842
+ - `tl.path.step` reports the index of the leg under the playhead.
843
+
844
+ ### Property tracks — `props`
845
+
846
+ When one element's properties need *independent* timing — different durations, different curves, offset phases — give each property a full track:
847
+
848
+ ```tsx
849
+ float: {
850
+ at: 0,
851
+ loop: true,
852
+ props: {
853
+ y: {
854
+ from: -9,
855
+ transition: { type: 'easing', duration: 0.6, ease: [0.45, 0, 0.55, 1] },
856
+ steps: [
857
+ { duration: 0.6, to: 9 },
858
+ { duration: 0.6, to: -9 },
859
+ ],
860
+ },
861
+ scale: {
862
+ from: 0.94,
863
+ delay: 0.12, // starts 120ms behind y — a phase offset
864
+ transition: { type: 'easing', duration: 0.6, ease: [0.8, 0, 0.2, 1] },
865
+ steps: [
866
+ { duration: 0.6, to: 1.06 },
867
+ { duration: 0.6, to: 0.94 },
868
+ ],
869
+ },
870
+ },
871
+ },
872
+ ```
873
+
874
+ Each track is a mini-clip: its own `from`/`to` or `steps`, `duration`, `transition`, and a `delay` offset from the clip's `at`. In the dock, the clip's row is a read-only composite bar — click it to expand one full row per track, where everything is editable and dragging a track's bar adjusts its phase. When the clip loops, each track folds against its own cycle length, so tracks with different periods drift in and out of phase like real oscillators.
875
+
876
+ `props` is mutually exclusive with `from`/`to`/`steps` on the same clip. Track names that collide with clip fields (`at`, `duration`, `loop`, `from`, `to`, `transition`, `delay`, `stepN`) are skipped with a warning. Properties that share timing belong in a plain `from`/`to` or `steps` clip instead.
877
+
878
+ ### Layers — nested groups
879
+
880
+ Nesting clips one level under a key groups them into a collapsible layer in the dock. Purely presentational — values nest the same way:
881
+
882
+ ```tsx
883
+ const tl = useDialTimeline('Compound', {
884
+ circle: {
885
+ path: { at: 0, /* ... */ },
886
+ float: { at: 0, /* ... */ },
887
+ },
888
+ });
889
+
890
+ tl.circle.path.current
891
+ tl.circle.float.current
892
+ ```
893
+
894
+ ### Looping
895
+
896
+ There are two loop controls, and they compose:
897
+
898
+ **Clip loop** — `loop: true` on a clip repeats its cycle from `at` until the timeline ends. The bar is one cycle, so dragging it longer slows the loop. Looping is code-defined rather than editable in the dock. There is no mirror mode — a loop that should return home (a bob, a pulse) is a sequence whose last leg lands back on the starting values, which also puts the loop seam at a natural zero-velocity point.
899
+
900
+ **Timeline loop** — the `loop` option wraps the *playhead*:
901
+
902
+ ```tsx
903
+ useDialTimeline('Hero', config, { loop: true }); // wrap to 0
904
+ useDialTimeline('Hero', config, { loop: { from: 1.4 } }); // wrap to 1.4s
905
+ ```
906
+
907
+ `{ from }` is the intro-then-idle pattern: clips before that time play exactly once, and looping clips inside the region keep cycling with continuous phase — no snap at the wrap. Scrubbing always pins the deterministic first-pass state.
908
+
909
+ **Event-driven timelines** — pass `autoplay: false` and drive the transport from your app. The dock and your code share the same clock: click your real button, watch the playhead run, scrub back, tune, click again.
910
+
911
+ ```tsx
912
+ const toast = useDialTimeline('Toast', config, { autoplay: false });
913
+
914
+ <button onClick={() => toast.replay()}>Save changes</button>
915
+ ```
916
+
917
+ ### Timeline duration
918
+
919
+ The top-level `duration` is the minimum editing window. Omit it and DialKit initially infers an exact fit to the last clip's end. If a live edit—such as switching to a longer physics spring—would move content past that boundary, DialKit extends the timeline automatically. Set `duration` explicitly when you want deliberate slack; authored content is never clipped to fit it.
920
+
921
+ ```tsx
922
+ const tl = useDialTimeline('Hero', {
923
+ duration: 4, // seconds; minimum window, inferred when omitted
924
+ headline: { at: 0.45, /* ... */ },
925
+ });
926
+ ```
927
+
928
+ ### The dock — `<DialTimeline />`
929
+
930
+ Mount once, anywhere. The dock renders fixed to the bottom of the screen via a portal and shows every registered timeline as a section, mirroring how `<DialRoot />` collects panels. It renders nothing until a timeline registers, and like `DialRoot` it's hidden in production builds.
931
+
932
+ ```tsx
933
+ <DialTimeline theme="dark" />
934
+ ```
935
+
936
+ | Prop | Type | Default |
937
+ |------|------|---------|
938
+ | `theme` | `'system' \| 'light' \| 'dark'` | `'system'` |
939
+ | `defaultVisible` | `boolean` | `true` |
940
+ | `visible` | `boolean` | uncontrolled |
941
+ | `onVisibilityChange` | `(visible: boolean) => void` | `undefined` |
942
+ | `defaultOpen` | `boolean` | `true` |
943
+ | `productionEnabled` | `boolean` | `false` in production, `true` otherwise |
944
+
945
+ Each section's toolbar has **Play/Pause**, **Add Version**, a version selector, **Copy**, and a collapse chevron. The collapsed toolbar stays playable and keeps its full-range overview scrubber, so you can inspect the animation without opening the detailed tracks.
946
+
947
+ When `DialRoot` and `DialTimeline` are mounted together, the panel header gets a timeline icon that hides or shows the entire dock. Visibility never changes playback, and showing the dock restores each section's previous collapsed or expanded state. Use `visible` and `onVisibilityChange` when the host app needs to control or persist this state.
948
+
949
+ In the grid:
950
+
951
+ - **Scrub the full timeline** by dragging the compact toolbar overview — playback pauses while you drag and resumes on release.
952
+ - **Zoom the timescale** by holding Option/Alt while dragging left or right on the expanded seconds ruler. The gesture zooms around the point where it began. Hold Shift and drag to reset to the full view; dragging without a modifier seeks directly.
953
+ - **Drag a clip** to move it in time; **drag its edges** to resize (the transition retimes with it).
954
+ - **Drag a segment boundary** on a sequence to resize the leg on its left; the overall clip grows or shrinks with it.
955
+ - **Click a clip** to open its popover — the same DialKit controls (including values, color pickers, and the spring/easing curve editor) scoped to that clip's values. Time-based transitions expose their duration there, and track popovers expose track delay. Click a segment to edit one leg.
956
+ - **Click a props clip's composite bar** to expand its tracks into full editable rows; drag a track's bar to phase-shift it.
957
+ - Physics-spring clips show `~` before their derived duration and resize via their popover physics instead of their edges.
958
+
959
+ Clip names are labels only. Editors open from clips and segments in the timeline.
960
+
961
+ **Copy** exports the current timing values as an agent-ready instruction — tuned `at`s, durations, curves, and values, normalized so defaults and editor-only fields don't add noise. Physics springs include their effective settled duration. The instruction tells the agent to keep `clip.current` during authoring and add a `TODO(production)` comment beside the hook. That breadcrumb explains that the sampled bindings must eventually be replaced with real Motion animations before `useDialTimeline` and `<DialTimeline />` are removed; hiding the dock alone leaves `clip.current` rendering in place.
962
+
963
+ `DialRoot` and `DialTimeline` are independent: use both together (panels for styling, timeline for timing), or the dock alone. Timeline-backed panels don't appear in `DialRoot`.
964
+
965
+ `formatClock(seconds, tenths?)` is exported for rendering `mm:ss` / `mm:ss.t` readouts in your own UI.
966
+
967
+ ---
968
+
624
969
  ## Solid
625
970
 
626
971
  DialKit also works with Solid. Import from `dialkit/solid` instead of `dialkit` — the API mirrors the React version, with `createDialKit` replacing `useDialKit` and `DialRoot` as a Solid component.
@@ -671,6 +1016,28 @@ function Card() {
671
1016
 
672
1017
  `createDialKit` returns an accessor — call `params()` to read the current values. All control types, config shapes, and panel features (presets, copy, folders, and `DialRoot` props like `onOpenChange`) work identically to the React version.
673
1018
 
1019
+ Solid timelines use the same accessor shape:
1020
+
1021
+ ```tsx
1022
+ import { createDialTimeline, DialTimeline } from 'dialkit/solid';
1023
+
1024
+ function Hero() {
1025
+ const timeline = createDialTimeline('Hero', {
1026
+ entrance: {
1027
+ at: 0,
1028
+ duration: 0.6,
1029
+ from: { y: 32, opacity: 0 },
1030
+ to: { y: 0, opacity: 1 },
1031
+ },
1032
+ });
1033
+
1034
+ return <>
1035
+ <h1 style={{ opacity: timeline().entrance.current.opacity }}>Ship it.</h1>
1036
+ <DialTimeline />
1037
+ </>;
1038
+ }
1039
+ ```
1040
+
674
1041
  Use `createDialKitController` when Solid code needs to update values:
675
1042
 
676
1043
  ```tsx
@@ -727,6 +1094,26 @@ npm install dialkit
727
1094
 
728
1095
  `createDialKit` returns a reactive object — access values directly (e.g. `params.blur`). Styles are injected automatically by `DialRoot` (no CSS import needed). Cleanup is automatic when the component unmounts. All control types, presets, folders, transitions, and `DialRoot` props like `onOpenChange` match the React/Solid entries.
729
1096
 
1097
+ Svelte timelines are reactive objects too:
1098
+
1099
+ ```svelte
1100
+ <script>
1101
+ import { createDialTimeline, DialTimeline } from 'dialkit/svelte';
1102
+
1103
+ const timeline = createDialTimeline('Hero', {
1104
+ entrance: {
1105
+ at: 0,
1106
+ duration: 0.6,
1107
+ from: { y: 32, opacity: 0 },
1108
+ to: { y: 0, opacity: 1 }
1109
+ }
1110
+ });
1111
+ </script>
1112
+
1113
+ <h1 style:opacity={timeline.entrance.current.opacity}>Ship it.</h1>
1114
+ <DialTimeline />
1115
+ ```
1116
+
730
1117
  Use `createDialKitController` when Svelte code needs to update values:
731
1118
 
732
1119
  ```svelte
@@ -811,6 +1198,28 @@ const params = useDialKit('Card', {
811
1198
 
812
1199
  `useDialKit` returns a reactive object. All control types, presets, folders, keyboard shortcuts, and transitions work identically to the other frameworks.
813
1200
 
1201
+ Vue timelines return a computed ref, which templates unwrap automatically:
1202
+
1203
+ ```vue
1204
+ <script setup>
1205
+ import { useDialTimeline, DialTimeline } from 'dialkit/vue';
1206
+
1207
+ const timeline = useDialTimeline('Hero', {
1208
+ entrance: {
1209
+ at: 0,
1210
+ duration: 0.6,
1211
+ from: { y: 32, opacity: 0 },
1212
+ to: { y: 0, opacity: 1 },
1213
+ },
1214
+ });
1215
+ </script>
1216
+
1217
+ <template>
1218
+ <h1 :style="{ opacity: timeline.entrance.current.opacity }">Ship it.</h1>
1219
+ <DialTimeline />
1220
+ </template>
1221
+ ```
1222
+
814
1223
  Use `useDialKitController` when Vue code needs to update values:
815
1224
 
816
1225
  ```vue
@@ -858,7 +1267,28 @@ import type {
858
1267
  } from 'dialkit';
859
1268
  ```
860
1269
 
861
- Return values are fully typed: `params.blur` infers as `number`, `params.color` as `string`, `params.spring` as `SpringConfig`, `params.shadow` as a nested object, etc.
1270
+ Timeline types are exported as well:
1271
+
1272
+ ```tsx
1273
+ import type {
1274
+ TimelineConfig,
1275
+ TimelineClipConfig,
1276
+ TimelineClipValues,
1277
+ TimelineClipCss,
1278
+ TimelineClipLoop,
1279
+ TimelineGroupConfig,
1280
+ TimelineGroupValues,
1281
+ TimelinePropConfig,
1282
+ TimelinePropStepConfig,
1283
+ TimelineStepConfig,
1284
+ TimelineStepValues,
1285
+ DialTimelineValues,
1286
+ UseDialTimelineOptions,
1287
+ DialTimelineProps,
1288
+ } from 'dialkit';
1289
+ ```
1290
+
1291
+ Return values are fully typed: `params.blur` infers as `number`, `params.color` as `string`, `params.spring` as `SpringConfig`, `params.shadow` as a nested object, etc. Timeline values are typed from the config shape too — `tl.card.current.y` infers as `number`, and `step` only exists on sequence clips.
862
1292
 
863
1293
  ---
864
1294
 
package/dist/icons.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  declare const ICON_CHEVRON = "M6 9.5L12 15.5L18 9.5";
2
2
  declare const ICON_CHECK = "M5 12.75L10 19L19 5";
3
+ declare const ICON_PAUSE: string[];
4
+ declare const ICON_PLAY = "M9.24394 2.36758C7.41419 1.18362 5 2.49701 5 4.67639V19.3238C5 21.5032 7.41419 22.8166 9.24394 21.6326L20.5624 14.3089C22.2371 13.2253 22.2372 10.775 20.5624 9.69129L9.24394 2.36758Z";
5
+ declare const ICON_TIMELINE: string[];
3
6
  declare const ICON_CLIPBOARD: {
4
7
  board: string;
5
8
  sparkle: string;
@@ -16,4 +19,4 @@ declare const ICON_PANEL: {
16
19
  }[];
17
20
  };
18
21
 
19
- export { ICON_ADD_PRESET, ICON_CHECK, ICON_CHEVRON, ICON_CLIPBOARD, ICON_PANEL, ICON_TRASH };
22
+ export { ICON_ADD_PRESET, ICON_CHECK, ICON_CHEVRON, ICON_CLIPBOARD, ICON_PANEL, ICON_PAUSE, ICON_PLAY, ICON_TIMELINE, ICON_TRASH };
package/dist/icons.js CHANGED
@@ -1,6 +1,16 @@
1
1
  // src/icons.ts
2
2
  var ICON_CHEVRON = "M6 9.5L12 15.5L18 9.5";
3
3
  var ICON_CHECK = "M5 12.75L10 19L19 5";
4
+ var ICON_PAUSE = [
5
+ "M6.75 3C5.23122 3 4 4.23122 4 5.75V18.25C4 19.7688 5.23122 21 6.75 21H7.25C8.76878 21 10 19.7688 10 18.25V5.75C10 4.23122 8.76878 3 7.25 3H6.75Z",
6
+ "M16.75 3C15.2312 3 14 4.23122 14 5.75V18.25C14 19.7688 15.2312 21 16.75 21H17.25C18.7688 21 20 19.7688 20 18.25V5.75C20 4.23122 18.7688 3 17.25 3H16.75Z"
7
+ ];
8
+ var ICON_PLAY = "M9.24394 2.36758C7.41419 1.18362 5 2.49701 5 4.67639V19.3238C5 21.5032 7.41419 22.8166 9.24394 21.6326L20.5624 14.3089C22.2371 13.2253 22.2372 10.775 20.5624 9.69129L9.24394 2.36758Z";
9
+ var ICON_TIMELINE = [
10
+ "M18.868 10C20.8517 10.0003 22.2886 11.8914 21.7577 13.8027L20.369 18.8027C20.0083 20.1012 18.826 20.9999 17.4784 21H6.51941C5.17179 21 3.98948 20.1012 3.62878 18.8027L2.24011 13.8027C1.7092 11.8913 3.14603 10.0003 5.12976 10H18.868Z",
11
+ "M18.9989 6.5C19.5511 6.50007 19.9989 6.94776 19.9989 7.5C19.9989 8.05224 19.5511 8.49993 18.9989 8.5H4.9989C4.44661 8.5 3.9989 8.05228 3.9989 7.5C3.9989 6.94772 4.44661 6.5 4.9989 6.5H18.9989Z",
12
+ "M16.9989 3C17.5511 3.00007 17.9989 3.44776 17.9989 4C17.9989 4.55224 17.5511 4.99993 16.9989 5H6.9989C6.44661 5 5.9989 4.55228 5.9989 4C5.9989 3.44772 6.44661 3 6.9989 3H16.9989Z"
13
+ ];
4
14
  var ICON_CLIPBOARD = {
5
15
  board: "M8 6C8 4.34315 9.34315 3 11 3H13C14.6569 3 16 4.34315 16 6V7H8V6Z",
6
16
  sparkle: "M19.2405 16.1852L18.5436 14.3733C18.4571 14.1484 18.241 14 18 14C17.759 14 17.5429 14.1484 17.4564 14.3733L16.7595 16.1852C16.658 16.4493 16.4493 16.658 16.1852 16.7595L14.3733 17.4564C14.1484 17.5429 14 17.759 14 18C14 18.241 14.1484 18.4571 14.3733 18.5436L16.1852 19.2405C16.4493 19.342 16.658 19.5507 16.7595 19.8148L17.4564 21.6267C17.5429 21.8516 17.759 22 18 22C18.241 22 18.4571 21.8516 18.5436 21.6267L19.2405 19.8148C19.342 19.5507 19.5507 19.342 19.8148 19.2405L21.6267 18.5436C21.8516 18.4571 22 18.241 22 18C22 17.759 21.8516 17.5429 21.6267 17.4564L19.8148 16.7595C19.5507 16.658 19.342 16.4493 19.2405 16.1852Z",
@@ -34,6 +44,9 @@ export {
34
44
  ICON_CHEVRON,
35
45
  ICON_CLIPBOARD,
36
46
  ICON_PANEL,
47
+ ICON_PAUSE,
48
+ ICON_PLAY,
49
+ ICON_TIMELINE,
37
50
  ICON_TRASH
38
51
  };
39
52
  //# sourceMappingURL=icons.js.map
package/dist/icons.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/icons.ts"],"sourcesContent":["// Shared SVG icon geometry — single source of truth across all framework adapters.\n\nexport const ICON_CHEVRON = 'M6 9.5L12 15.5L18 9.5';\n\nexport const ICON_CHECK = 'M5 12.75L10 19L19 5';\n\nexport const ICON_CLIPBOARD = {\n board: 'M8 6C8 4.34315 9.34315 3 11 3H13C14.6569 3 16 4.34315 16 6V7H8V6Z',\n sparkle: 'M19.2405 16.1852L18.5436 14.3733C18.4571 14.1484 18.241 14 18 14C17.759 14 17.5429 14.1484 17.4564 14.3733L16.7595 16.1852C16.658 16.4493 16.4493 16.658 16.1852 16.7595L14.3733 17.4564C14.1484 17.5429 14 17.759 14 18C14 18.241 14.1484 18.4571 14.3733 18.5436L16.1852 19.2405C16.4493 19.342 16.658 19.5507 16.7595 19.8148L17.4564 21.6267C17.5429 21.8516 17.759 22 18 22C18.241 22 18.4571 21.8516 18.5436 21.6267L19.2405 19.8148C19.342 19.5507 19.5507 19.342 19.8148 19.2405L21.6267 18.5436C21.8516 18.4571 22 18.241 22 18C22 17.759 21.8516 17.5429 21.6267 17.4564L19.8148 16.7595C19.5507 16.658 19.342 16.4493 19.2405 16.1852Z',\n body: 'M16 5H17C18.6569 5 20 6.34315 20 8V11M8 5H7C5.34315 5 4 6.34315 4 8V18C4 19.6569 5.34315 21 7 21H12',\n};\n\nexport const ICON_ADD_PRESET = [\n 'M4 6H20',\n 'M4 12H10',\n 'M15 15L21 15',\n 'M18 12V18',\n 'M4 18H10',\n];\n\nexport const ICON_TRASH = [\n 'M5 6.5L5.80734 18.2064C5.91582 19.7794 7.22348 21 8.80023 21H15.1998C16.7765 21 18.0842 19.7794 18.1927 18.2064L19 6.5',\n 'M10 11V16',\n 'M14 11V16',\n 'M3.5 6H20.5',\n 'M8.07092 5.74621C8.42348 3.89745 10.0485 2.5 12 2.5C13.9515 2.5 15.5765 3.89745 15.9291 5.74621',\n];\n\nexport const ICON_PANEL = {\n path: 'M6.84766 11.75C6.78583 11.9899 6.75 12.2408 6.75 12.5C6.75 12.7592 6.78583 13.0101 6.84766 13.25H2C1.58579 13.25 1.25 12.9142 1.25 12.5C1.25 12.0858 1.58579 11.75 2 11.75H6.84766ZM14 11.75C14.4142 11.75 14.75 12.0858 14.75 12.5C14.75 12.9142 14.4142 13.25 14 13.25H12.6523C12.7142 13.0101 12.75 12.7592 12.75 12.5C12.75 12.2408 12.7142 11.9899 12.6523 11.75H14ZM3.09766 7.25C3.03583 7.48994 3 7.74075 3 8C3 8.25925 3.03583 8.51006 3.09766 8.75H2C1.58579 8.75 1.25 8.41421 1.25 8C1.25 7.58579 1.58579 7.25 2 7.25H3.09766ZM14 7.25C14.4142 7.25 14.75 7.58579 14.75 8C14.75 8.41421 14.4142 8.75 14 8.75H8.90234C8.96417 8.51006 9 8.25925 9 8C9 7.74075 8.96417 7.48994 8.90234 7.25H14ZM7.59766 2.75C7.53583 2.98994 7.5 3.24075 7.5 3.5C7.5 3.75925 7.53583 4.01006 7.59766 4.25H2C1.58579 4.25 1.25 3.91421 1.25 3.5C1.25 3.08579 1.58579 2.75 2 2.75H7.59766ZM14 2.75C14.4142 2.75 14.75 3.08579 14.75 3.5C14.75 3.91421 14.4142 4.25 14 4.25H13.4023C13.4642 4.01006 13.5 3.75925 13.5 3.5C13.5 3.24075 13.4642 2.98994 13.4023 2.75H14Z',\n circles: [\n { cx: '6', cy: '8', r: '0.998596' },\n { cx: '10.4999', cy: '3.5', r: '0.998657' },\n { cx: '9.75015', cy: '12.5', r: '0.997986' },\n ],\n};\n"],"mappings":";AAEO,IAAM,eAAe;AAErB,IAAM,aAAa;AAEnB,IAAM,iBAAiB;AAAA,EAC5B,OAAO;AAAA,EACP,SAAS;AAAA,EACT,MAAM;AACR;AAEO,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,aAAa;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,aAAa;AAAA,EACxB,MAAM;AAAA,EACN,SAAS;AAAA,IACP,EAAE,IAAI,KAAK,IAAI,KAAK,GAAG,WAAW;AAAA,IAClC,EAAE,IAAI,WAAW,IAAI,OAAO,GAAG,WAAW;AAAA,IAC1C,EAAE,IAAI,WAAW,IAAI,QAAQ,GAAG,WAAW;AAAA,EAC7C;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/icons.ts"],"sourcesContent":["// Shared SVG icon geometry — single source of truth across all framework adapters.\n\nexport const ICON_CHEVRON = 'M6 9.5L12 15.5L18 9.5';\n\nexport const ICON_CHECK = 'M5 12.75L10 19L19 5';\n\nexport const ICON_PAUSE = [\n 'M6.75 3C5.23122 3 4 4.23122 4 5.75V18.25C4 19.7688 5.23122 21 6.75 21H7.25C8.76878 21 10 19.7688 10 18.25V5.75C10 4.23122 8.76878 3 7.25 3H6.75Z',\n 'M16.75 3C15.2312 3 14 4.23122 14 5.75V18.25C14 19.7688 15.2312 21 16.75 21H17.25C18.7688 21 20 19.7688 20 18.25V5.75C20 4.23122 18.7688 3 17.25 3H16.75Z',\n];\n\nexport const ICON_PLAY = 'M9.24394 2.36758C7.41419 1.18362 5 2.49701 5 4.67639V19.3238C5 21.5032 7.41419 22.8166 9.24394 21.6326L20.5624 14.3089C22.2371 13.2253 22.2372 10.775 20.5624 9.69129L9.24394 2.36758Z';\n\nexport const ICON_TIMELINE = [\n 'M18.868 10C20.8517 10.0003 22.2886 11.8914 21.7577 13.8027L20.369 18.8027C20.0083 20.1012 18.826 20.9999 17.4784 21H6.51941C5.17179 21 3.98948 20.1012 3.62878 18.8027L2.24011 13.8027C1.7092 11.8913 3.14603 10.0003 5.12976 10H18.868Z',\n 'M18.9989 6.5C19.5511 6.50007 19.9989 6.94776 19.9989 7.5C19.9989 8.05224 19.5511 8.49993 18.9989 8.5H4.9989C4.44661 8.5 3.9989 8.05228 3.9989 7.5C3.9989 6.94772 4.44661 6.5 4.9989 6.5H18.9989Z',\n 'M16.9989 3C17.5511 3.00007 17.9989 3.44776 17.9989 4C17.9989 4.55224 17.5511 4.99993 16.9989 5H6.9989C6.44661 5 5.9989 4.55228 5.9989 4C5.9989 3.44772 6.44661 3 6.9989 3H16.9989Z',\n];\n\nexport const ICON_CLIPBOARD = {\n board: 'M8 6C8 4.34315 9.34315 3 11 3H13C14.6569 3 16 4.34315 16 6V7H8V6Z',\n sparkle: 'M19.2405 16.1852L18.5436 14.3733C18.4571 14.1484 18.241 14 18 14C17.759 14 17.5429 14.1484 17.4564 14.3733L16.7595 16.1852C16.658 16.4493 16.4493 16.658 16.1852 16.7595L14.3733 17.4564C14.1484 17.5429 14 17.759 14 18C14 18.241 14.1484 18.4571 14.3733 18.5436L16.1852 19.2405C16.4493 19.342 16.658 19.5507 16.7595 19.8148L17.4564 21.6267C17.5429 21.8516 17.759 22 18 22C18.241 22 18.4571 21.8516 18.5436 21.6267L19.2405 19.8148C19.342 19.5507 19.5507 19.342 19.8148 19.2405L21.6267 18.5436C21.8516 18.4571 22 18.241 22 18C22 17.759 21.8516 17.5429 21.6267 17.4564L19.8148 16.7595C19.5507 16.658 19.342 16.4493 19.2405 16.1852Z',\n body: 'M16 5H17C18.6569 5 20 6.34315 20 8V11M8 5H7C5.34315 5 4 6.34315 4 8V18C4 19.6569 5.34315 21 7 21H12',\n};\n\nexport const ICON_ADD_PRESET = [\n 'M4 6H20',\n 'M4 12H10',\n 'M15 15L21 15',\n 'M18 12V18',\n 'M4 18H10',\n];\n\nexport const ICON_TRASH = [\n 'M5 6.5L5.80734 18.2064C5.91582 19.7794 7.22348 21 8.80023 21H15.1998C16.7765 21 18.0842 19.7794 18.1927 18.2064L19 6.5',\n 'M10 11V16',\n 'M14 11V16',\n 'M3.5 6H20.5',\n 'M8.07092 5.74621C8.42348 3.89745 10.0485 2.5 12 2.5C13.9515 2.5 15.5765 3.89745 15.9291 5.74621',\n];\n\nexport const ICON_PANEL = {\n path: 'M6.84766 11.75C6.78583 11.9899 6.75 12.2408 6.75 12.5C6.75 12.7592 6.78583 13.0101 6.84766 13.25H2C1.58579 13.25 1.25 12.9142 1.25 12.5C1.25 12.0858 1.58579 11.75 2 11.75H6.84766ZM14 11.75C14.4142 11.75 14.75 12.0858 14.75 12.5C14.75 12.9142 14.4142 13.25 14 13.25H12.6523C12.7142 13.0101 12.75 12.7592 12.75 12.5C12.75 12.2408 12.7142 11.9899 12.6523 11.75H14ZM3.09766 7.25C3.03583 7.48994 3 7.74075 3 8C3 8.25925 3.03583 8.51006 3.09766 8.75H2C1.58579 8.75 1.25 8.41421 1.25 8C1.25 7.58579 1.58579 7.25 2 7.25H3.09766ZM14 7.25C14.4142 7.25 14.75 7.58579 14.75 8C14.75 8.41421 14.4142 8.75 14 8.75H8.90234C8.96417 8.51006 9 8.25925 9 8C9 7.74075 8.96417 7.48994 8.90234 7.25H14ZM7.59766 2.75C7.53583 2.98994 7.5 3.24075 7.5 3.5C7.5 3.75925 7.53583 4.01006 7.59766 4.25H2C1.58579 4.25 1.25 3.91421 1.25 3.5C1.25 3.08579 1.58579 2.75 2 2.75H7.59766ZM14 2.75C14.4142 2.75 14.75 3.08579 14.75 3.5C14.75 3.91421 14.4142 4.25 14 4.25H13.4023C13.4642 4.01006 13.5 3.75925 13.5 3.5C13.5 3.24075 13.4642 2.98994 13.4023 2.75H14Z',\n circles: [\n { cx: '6', cy: '8', r: '0.998596' },\n { cx: '10.4999', cy: '3.5', r: '0.998657' },\n { cx: '9.75015', cy: '12.5', r: '0.997986' },\n ],\n};\n"],"mappings":";AAEO,IAAM,eAAe;AAErB,IAAM,aAAa;AAEnB,IAAM,aAAa;AAAA,EACxB;AAAA,EACA;AACF;AAEO,IAAM,YAAY;AAElB,IAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,iBAAiB;AAAA,EAC5B,OAAO;AAAA,EACP,SAAS;AAAA,EACT,MAAM;AACR;AAEO,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,aAAa;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,aAAa;AAAA,EACxB,MAAM;AAAA,EACN,SAAS;AAAA,IACP,EAAE,IAAI,KAAK,IAAI,KAAK,GAAG,WAAW;AAAA,IAClC,EAAE,IAAI,WAAW,IAAI,OAAO,GAAG,WAAW;AAAA,IAC1C,EAAE,IAAI,WAAW,IAAI,QAAQ,GAAG,WAAW;AAAA,EAC7C;AACF;","names":[]}