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/dist/index.d.ts
CHANGED
|
@@ -33,10 +33,10 @@ type TextConfig = {
|
|
|
33
33
|
};
|
|
34
34
|
type DialValue = number | boolean | string | SpringConfig | ActionConfig | SelectConfig | ColorConfig | TextConfig;
|
|
35
35
|
type DialConfig = {
|
|
36
|
-
[key: string]: DialValue | [number, number, number] | DialConfig;
|
|
36
|
+
[key: string]: DialValue | [number, number, number, number?] | DialConfig;
|
|
37
37
|
};
|
|
38
38
|
type ResolvedValues<T extends DialConfig> = {
|
|
39
|
-
[K in keyof T]: T[K] extends [number, number, number] ? number : T[K] extends SpringConfig ? SpringConfig : T[K] extends SelectConfig ? string : T[K] extends ColorConfig ? string : T[K] extends TextConfig ? string : T[K] extends DialConfig ? ResolvedValues<T[K]> : T[K];
|
|
39
|
+
[K in keyof T]: T[K] extends [number, number, number, number?] ? number : T[K] extends SpringConfig ? SpringConfig : T[K] extends SelectConfig ? string : T[K] extends ColorConfig ? string : T[K] extends TextConfig ? string : T[K] extends DialConfig ? ResolvedValues<T[K]> : T[K];
|
|
40
40
|
};
|
|
41
41
|
type ControlMeta = {
|
|
42
42
|
type: 'slider' | 'toggle' | 'spring' | 'folder' | 'action' | 'select' | 'color' | 'text';
|
|
@@ -46,6 +46,7 @@ type ControlMeta = {
|
|
|
46
46
|
max?: number;
|
|
47
47
|
step?: number;
|
|
48
48
|
children?: ControlMeta[];
|
|
49
|
+
defaultOpen?: boolean;
|
|
49
50
|
options?: (string | {
|
|
50
51
|
value: string;
|
|
51
52
|
label: string;
|
package/dist/index.js
CHANGED
|
@@ -160,16 +160,17 @@ var DialStoreClass = class {
|
|
|
160
160
|
parseConfig(config, prefix) {
|
|
161
161
|
const controls = [];
|
|
162
162
|
for (const [key, value] of Object.entries(config)) {
|
|
163
|
+
if (key === "_collapsed") continue;
|
|
163
164
|
const path = prefix ? `${prefix}.${key}` : key;
|
|
164
165
|
const label = this.formatLabel(key);
|
|
165
|
-
if (Array.isArray(value) && value.length
|
|
166
|
+
if (Array.isArray(value) && value.length <= 4 && typeof value[0] === "number") {
|
|
166
167
|
controls.push({
|
|
167
168
|
type: "slider",
|
|
168
169
|
path,
|
|
169
170
|
label,
|
|
170
171
|
min: value[1],
|
|
171
172
|
max: value[2],
|
|
172
|
-
step: this.inferStep(value[1], value[2])
|
|
173
|
+
step: value[3] ?? this.inferStep(value[1], value[2])
|
|
173
174
|
});
|
|
174
175
|
} else if (typeof value === "number") {
|
|
175
176
|
const { min, max, step } = this.inferRange(value);
|
|
@@ -193,11 +194,14 @@ var DialStoreClass = class {
|
|
|
193
194
|
controls.push({ type: "text", path, label });
|
|
194
195
|
}
|
|
195
196
|
} else if (typeof value === "object" && value !== null) {
|
|
197
|
+
const folderConfig = value;
|
|
198
|
+
const defaultOpen = "_collapsed" in folderConfig ? !folderConfig._collapsed : true;
|
|
196
199
|
controls.push({
|
|
197
200
|
type: "folder",
|
|
198
201
|
path,
|
|
199
202
|
label,
|
|
200
|
-
|
|
203
|
+
defaultOpen,
|
|
204
|
+
children: this.parseConfig(folderConfig, path)
|
|
201
205
|
});
|
|
202
206
|
}
|
|
203
207
|
}
|
|
@@ -206,8 +210,9 @@ var DialStoreClass = class {
|
|
|
206
210
|
flattenValues(config, prefix) {
|
|
207
211
|
const values = {};
|
|
208
212
|
for (const [key, value] of Object.entries(config)) {
|
|
213
|
+
if (key === "_collapsed") continue;
|
|
209
214
|
const path = prefix ? `${prefix}.${key}` : key;
|
|
210
|
-
if (Array.isArray(value) && value.length
|
|
215
|
+
if (Array.isArray(value) && value.length <= 4 && typeof value[0] === "number") {
|
|
211
216
|
values[path] = value[0];
|
|
212
217
|
} else if (typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
|
|
213
218
|
values[path] = value;
|
|
@@ -299,8 +304,9 @@ function useDialKit(name, config, options) {
|
|
|
299
304
|
function buildResolvedValues(config, flatValues, prefix) {
|
|
300
305
|
const result = {};
|
|
301
306
|
for (const [key, configValue] of Object.entries(config)) {
|
|
307
|
+
if (key === "_collapsed") continue;
|
|
302
308
|
const path = prefix ? `${prefix}.${key}` : key;
|
|
303
|
-
if (Array.isArray(configValue) && configValue.length
|
|
309
|
+
if (Array.isArray(configValue) && configValue.length <= 4 && typeof configValue[0] === "number") {
|
|
304
310
|
result[key] = flatValues[path] ?? configValue[0];
|
|
305
311
|
} else if (typeof configValue === "number" || typeof configValue === "boolean" || typeof configValue === "string") {
|
|
306
312
|
result[key] = flatValues[path] ?? configValue;
|
|
@@ -346,7 +352,7 @@ function getFirstOptionValue(options) {
|
|
|
346
352
|
|
|
347
353
|
// src/components/DialRoot.tsx
|
|
348
354
|
import { useEffect as useEffect7, useState as useState8 } from "react";
|
|
349
|
-
import { createPortal as
|
|
355
|
+
import { createPortal as createPortal3 } from "react-dom";
|
|
350
356
|
|
|
351
357
|
// src/components/Panel.tsx
|
|
352
358
|
import { useState as useState7, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
@@ -464,10 +470,14 @@ var CLICK_THRESHOLD = 3;
|
|
|
464
470
|
var DEAD_ZONE = 32;
|
|
465
471
|
var MAX_CURSOR_RANGE = 200;
|
|
466
472
|
var MAX_STRETCH = 8;
|
|
473
|
+
function decimalsForStep(step) {
|
|
474
|
+
const s = step.toString();
|
|
475
|
+
const dot = s.indexOf(".");
|
|
476
|
+
return dot === -1 ? 0 : s.length - dot - 1;
|
|
477
|
+
}
|
|
467
478
|
function roundValue(val, step) {
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
return Math.round(val * factor) / factor;
|
|
479
|
+
const raw = Math.round(val / step) * step;
|
|
480
|
+
return parseFloat(raw.toFixed(decimalsForStep(step)));
|
|
471
481
|
}
|
|
472
482
|
function snapToDecile(rawValue, min, max) {
|
|
473
483
|
const normalized = (rawValue - min) / (max - min);
|
|
@@ -615,7 +625,8 @@ function Slider({
|
|
|
615
625
|
if (!isInteracting) return;
|
|
616
626
|
if (isClickRef.current) {
|
|
617
627
|
const rawValue = positionToValue(e.clientX);
|
|
618
|
-
const
|
|
628
|
+
const discreteSteps2 = (max - min) / step;
|
|
629
|
+
const snappedValue = discreteSteps2 <= 10 ? Math.max(min, Math.min(max, min + Math.round((rawValue - min) / step) * step)) : snapToDecile(rawValue, min, max);
|
|
619
630
|
const newPct = percentFromValue(snappedValue);
|
|
620
631
|
if (animRef.current) {
|
|
621
632
|
animRef.current.stop();
|
|
@@ -695,7 +706,7 @@ function Slider({
|
|
|
695
706
|
e.stopPropagation();
|
|
696
707
|
e.preventDefault();
|
|
697
708
|
setShowInput(true);
|
|
698
|
-
setInputValue(
|
|
709
|
+
setInputValue(value.toFixed(decimalsForStep(step)));
|
|
699
710
|
}
|
|
700
711
|
};
|
|
701
712
|
const handleInputKeyDown = (e) => {
|
|
@@ -709,7 +720,7 @@ function Slider({
|
|
|
709
720
|
const handleInputBlur = () => {
|
|
710
721
|
handleInputSubmit();
|
|
711
722
|
};
|
|
712
|
-
const displayValue =
|
|
723
|
+
const displayValue = value.toFixed(decimalsForStep(step));
|
|
713
724
|
const HANDLE_BUFFER = 8;
|
|
714
725
|
const LABEL_CSS_LEFT = 10;
|
|
715
726
|
const VALUE_CSS_RIGHT = 10;
|
|
@@ -727,7 +738,18 @@ function Slider({
|
|
|
727
738
|
const valueDodge = percentage < leftThreshold || percentage > rightThreshold;
|
|
728
739
|
const handleOpacity = !isActive ? 0 : valueDodge ? 0.1 : isDragging ? 0.9 : 0.5;
|
|
729
740
|
const fillBackground = isActive ? "rgba(255, 255, 255, 0.15)" : "rgba(255, 255, 255, 0.11)";
|
|
730
|
-
const
|
|
741
|
+
const discreteSteps = (max - min) / step;
|
|
742
|
+
const hashMarks = discreteSteps <= 10 ? Array.from({ length: discreteSteps - 1 }, (_, i) => {
|
|
743
|
+
const pct = (i + 1) * step / (max - min) * 100;
|
|
744
|
+
return /* @__PURE__ */ jsx2(
|
|
745
|
+
"div",
|
|
746
|
+
{
|
|
747
|
+
className: "dialkit-slider-hashmark",
|
|
748
|
+
style: { left: `${pct}%` }
|
|
749
|
+
},
|
|
750
|
+
i
|
|
751
|
+
);
|
|
752
|
+
}) : Array.from({ length: 9 }, (_, i) => {
|
|
731
753
|
const pct = (i + 1) * 10;
|
|
732
754
|
return /* @__PURE__ */ jsx2(
|
|
733
755
|
"div",
|
|
@@ -1116,7 +1138,8 @@ function TextControl({ label, value, onChange, placeholder }) {
|
|
|
1116
1138
|
}
|
|
1117
1139
|
|
|
1118
1140
|
// src/components/SelectControl.tsx
|
|
1119
|
-
import { useState as useState4, useRef as useRef5, useEffect as useEffect4 } from "react";
|
|
1141
|
+
import { useState as useState4, useRef as useRef5, useEffect as useEffect4, useCallback as useCallback2 } from "react";
|
|
1142
|
+
import { createPortal } from "react-dom";
|
|
1120
1143
|
import { motion as motion4, AnimatePresence as AnimatePresence2 } from "motion/react";
|
|
1121
1144
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1122
1145
|
function toTitleCase(s) {
|
|
@@ -1129,23 +1152,50 @@ function normalizeOptions(options) {
|
|
|
1129
1152
|
}
|
|
1130
1153
|
function SelectControl({ label, value, options, onChange }) {
|
|
1131
1154
|
const [isOpen, setIsOpen] = useState4(false);
|
|
1132
|
-
const
|
|
1155
|
+
const triggerRef = useRef5(null);
|
|
1156
|
+
const dropdownRef = useRef5(null);
|
|
1157
|
+
const [portalTarget, setPortalTarget] = useState4(null);
|
|
1158
|
+
const [pos, setPos] = useState4(null);
|
|
1133
1159
|
const normalized = normalizeOptions(options);
|
|
1134
1160
|
const selectedOption = normalized.find((o) => o.value === value);
|
|
1161
|
+
const updatePos = useCallback2(() => {
|
|
1162
|
+
const el = triggerRef.current;
|
|
1163
|
+
if (!el) return;
|
|
1164
|
+
const rect = el.getBoundingClientRect();
|
|
1165
|
+
const dropdownHeight = 8 + normalized.length * 36;
|
|
1166
|
+
const spaceBelow = window.innerHeight - rect.bottom - 4;
|
|
1167
|
+
const above = spaceBelow < dropdownHeight && rect.top > spaceBelow;
|
|
1168
|
+
setPos({
|
|
1169
|
+
top: above ? rect.top - 4 : rect.bottom + 4,
|
|
1170
|
+
left: rect.left,
|
|
1171
|
+
width: rect.width,
|
|
1172
|
+
above
|
|
1173
|
+
});
|
|
1174
|
+
}, [normalized.length]);
|
|
1175
|
+
useEffect4(() => {
|
|
1176
|
+
const root = triggerRef.current?.closest(".dialkit-root");
|
|
1177
|
+
setPortalTarget(root ?? document.body);
|
|
1178
|
+
}, []);
|
|
1179
|
+
useEffect4(() => {
|
|
1180
|
+
if (!isOpen) return;
|
|
1181
|
+
updatePos();
|
|
1182
|
+
}, [isOpen, updatePos]);
|
|
1135
1183
|
useEffect4(() => {
|
|
1136
1184
|
if (!isOpen) return;
|
|
1137
1185
|
const handleClick = (e) => {
|
|
1138
|
-
|
|
1186
|
+
const target = e.target;
|
|
1187
|
+
if (triggerRef.current && !triggerRef.current.contains(target) && dropdownRef.current && !dropdownRef.current.contains(target)) {
|
|
1139
1188
|
setIsOpen(false);
|
|
1140
1189
|
}
|
|
1141
1190
|
};
|
|
1142
1191
|
document.addEventListener("mousedown", handleClick);
|
|
1143
1192
|
return () => document.removeEventListener("mousedown", handleClick);
|
|
1144
1193
|
}, [isOpen]);
|
|
1145
|
-
return /* @__PURE__ */ jsxs8("div", {
|
|
1194
|
+
return /* @__PURE__ */ jsxs8("div", { className: "dialkit-select-row", children: [
|
|
1146
1195
|
/* @__PURE__ */ jsxs8(
|
|
1147
1196
|
"button",
|
|
1148
1197
|
{
|
|
1198
|
+
ref: triggerRef,
|
|
1149
1199
|
className: "dialkit-select-trigger",
|
|
1150
1200
|
onClick: () => setIsOpen(!isOpen),
|
|
1151
1201
|
"data-open": String(isOpen),
|
|
@@ -1172,29 +1222,39 @@ function SelectControl({ label, value, options, onChange }) {
|
|
|
1172
1222
|
]
|
|
1173
1223
|
}
|
|
1174
1224
|
),
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
"
|
|
1185
|
-
{
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
setIsOpen(false);
|
|
1191
|
-
},
|
|
1192
|
-
children: option.label
|
|
1225
|
+
portalTarget && createPortal(
|
|
1226
|
+
/* @__PURE__ */ jsx8(AnimatePresence2, { children: isOpen && pos && /* @__PURE__ */ jsx8(
|
|
1227
|
+
motion4.div,
|
|
1228
|
+
{
|
|
1229
|
+
ref: dropdownRef,
|
|
1230
|
+
className: "dialkit-select-dropdown",
|
|
1231
|
+
initial: { opacity: 0, y: pos.above ? 8 : -8, scale: 0.95 },
|
|
1232
|
+
animate: { opacity: 1, y: 0, scale: 1 },
|
|
1233
|
+
exit: { opacity: 0, y: pos.above ? 8 : -8, scale: 0.95 },
|
|
1234
|
+
transition: { type: "spring", visualDuration: 0.15, bounce: 0 },
|
|
1235
|
+
style: {
|
|
1236
|
+
position: "fixed",
|
|
1237
|
+
left: pos.left,
|
|
1238
|
+
width: pos.width,
|
|
1239
|
+
...pos.above ? { bottom: window.innerHeight - pos.top, transformOrigin: "bottom" } : { top: pos.top, transformOrigin: "top" }
|
|
1193
1240
|
},
|
|
1194
|
-
option
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1241
|
+
children: normalized.map((option) => /* @__PURE__ */ jsx8(
|
|
1242
|
+
"button",
|
|
1243
|
+
{
|
|
1244
|
+
className: "dialkit-select-option",
|
|
1245
|
+
"data-selected": String(option.value === value),
|
|
1246
|
+
onClick: () => {
|
|
1247
|
+
onChange(option.value);
|
|
1248
|
+
setIsOpen(false);
|
|
1249
|
+
},
|
|
1250
|
+
children: option.label
|
|
1251
|
+
},
|
|
1252
|
+
option.value
|
|
1253
|
+
))
|
|
1254
|
+
}
|
|
1255
|
+
) }),
|
|
1256
|
+
portalTarget
|
|
1257
|
+
)
|
|
1198
1258
|
] });
|
|
1199
1259
|
}
|
|
1200
1260
|
|
|
@@ -1277,8 +1337,8 @@ function expandShorthandHex(hex) {
|
|
|
1277
1337
|
}
|
|
1278
1338
|
|
|
1279
1339
|
// src/components/PresetManager.tsx
|
|
1280
|
-
import { useState as useState6, useRef as useRef7, useEffect as useEffect6, useCallback as
|
|
1281
|
-
import { createPortal } from "react-dom";
|
|
1340
|
+
import { useState as useState6, useRef as useRef7, useEffect as useEffect6, useCallback as useCallback3 } from "react";
|
|
1341
|
+
import { createPortal as createPortal2 } from "react-dom";
|
|
1282
1342
|
import { motion as motion5, AnimatePresence as AnimatePresence3 } from "motion/react";
|
|
1283
1343
|
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1284
1344
|
function PresetManager({ panelId, presets, activePresetId, onAdd }) {
|
|
@@ -1288,7 +1348,7 @@ function PresetManager({ panelId, presets, activePresetId, onAdd }) {
|
|
|
1288
1348
|
const [pos, setPos] = useState6({ top: 0, left: 0, width: 0 });
|
|
1289
1349
|
const hasPresets = presets.length > 0;
|
|
1290
1350
|
const activePreset = presets.find((p) => p.id === activePresetId);
|
|
1291
|
-
const open =
|
|
1351
|
+
const open = useCallback3(() => {
|
|
1292
1352
|
if (!hasPresets) return;
|
|
1293
1353
|
const rect = triggerRef.current?.getBoundingClientRect();
|
|
1294
1354
|
if (rect) {
|
|
@@ -1296,8 +1356,8 @@ function PresetManager({ panelId, presets, activePresetId, onAdd }) {
|
|
|
1296
1356
|
}
|
|
1297
1357
|
setIsOpen(true);
|
|
1298
1358
|
}, [hasPresets]);
|
|
1299
|
-
const close =
|
|
1300
|
-
const toggle =
|
|
1359
|
+
const close = useCallback3(() => setIsOpen(false), []);
|
|
1360
|
+
const toggle = useCallback3(() => {
|
|
1301
1361
|
if (isOpen) close();
|
|
1302
1362
|
else open();
|
|
1303
1363
|
}, [isOpen, open, close]);
|
|
@@ -1353,12 +1413,12 @@ function PresetManager({ panelId, presets, activePresetId, onAdd }) {
|
|
|
1353
1413
|
]
|
|
1354
1414
|
}
|
|
1355
1415
|
),
|
|
1356
|
-
|
|
1416
|
+
createPortal2(
|
|
1357
1417
|
/* @__PURE__ */ jsx10(AnimatePresence3, { children: isOpen && /* @__PURE__ */ jsxs10(
|
|
1358
1418
|
motion5.div,
|
|
1359
1419
|
{
|
|
1360
1420
|
ref: dropdownRef,
|
|
1361
|
-
className: "dialkit-preset-dropdown",
|
|
1421
|
+
className: "dialkit-root dialkit-preset-dropdown",
|
|
1362
1422
|
style: { position: "fixed", top: pos.top, left: pos.left, minWidth: pos.width },
|
|
1363
1423
|
initial: { opacity: 0, y: 4, scale: 0.97 },
|
|
1364
1424
|
animate: { opacity: 1, y: 0, scale: 1 },
|
|
@@ -1477,7 +1537,7 @@ Apply these values as the new defaults in the useDialKit call.`;
|
|
|
1477
1537
|
control.path
|
|
1478
1538
|
);
|
|
1479
1539
|
case "folder":
|
|
1480
|
-
return /* @__PURE__ */ jsx11(Folder, { title: control.label, children: control.children?.map(renderControl) }, control.path);
|
|
1540
|
+
return /* @__PURE__ */ jsx11(Folder, { title: control.label, defaultOpen: control.defaultOpen ?? true, children: control.children?.map(renderControl) }, control.path);
|
|
1481
1541
|
case "text":
|
|
1482
1542
|
return /* @__PURE__ */ jsx11(
|
|
1483
1543
|
TextControl,
|
|
@@ -1638,7 +1698,7 @@ function DialRoot({ position = "top-right" }) {
|
|
|
1638
1698
|
return null;
|
|
1639
1699
|
}
|
|
1640
1700
|
const content = /* @__PURE__ */ jsx12("div", { className: "dialkit-root", children: /* @__PURE__ */ jsx12("div", { className: "dialkit-panel", "data-position": position, children: panels.map((panel) => /* @__PURE__ */ jsx12(Panel, { panel }, panel.id)) }) });
|
|
1641
|
-
return
|
|
1701
|
+
return createPortal3(content, document.body);
|
|
1642
1702
|
}
|
|
1643
1703
|
|
|
1644
1704
|
// src/components/ButtonGroup.tsx
|