dialkit 0.2.1 → 0.2.2
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/README.md +27 -6
- package/dist/index.cjs +103 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +108 -48
- package/dist/index.js.map +1 -1
- package/dist/styles.css +2 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,12 +72,24 @@ Returns a fully typed object matching your config shape with live values. Updati
|
|
|
72
72
|
|
|
73
73
|
### Slider
|
|
74
74
|
|
|
75
|
+
Numbers create sliders. There are three ways to define them:
|
|
76
|
+
|
|
77
|
+
**Explicit range** — `[default, min, max]`:
|
|
78
|
+
```tsx
|
|
79
|
+
blur: [24, 0, 100]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Explicit range + step** — `[default, min, max, step]`:
|
|
75
83
|
```tsx
|
|
76
|
-
blur: [24, 0, 100]
|
|
77
|
-
scale: 1.2 // auto-infers range from value
|
|
84
|
+
blur: [24, 0, 100, 5] // snaps in increments of 5
|
|
78
85
|
```
|
|
86
|
+
When `step` is omitted, it's inferred from the range (see table below).
|
|
79
87
|
|
|
80
|
-
|
|
88
|
+
**Auto-inferred** — bare number:
|
|
89
|
+
```tsx
|
|
90
|
+
scale: 1.2
|
|
91
|
+
```
|
|
92
|
+
A single number auto-infers a reasonable min, max, and step:
|
|
81
93
|
|
|
82
94
|
| Value range | Inferred min/max | Step |
|
|
83
95
|
|-------------|-----------------|------|
|
|
@@ -197,19 +209,28 @@ Action buttons trigger callbacks without storing any value. The `label` defaults
|
|
|
197
209
|
|
|
198
210
|
### Folder
|
|
199
211
|
|
|
212
|
+
Any nested plain object becomes a collapsible folder. Folders can nest arbitrarily deep.
|
|
213
|
+
|
|
200
214
|
```tsx
|
|
201
215
|
shadow: {
|
|
202
216
|
blur: [10, 0, 50],
|
|
203
217
|
opacity: [0.25, 0, 1],
|
|
204
218
|
color: '#000000',
|
|
205
219
|
}
|
|
220
|
+
|
|
221
|
+
// Access nested values:
|
|
222
|
+
params.shadow.blur // number
|
|
223
|
+
params.shadow.color // string
|
|
206
224
|
```
|
|
207
225
|
|
|
208
|
-
|
|
226
|
+
Folders are open by default. Add `_collapsed: true` to start a folder closed. This is a reserved metadata key — it controls the UI only and won't appear in your returned values.
|
|
209
227
|
|
|
210
228
|
```tsx
|
|
211
|
-
|
|
212
|
-
|
|
229
|
+
shadow: {
|
|
230
|
+
_collapsed: true, // folder starts closed
|
|
231
|
+
blur: [10, 0, 50],
|
|
232
|
+
opacity: [0.25, 0, 1],
|
|
233
|
+
}
|
|
213
234
|
```
|
|
214
235
|
|
|
215
236
|
---
|
package/dist/index.cjs
CHANGED
|
@@ -197,16 +197,17 @@ var DialStoreClass = class {
|
|
|
197
197
|
parseConfig(config, prefix) {
|
|
198
198
|
const controls = [];
|
|
199
199
|
for (const [key, value] of Object.entries(config)) {
|
|
200
|
+
if (key === "_collapsed") continue;
|
|
200
201
|
const path = prefix ? `${prefix}.${key}` : key;
|
|
201
202
|
const label = this.formatLabel(key);
|
|
202
|
-
if (Array.isArray(value) && value.length
|
|
203
|
+
if (Array.isArray(value) && value.length <= 4 && typeof value[0] === "number") {
|
|
203
204
|
controls.push({
|
|
204
205
|
type: "slider",
|
|
205
206
|
path,
|
|
206
207
|
label,
|
|
207
208
|
min: value[1],
|
|
208
209
|
max: value[2],
|
|
209
|
-
step: this.inferStep(value[1], value[2])
|
|
210
|
+
step: value[3] ?? this.inferStep(value[1], value[2])
|
|
210
211
|
});
|
|
211
212
|
} else if (typeof value === "number") {
|
|
212
213
|
const { min, max, step } = this.inferRange(value);
|
|
@@ -230,11 +231,14 @@ var DialStoreClass = class {
|
|
|
230
231
|
controls.push({ type: "text", path, label });
|
|
231
232
|
}
|
|
232
233
|
} else if (typeof value === "object" && value !== null) {
|
|
234
|
+
const folderConfig = value;
|
|
235
|
+
const defaultOpen = "_collapsed" in folderConfig ? !folderConfig._collapsed : true;
|
|
233
236
|
controls.push({
|
|
234
237
|
type: "folder",
|
|
235
238
|
path,
|
|
236
239
|
label,
|
|
237
|
-
|
|
240
|
+
defaultOpen,
|
|
241
|
+
children: this.parseConfig(folderConfig, path)
|
|
238
242
|
});
|
|
239
243
|
}
|
|
240
244
|
}
|
|
@@ -243,8 +247,9 @@ var DialStoreClass = class {
|
|
|
243
247
|
flattenValues(config, prefix) {
|
|
244
248
|
const values = {};
|
|
245
249
|
for (const [key, value] of Object.entries(config)) {
|
|
250
|
+
if (key === "_collapsed") continue;
|
|
246
251
|
const path = prefix ? `${prefix}.${key}` : key;
|
|
247
|
-
if (Array.isArray(value) && value.length
|
|
252
|
+
if (Array.isArray(value) && value.length <= 4 && typeof value[0] === "number") {
|
|
248
253
|
values[path] = value[0];
|
|
249
254
|
} else if (typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
|
|
250
255
|
values[path] = value;
|
|
@@ -336,8 +341,9 @@ function useDialKit(name, config, options) {
|
|
|
336
341
|
function buildResolvedValues(config, flatValues, prefix) {
|
|
337
342
|
const result = {};
|
|
338
343
|
for (const [key, configValue] of Object.entries(config)) {
|
|
344
|
+
if (key === "_collapsed") continue;
|
|
339
345
|
const path = prefix ? `${prefix}.${key}` : key;
|
|
340
|
-
if (Array.isArray(configValue) && configValue.length
|
|
346
|
+
if (Array.isArray(configValue) && configValue.length <= 4 && typeof configValue[0] === "number") {
|
|
341
347
|
result[key] = flatValues[path] ?? configValue[0];
|
|
342
348
|
} else if (typeof configValue === "number" || typeof configValue === "boolean" || typeof configValue === "string") {
|
|
343
349
|
result[key] = flatValues[path] ?? configValue;
|
|
@@ -383,7 +389,7 @@ function getFirstOptionValue(options) {
|
|
|
383
389
|
|
|
384
390
|
// src/components/DialRoot.tsx
|
|
385
391
|
var import_react16 = require("react");
|
|
386
|
-
var
|
|
392
|
+
var import_react_dom3 = require("react-dom");
|
|
387
393
|
|
|
388
394
|
// src/components/Panel.tsx
|
|
389
395
|
var import_react14 = require("react");
|
|
@@ -501,10 +507,14 @@ var CLICK_THRESHOLD = 3;
|
|
|
501
507
|
var DEAD_ZONE = 32;
|
|
502
508
|
var MAX_CURSOR_RANGE = 200;
|
|
503
509
|
var MAX_STRETCH = 8;
|
|
510
|
+
function decimalsForStep(step) {
|
|
511
|
+
const s = step.toString();
|
|
512
|
+
const dot = s.indexOf(".");
|
|
513
|
+
return dot === -1 ? 0 : s.length - dot - 1;
|
|
514
|
+
}
|
|
504
515
|
function roundValue(val, step) {
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
return Math.round(val * factor) / factor;
|
|
516
|
+
const raw = Math.round(val / step) * step;
|
|
517
|
+
return parseFloat(raw.toFixed(decimalsForStep(step)));
|
|
508
518
|
}
|
|
509
519
|
function snapToDecile(rawValue, min, max) {
|
|
510
520
|
const normalized = (rawValue - min) / (max - min);
|
|
@@ -652,7 +662,8 @@ function Slider({
|
|
|
652
662
|
if (!isInteracting) return;
|
|
653
663
|
if (isClickRef.current) {
|
|
654
664
|
const rawValue = positionToValue(e.clientX);
|
|
655
|
-
const
|
|
665
|
+
const discreteSteps2 = (max - min) / step;
|
|
666
|
+
const snappedValue = discreteSteps2 <= 10 ? Math.max(min, Math.min(max, min + Math.round((rawValue - min) / step) * step)) : snapToDecile(rawValue, min, max);
|
|
656
667
|
const newPct = percentFromValue(snappedValue);
|
|
657
668
|
if (animRef.current) {
|
|
658
669
|
animRef.current.stop();
|
|
@@ -732,7 +743,7 @@ function Slider({
|
|
|
732
743
|
e.stopPropagation();
|
|
733
744
|
e.preventDefault();
|
|
734
745
|
setShowInput(true);
|
|
735
|
-
setInputValue(
|
|
746
|
+
setInputValue(value.toFixed(decimalsForStep(step)));
|
|
736
747
|
}
|
|
737
748
|
};
|
|
738
749
|
const handleInputKeyDown = (e) => {
|
|
@@ -746,7 +757,7 @@ function Slider({
|
|
|
746
757
|
const handleInputBlur = () => {
|
|
747
758
|
handleInputSubmit();
|
|
748
759
|
};
|
|
749
|
-
const displayValue =
|
|
760
|
+
const displayValue = value.toFixed(decimalsForStep(step));
|
|
750
761
|
const HANDLE_BUFFER = 8;
|
|
751
762
|
const LABEL_CSS_LEFT = 10;
|
|
752
763
|
const VALUE_CSS_RIGHT = 10;
|
|
@@ -764,7 +775,18 @@ function Slider({
|
|
|
764
775
|
const valueDodge = percentage < leftThreshold || percentage > rightThreshold;
|
|
765
776
|
const handleOpacity = !isActive ? 0 : valueDodge ? 0.1 : isDragging ? 0.9 : 0.5;
|
|
766
777
|
const fillBackground = isActive ? "rgba(255, 255, 255, 0.15)" : "rgba(255, 255, 255, 0.11)";
|
|
767
|
-
const
|
|
778
|
+
const discreteSteps = (max - min) / step;
|
|
779
|
+
const hashMarks = discreteSteps <= 10 ? Array.from({ length: discreteSteps - 1 }, (_, i) => {
|
|
780
|
+
const pct = (i + 1) * step / (max - min) * 100;
|
|
781
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
782
|
+
"div",
|
|
783
|
+
{
|
|
784
|
+
className: "dialkit-slider-hashmark",
|
|
785
|
+
style: { left: `${pct}%` }
|
|
786
|
+
},
|
|
787
|
+
i
|
|
788
|
+
);
|
|
789
|
+
}) : Array.from({ length: 9 }, (_, i) => {
|
|
768
790
|
const pct = (i + 1) * 10;
|
|
769
791
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
770
792
|
"div",
|
|
@@ -1154,6 +1176,7 @@ function TextControl({ label, value, onChange, placeholder }) {
|
|
|
1154
1176
|
|
|
1155
1177
|
// src/components/SelectControl.tsx
|
|
1156
1178
|
var import_react9 = require("react");
|
|
1179
|
+
var import_react_dom = require("react-dom");
|
|
1157
1180
|
var import_react10 = require("motion/react");
|
|
1158
1181
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1159
1182
|
function toTitleCase(s) {
|
|
@@ -1166,23 +1189,50 @@ function normalizeOptions(options) {
|
|
|
1166
1189
|
}
|
|
1167
1190
|
function SelectControl({ label, value, options, onChange }) {
|
|
1168
1191
|
const [isOpen, setIsOpen] = (0, import_react9.useState)(false);
|
|
1169
|
-
const
|
|
1192
|
+
const triggerRef = (0, import_react9.useRef)(null);
|
|
1193
|
+
const dropdownRef = (0, import_react9.useRef)(null);
|
|
1194
|
+
const [portalTarget, setPortalTarget] = (0, import_react9.useState)(null);
|
|
1195
|
+
const [pos, setPos] = (0, import_react9.useState)(null);
|
|
1170
1196
|
const normalized = normalizeOptions(options);
|
|
1171
1197
|
const selectedOption = normalized.find((o) => o.value === value);
|
|
1198
|
+
const updatePos = (0, import_react9.useCallback)(() => {
|
|
1199
|
+
const el = triggerRef.current;
|
|
1200
|
+
if (!el) return;
|
|
1201
|
+
const rect = el.getBoundingClientRect();
|
|
1202
|
+
const dropdownHeight = 8 + normalized.length * 36;
|
|
1203
|
+
const spaceBelow = window.innerHeight - rect.bottom - 4;
|
|
1204
|
+
const above = spaceBelow < dropdownHeight && rect.top > spaceBelow;
|
|
1205
|
+
setPos({
|
|
1206
|
+
top: above ? rect.top - 4 : rect.bottom + 4,
|
|
1207
|
+
left: rect.left,
|
|
1208
|
+
width: rect.width,
|
|
1209
|
+
above
|
|
1210
|
+
});
|
|
1211
|
+
}, [normalized.length]);
|
|
1212
|
+
(0, import_react9.useEffect)(() => {
|
|
1213
|
+
const root = triggerRef.current?.closest(".dialkit-root");
|
|
1214
|
+
setPortalTarget(root ?? document.body);
|
|
1215
|
+
}, []);
|
|
1216
|
+
(0, import_react9.useEffect)(() => {
|
|
1217
|
+
if (!isOpen) return;
|
|
1218
|
+
updatePos();
|
|
1219
|
+
}, [isOpen, updatePos]);
|
|
1172
1220
|
(0, import_react9.useEffect)(() => {
|
|
1173
1221
|
if (!isOpen) return;
|
|
1174
1222
|
const handleClick = (e) => {
|
|
1175
|
-
|
|
1223
|
+
const target = e.target;
|
|
1224
|
+
if (triggerRef.current && !triggerRef.current.contains(target) && dropdownRef.current && !dropdownRef.current.contains(target)) {
|
|
1176
1225
|
setIsOpen(false);
|
|
1177
1226
|
}
|
|
1178
1227
|
};
|
|
1179
1228
|
document.addEventListener("mousedown", handleClick);
|
|
1180
1229
|
return () => document.removeEventListener("mousedown", handleClick);
|
|
1181
1230
|
}, [isOpen]);
|
|
1182
|
-
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", {
|
|
1231
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dialkit-select-row", children: [
|
|
1183
1232
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1184
1233
|
"button",
|
|
1185
1234
|
{
|
|
1235
|
+
ref: triggerRef,
|
|
1186
1236
|
className: "dialkit-select-trigger",
|
|
1187
1237
|
onClick: () => setIsOpen(!isOpen),
|
|
1188
1238
|
"data-open": String(isOpen),
|
|
@@ -1209,29 +1259,39 @@ function SelectControl({ label, value, options, onChange }) {
|
|
|
1209
1259
|
]
|
|
1210
1260
|
}
|
|
1211
1261
|
),
|
|
1212
|
-
|
|
1213
|
-
import_react10.
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
"
|
|
1222
|
-
{
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
setIsOpen(false);
|
|
1228
|
-
},
|
|
1229
|
-
children: option.label
|
|
1262
|
+
portalTarget && (0, import_react_dom.createPortal)(
|
|
1263
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react10.AnimatePresence, { children: isOpen && pos && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1264
|
+
import_react10.motion.div,
|
|
1265
|
+
{
|
|
1266
|
+
ref: dropdownRef,
|
|
1267
|
+
className: "dialkit-select-dropdown",
|
|
1268
|
+
initial: { opacity: 0, y: pos.above ? 8 : -8, scale: 0.95 },
|
|
1269
|
+
animate: { opacity: 1, y: 0, scale: 1 },
|
|
1270
|
+
exit: { opacity: 0, y: pos.above ? 8 : -8, scale: 0.95 },
|
|
1271
|
+
transition: { type: "spring", visualDuration: 0.15, bounce: 0 },
|
|
1272
|
+
style: {
|
|
1273
|
+
position: "fixed",
|
|
1274
|
+
left: pos.left,
|
|
1275
|
+
width: pos.width,
|
|
1276
|
+
...pos.above ? { bottom: window.innerHeight - pos.top, transformOrigin: "bottom" } : { top: pos.top, transformOrigin: "top" }
|
|
1230
1277
|
},
|
|
1231
|
-
option.
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1278
|
+
children: normalized.map((option) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1279
|
+
"button",
|
|
1280
|
+
{
|
|
1281
|
+
className: "dialkit-select-option",
|
|
1282
|
+
"data-selected": String(option.value === value),
|
|
1283
|
+
onClick: () => {
|
|
1284
|
+
onChange(option.value);
|
|
1285
|
+
setIsOpen(false);
|
|
1286
|
+
},
|
|
1287
|
+
children: option.label
|
|
1288
|
+
},
|
|
1289
|
+
option.value
|
|
1290
|
+
))
|
|
1291
|
+
}
|
|
1292
|
+
) }),
|
|
1293
|
+
portalTarget
|
|
1294
|
+
)
|
|
1235
1295
|
] });
|
|
1236
1296
|
}
|
|
1237
1297
|
|
|
@@ -1315,7 +1375,7 @@ function expandShorthandHex(hex) {
|
|
|
1315
1375
|
|
|
1316
1376
|
// src/components/PresetManager.tsx
|
|
1317
1377
|
var import_react12 = require("react");
|
|
1318
|
-
var
|
|
1378
|
+
var import_react_dom2 = require("react-dom");
|
|
1319
1379
|
var import_react13 = require("motion/react");
|
|
1320
1380
|
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1321
1381
|
function PresetManager({ panelId, presets, activePresetId, onAdd }) {
|
|
@@ -1390,12 +1450,12 @@ function PresetManager({ panelId, presets, activePresetId, onAdd }) {
|
|
|
1390
1450
|
]
|
|
1391
1451
|
}
|
|
1392
1452
|
),
|
|
1393
|
-
(0,
|
|
1453
|
+
(0, import_react_dom2.createPortal)(
|
|
1394
1454
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react13.AnimatePresence, { children: isOpen && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
1395
1455
|
import_react13.motion.div,
|
|
1396
1456
|
{
|
|
1397
1457
|
ref: dropdownRef,
|
|
1398
|
-
className: "dialkit-preset-dropdown",
|
|
1458
|
+
className: "dialkit-root dialkit-preset-dropdown",
|
|
1399
1459
|
style: { position: "fixed", top: pos.top, left: pos.left, minWidth: pos.width },
|
|
1400
1460
|
initial: { opacity: 0, y: 4, scale: 0.97 },
|
|
1401
1461
|
animate: { opacity: 1, y: 0, scale: 1 },
|
|
@@ -1514,7 +1574,7 @@ Apply these values as the new defaults in the useDialKit call.`;
|
|
|
1514
1574
|
control.path
|
|
1515
1575
|
);
|
|
1516
1576
|
case "folder":
|
|
1517
|
-
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Folder, { title: control.label, children: control.children?.map(renderControl) }, control.path);
|
|
1577
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Folder, { title: control.label, defaultOpen: control.defaultOpen ?? true, children: control.children?.map(renderControl) }, control.path);
|
|
1518
1578
|
case "text":
|
|
1519
1579
|
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1520
1580
|
TextControl,
|
|
@@ -1675,7 +1735,7 @@ function DialRoot({ position = "top-right" }) {
|
|
|
1675
1735
|
return null;
|
|
1676
1736
|
}
|
|
1677
1737
|
const content = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "dialkit-root", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "dialkit-panel", "data-position": position, children: panels.map((panel) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Panel, { panel }, panel.id)) }) });
|
|
1678
|
-
return (0,
|
|
1738
|
+
return (0, import_react_dom3.createPortal)(content, document.body);
|
|
1679
1739
|
}
|
|
1680
1740
|
|
|
1681
1741
|
// src/components/ButtonGroup.tsx
|