dialkit 1.2.1 → 1.3.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 (55) hide show
  1. package/README.md +220 -3
  2. package/dist/dropdown-position.d.ts +15 -0
  3. package/dist/dropdown-position.js +22 -0
  4. package/dist/dropdown-position.js.map +1 -0
  5. package/dist/index.cjs +697 -307
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +42 -5
  8. package/dist/index.d.ts +42 -5
  9. package/dist/index.js +720 -331
  10. package/dist/index.js.map +1 -1
  11. package/dist/panel-drag.d.ts +19 -0
  12. package/dist/panel-drag.js +72 -0
  13. package/dist/panel-drag.js.map +1 -0
  14. package/dist/solid/index.cjs +661 -204
  15. package/dist/solid/index.cjs.map +1 -1
  16. package/dist/solid/index.d.cts +40 -3
  17. package/dist/solid/index.d.ts +40 -3
  18. package/dist/solid/index.js +646 -190
  19. package/dist/solid/index.js.map +1 -1
  20. package/dist/store/index.cjs +272 -41
  21. package/dist/store/index.cjs.map +1 -1
  22. package/dist/store/index.d.cts +30 -3
  23. package/dist/store/index.d.ts +30 -3
  24. package/dist/store/index.js +269 -40
  25. package/dist/store/index.js.map +1 -1
  26. package/dist/styles.css +105 -7
  27. package/dist/svelte/components/DialRoot.svelte +176 -11
  28. package/dist/svelte/components/DialRoot.svelte.d.ts +1 -0
  29. package/dist/svelte/components/DialRoot.svelte.d.ts.map +1 -1
  30. package/dist/svelte/components/Folder.svelte +24 -13
  31. package/dist/svelte/components/Folder.svelte.d.ts +1 -0
  32. package/dist/svelte/components/Folder.svelte.d.ts.map +1 -1
  33. package/dist/svelte/components/Panel.svelte +98 -71
  34. package/dist/svelte/components/Panel.svelte.d.ts +2 -0
  35. package/dist/svelte/components/Panel.svelte.d.ts.map +1 -1
  36. package/dist/svelte/components/PresetManager.svelte +5 -5
  37. package/dist/svelte/components/PresetManager.svelte.d.ts.map +1 -1
  38. package/dist/svelte/components/SelectControl.svelte +6 -14
  39. package/dist/svelte/components/SelectControl.svelte.d.ts.map +1 -1
  40. package/dist/svelte/createDialKit.svelte.d.ts +11 -1
  41. package/dist/svelte/createDialKit.svelte.d.ts.map +1 -1
  42. package/dist/svelte/createDialKit.svelte.js +61 -34
  43. package/dist/svelte/index.d.ts +3 -3
  44. package/dist/svelte/index.d.ts.map +1 -1
  45. package/dist/svelte/index.js +1 -1
  46. package/dist/svelte/theme-css.d.ts +1 -1
  47. package/dist/svelte/theme-css.d.ts.map +1 -1
  48. package/dist/svelte/theme-css.js +105 -7
  49. package/dist/vue/index.cjs +573 -132
  50. package/dist/vue/index.cjs.map +1 -1
  51. package/dist/vue/index.d.cts +53 -6
  52. package/dist/vue/index.d.ts +53 -6
  53. package/dist/vue/index.js +573 -133
  54. package/dist/vue/index.js.map +1 -1
  55. package/package.json +2 -2
package/README.md CHANGED
@@ -72,6 +72,8 @@ const params = useDialKit(name, config, options?)
72
72
  |-------|------|-------------|
73
73
  | `name` | `string` | Panel title displayed in the UI |
74
74
  | `config` | `DialConfig` | Parameter definitions (see Control Types below) |
75
+ | `options.id` | `string` | Stable logical id for sharing values across remounts/pages |
76
+ | `options.persist` | `DialKitPersistOptions` | Persist values to browser storage |
75
77
  | `options.onAction` | `(path: string) => void` | Callback when action buttons are clicked |
76
78
  | `options.shortcuts` | `Record<string, ShortcutConfig>` | Keyboard shortcuts for controls (see [Keyboard Shortcuts](#keyboard-shortcuts)) |
77
79
 
@@ -79,6 +81,122 @@ Returns a fully typed object matching your config shape with live values. Updati
79
81
 
80
82
  ---
81
83
 
84
+ ## Stable IDs and Persistence
85
+
86
+ By default, a DialKit panel is tied to the lifecycle of the component that calls `useDialKit`. Pass `id` when multiple mounts should reconnect to the same logical panel, and pass `persist: true` when values should survive reloads and browser sessions.
87
+
88
+ ```tsx
89
+ // dials/useOnboardingDials.ts
90
+ import { useDialKit } from 'dialkit';
91
+
92
+ export function useOnboardingDials() {
93
+ return useDialKit('Onboarding', {
94
+ name: { type: 'text', default: 'Avery', placeholder: 'Name' },
95
+ avatarScale: [1, 0.6, 1.6, 0.01],
96
+ accent: { type: 'color', default: '#6C5CE7' },
97
+ }, {
98
+ id: 'onboarding',
99
+ persist: true,
100
+ });
101
+ }
102
+ ```
103
+
104
+ Use that helper anywhere the shared values are needed:
105
+
106
+ ```tsx
107
+ function PageTwo() {
108
+ const onboarding = useOnboardingDials();
109
+
110
+ const page = useDialKit('Page Two', {
111
+ cardRadius: [16, 0, 64],
112
+ });
113
+
114
+ return (
115
+ <Card
116
+ name={onboarding.name}
117
+ radius={page.cardRadius}
118
+ accent={onboarding.accent}
119
+ />
120
+ );
121
+ }
122
+ ```
123
+
124
+ When `PageTwo` is mounted, the single `<DialRoot />` shows both `Onboarding` and `Page Two` as top-level sections. If another page calls `useOnboardingDials()`, DialKit reconnects to the same `id` and keeps the shared values.
125
+
126
+ `persist: true` stores values, presets, and the active preset in `localStorage` using `dialkit:${id}` as the key. Use the object form to customize storage:
127
+
128
+ ```tsx
129
+ useDialKit('Onboarding', config, {
130
+ id: 'onboarding',
131
+ persist: {
132
+ key: 'my-app:onboarding-dials',
133
+ storage: 'sessionStorage',
134
+ presets: false,
135
+ },
136
+ });
137
+ ```
138
+
139
+ The `id` string has no special format; it only needs to be reused wherever you want the same logical panel. Without `id` or `persist`, DialKit behaves exactly as before.
140
+
141
+ ---
142
+
143
+ ## useDialKitController
144
+
145
+ Use the controller API when your app code also needs to update DialKit values, such as reset buttons, URL sync, or app-defined preset buttons.
146
+
147
+ ```tsx
148
+ import { useDialKitController } from 'dialkit';
149
+
150
+ function Card() {
151
+ const dial = useDialKitController('Card', {
152
+ blur: [24, 0, 100],
153
+ scale: 1.2,
154
+ color: '#ff5500',
155
+ visible: true,
156
+ shadow: {
157
+ radius: [16, 0, 64],
158
+ },
159
+ });
160
+
161
+ return (
162
+ <>
163
+ <button onClick={() => dial.setValues({
164
+ blur: 48,
165
+ scale: 1,
166
+ shadow: { radius: 28 },
167
+ })}>
168
+ Apply preset
169
+ </button>
170
+ <button onClick={() => dial.resetValues()}>Reset</button>
171
+
172
+ <div style={{
173
+ filter: `blur(${dial.values.blur}px)`,
174
+ transform: `scale(${dial.values.scale})`,
175
+ color: dial.values.color,
176
+ opacity: dial.values.visible ? 1 : 0,
177
+ borderRadius: dial.values.shadow.radius,
178
+ }}>
179
+ ...
180
+ </div>
181
+ </>
182
+ );
183
+ }
184
+ ```
185
+
186
+ Controller methods:
187
+
188
+ | Method | Description |
189
+ |--------|-------------|
190
+ | `values` | The same live resolved values returned by `useDialKit` |
191
+ | `setValue(path, value)` | Updates one control by dot path, like `'shadow.radius'` |
192
+ | `setValues(values)` | Updates multiple controls with a typed nested partial object |
193
+ | `resetValues()` | Restores the current config defaults and clears the active preset |
194
+ | `getValues()` | Reads the latest resolved values outside render callbacks |
195
+
196
+ Programmatic updates use the same state as panel edits. If a saved preset is active, updates are saved into that preset; otherwise they update the base "Version 1" values. Action controls are triggers, so they are not set by `setValues`.
197
+
198
+ ---
199
+
82
200
  ## Control Types
83
201
 
84
202
  ### Slider
@@ -269,9 +387,47 @@ const values = useDialKit('Controls', {
269
387
  | `defaultOpen` | `boolean` | `true` |
270
388
  | `mode` | `'popover' \| 'inline'` | `'popover'` |
271
389
  | `productionEnabled` | `boolean` | `false` in production, `true` otherwise |
390
+ | `onOpenChange` | `(open: boolean) => void` | `undefined` |
272
391
 
273
392
  Mount once at your app root. In the default `popover` mode, the panel renders via a portal on `document.body`. It collapses to a small icon button and expands to 280px wide on click.
274
393
 
394
+ ### Multiple panels
395
+
396
+ If multiple `useDialKit` calls are registered under the same root, DialKit renders one shared shell and shows each panel as a collapsible top-level section. No extra API is needed:
397
+
398
+ ```tsx
399
+ function PhotoStack() {
400
+ const photo = useDialKit('Photo Stack', {
401
+ blur: [12, 0, 40],
402
+ scale: [1, 0.5, 2],
403
+ });
404
+
405
+ const stage = useDialKit('Stage', {
406
+ pagePadding: [40, 16, 96],
407
+ background: '#ffffff',
408
+ });
409
+
410
+ return (
411
+ <div style={{ padding: stage.pagePadding, background: stage.background }}>
412
+ <img style={{ filter: `blur(${photo.blur}px)`, transform: `scale(${photo.scale})` }} />
413
+ </div>
414
+ );
415
+ }
416
+ ```
417
+
418
+ The mounted `<DialRoot />` stays the same. With a single registered panel, the panel title and layout stay unchanged. This behavior works the same way in React, Solid, Svelte, and Vue.
419
+
420
+ Use `onOpenChange` when you need to persist whether the floating panel is open or collapsed:
421
+
422
+ ```tsx
423
+ <DialRoot
424
+ defaultOpen={localStorage.getItem('dialkit-open') !== '0'}
425
+ onOpenChange={(open) => {
426
+ localStorage.setItem('dialkit-open', open ? '1' : '0');
427
+ }}
428
+ />
429
+ ```
430
+
275
431
  DialKit is automatically hidden in production builds. To enable it in production, pass `productionEnabled`:
276
432
 
277
433
  ```tsx
@@ -513,7 +669,22 @@ function Card() {
513
669
  }
514
670
  ```
515
671
 
516
- `createDialKit` returns an accessor — call `params()` to read the current values. All control types, config shapes, and panel features (presets, copy, folders) work identically to the React version.
672
+ `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
+
674
+ Use `createDialKitController` when Solid code needs to update values:
675
+
676
+ ```tsx
677
+ import { createDialKitController } from 'dialkit/solid';
678
+
679
+ const dial = createDialKitController('Card', {
680
+ blur: [24, 0, 100],
681
+ shadow: { radius: [16, 0, 64] },
682
+ });
683
+
684
+ dial.setValues({ blur: 48, shadow: { radius: 28 } });
685
+ dial.resetValues();
686
+ dial.values().blur;
687
+ ```
517
688
 
518
689
  ---
519
690
 
@@ -554,7 +725,30 @@ npm install dialkit
554
725
  </div>
555
726
  ```
556
727
 
557
- `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, and transitions match the React/Solid entries.
728
+ `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
+
730
+ Use `createDialKitController` when Svelte code needs to update values:
731
+
732
+ ```svelte
733
+ <script>
734
+ import { createDialKitController } from 'dialkit/svelte';
735
+
736
+ const dial = createDialKitController('Card', {
737
+ blur: [24, 0, 100],
738
+ shadow: { radius: [16, 0, 64] }
739
+ });
740
+
741
+ const params = dial.values;
742
+ </script>
743
+
744
+ <button onclick={() => dial.setValues({ blur: 48, shadow: { radius: 28 } })}>
745
+ Apply preset
746
+ </button>
747
+
748
+ <div style:filter={`blur(${params.blur}px)`} style:border-radius={`${params.shadow.radius}px`}>
749
+ ...
750
+ </div>
751
+ ```
558
752
 
559
753
  ---
560
754
 
@@ -586,7 +780,7 @@ import Card from './Card.vue';
586
780
 
587
781
  <template>
588
782
  <Card />
589
- <DialRoot />
783
+ <DialRoot @open-change="(open) => localStorage.setItem('dialkit-open', open ? '1' : '0')" />
590
784
  </template>
591
785
  ```
592
786
 
@@ -617,6 +811,25 @@ const params = useDialKit('Card', {
617
811
 
618
812
  `useDialKit` returns a reactive object. All control types, presets, folders, keyboard shortcuts, and transitions work identically to the other frameworks.
619
813
 
814
+ Use `useDialKitController` when Vue code needs to update values:
815
+
816
+ ```vue
817
+ <script setup>
818
+ import { useDialKitController } from 'dialkit/vue';
819
+
820
+ const { values, setValues, resetValues } = useDialKitController('Card', {
821
+ blur: [24, 0, 100],
822
+ shadow: { radius: [16, 0, 64] },
823
+ });
824
+ </script>
825
+
826
+ <template>
827
+ <button @click="setValues({ blur: 48, shadow: { radius: 28 } })">Apply preset</button>
828
+ <button @click="resetValues()">Reset</button>
829
+ <div :style="{ filter: `blur(${values.blur}px)`, borderRadius: `${values.shadow.radius}px` }" />
830
+ </template>
831
+ ```
832
+
620
833
  ---
621
834
 
622
835
  ## Types
@@ -626,6 +839,8 @@ All config and value types are exported:
626
839
  ```tsx
627
840
  import type {
628
841
  SpringConfig,
842
+ EasingConfig,
843
+ TransitionConfig,
629
844
  ActionConfig,
630
845
  SelectConfig,
631
846
  ColorConfig,
@@ -634,6 +849,8 @@ import type {
634
849
  ShortcutMode,
635
850
  DialConfig,
636
851
  DialValue,
852
+ DialKitController,
853
+ DialKitValueUpdates,
637
854
  ResolvedValues,
638
855
  ControlMeta,
639
856
  PanelConfig,
@@ -0,0 +1,15 @@
1
+ type DropdownPosition = {
2
+ top: number;
3
+ left: number;
4
+ width: number;
5
+ above: boolean;
6
+ };
7
+ type DropdownPositionOptions = {
8
+ dropdownHeight?: number;
9
+ gap?: number;
10
+ allowAbove?: boolean;
11
+ };
12
+ declare function getDropdownPosition(trigger: HTMLElement, portalRoot: HTMLElement, options?: DropdownPositionOptions): DropdownPosition;
13
+ declare function getDialKitPortalRoot(trigger: HTMLElement | null | undefined): HTMLElement | null;
14
+
15
+ export { type DropdownPosition, type DropdownPositionOptions, getDialKitPortalRoot, getDropdownPosition };
@@ -0,0 +1,22 @@
1
+ // src/dropdown-position.ts
2
+ function getDropdownPosition(trigger, portalRoot, options = {}) {
3
+ const { dropdownHeight = 0, gap = 4, allowAbove = true } = options;
4
+ const triggerRect = trigger.getBoundingClientRect();
5
+ const rootRect = portalRoot.getBoundingClientRect();
6
+ const spaceBelow = window.innerHeight - triggerRect.bottom - gap;
7
+ const above = allowAbove && spaceBelow < dropdownHeight && triggerRect.top > spaceBelow;
8
+ return {
9
+ top: above ? triggerRect.top - rootRect.top - dropdownHeight - gap : triggerRect.bottom - rootRect.top + gap,
10
+ left: triggerRect.left - rootRect.left,
11
+ width: triggerRect.width,
12
+ above
13
+ };
14
+ }
15
+ function getDialKitPortalRoot(trigger) {
16
+ return trigger?.closest(".dialkit-root") ?? null;
17
+ }
18
+ export {
19
+ getDialKitPortalRoot,
20
+ getDropdownPosition
21
+ };
22
+ //# sourceMappingURL=dropdown-position.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dropdown-position.ts"],"sourcesContent":["export type DropdownPosition = {\n top: number;\n left: number;\n width: number;\n above: boolean;\n};\n\nexport type DropdownPositionOptions = {\n dropdownHeight?: number;\n gap?: number;\n allowAbove?: boolean;\n};\n\nexport function getDropdownPosition(\n trigger: HTMLElement,\n portalRoot: HTMLElement,\n options: DropdownPositionOptions = {}\n): DropdownPosition {\n const { dropdownHeight = 0, gap = 4, allowAbove = true } = options;\n const triggerRect = trigger.getBoundingClientRect();\n const rootRect = portalRoot.getBoundingClientRect();\n const spaceBelow = window.innerHeight - triggerRect.bottom - gap;\n const above = allowAbove && spaceBelow < dropdownHeight && triggerRect.top > spaceBelow;\n\n return {\n top: above\n ? triggerRect.top - rootRect.top - dropdownHeight - gap\n : triggerRect.bottom - rootRect.top + gap,\n left: triggerRect.left - rootRect.left,\n width: triggerRect.width,\n above,\n };\n}\n\nexport function getDialKitPortalRoot(trigger: HTMLElement | null | undefined): HTMLElement | null {\n return (trigger?.closest('.dialkit-root') as HTMLElement | null) ?? null;\n}\n"],"mappings":";AAaO,SAAS,oBACd,SACA,YACA,UAAmC,CAAC,GAClB;AAClB,QAAM,EAAE,iBAAiB,GAAG,MAAM,GAAG,aAAa,KAAK,IAAI;AAC3D,QAAM,cAAc,QAAQ,sBAAsB;AAClD,QAAM,WAAW,WAAW,sBAAsB;AAClD,QAAM,aAAa,OAAO,cAAc,YAAY,SAAS;AAC7D,QAAM,QAAQ,cAAc,aAAa,kBAAkB,YAAY,MAAM;AAE7E,SAAO;AAAA,IACL,KAAK,QACD,YAAY,MAAM,SAAS,MAAM,iBAAiB,MAClD,YAAY,SAAS,SAAS,MAAM;AAAA,IACxC,MAAM,YAAY,OAAO,SAAS;AAAA,IAClC,OAAO,YAAY;AAAA,IACnB;AAAA,EACF;AACF;AAEO,SAAS,qBAAqB,SAA6D;AAChG,SAAQ,SAAS,QAAQ,eAAe,KAA4B;AACtE;","names":[]}