gridsmith-ui 0.10.0 → 0.11.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/dist/index.js CHANGED
@@ -1220,7 +1220,7 @@ function parseTypedDate(text, dayFirst = localeDayFirst()) {
1220
1220
  const mi = monthIndex(m[2]);
1221
1221
  return mi < 0 ? null : validDate(Number(m[3]), mi, Number(m[1]));
1222
1222
  }
1223
- m = /^(\d{1,2})[/.\-](\d{1,2})[/.\-](\d{2,4})$/.exec(t);
1223
+ m = /^(\d{1,2})[/.-](\d{1,2})[/.-](\d{2,4})$/.exec(t);
1224
1224
  if (m) {
1225
1225
  const a = Number(m[1]), b = Number(m[2]);
1226
1226
  const year = m[3].length === 2 ? 2e3 + Number(m[3]) : Number(m[3]);
@@ -1246,26 +1246,6 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1246
1246
  const firstDay = getFirstDayOfWeek(year, month);
1247
1247
  const today = useMemo(() => /* @__PURE__ */ new Date(), []);
1248
1248
  const isTopLayer = useOverlayLayer(open);
1249
- useEffect(() => {
1250
- if (!open) return;
1251
- const handler = (e) => {
1252
- const t = e.target;
1253
- if (triggerRef.current?.contains(t) || panelRef.current?.contains(t)) return;
1254
- setOpen(false);
1255
- };
1256
- const keyHandler = (e) => {
1257
- if (e.key === "Escape" && isTopLayer()) {
1258
- setOpen(false);
1259
- focusSegment(0);
1260
- }
1261
- };
1262
- document.addEventListener("mousedown", handler);
1263
- document.addEventListener("keydown", keyHandler);
1264
- return () => {
1265
- document.removeEventListener("mousedown", handler);
1266
- document.removeEventListener("keydown", keyHandler);
1267
- };
1268
- }, [open, isTopLayer]);
1269
1249
  const prevMonth = () => setViewDate(new Date(year, month - 1, 1));
1270
1250
  const nextMonth = () => setViewDate(new Date(year, month + 1, 1));
1271
1251
  const startOf = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
@@ -1280,18 +1260,39 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1280
1260
  };
1281
1261
  const dayFirst = useMemo(() => localeDayFirst(), []);
1282
1262
  const [segs, setSegs] = useState(() => value ? { day: value.getDate(), month: value.getMonth() + 1, year: value.getFullYear() } : { day: null, month: null, year: null });
1283
- const buffers = useRef({});
1263
+ const [buffers, setBuffers] = useState({});
1284
1264
  const lastCommitted = useRef(null);
1285
1265
  useEffect(() => {
1286
1266
  if (value && value.getTime() === lastCommitted.current) return;
1287
1267
  setSegs(value ? { day: value.getDate(), month: value.getMonth() + 1, year: value.getFullYear() } : { day: null, month: null, year: null });
1288
- buffers.current = {};
1268
+ setBuffers({});
1289
1269
  }, [value]);
1290
1270
  const specs = dateSegments(dayFirst, segs.month && segs.year ? getDaysInMonth(segs.year, segs.month - 1) : 31);
1291
1271
  const segmentRefs = useRef([]);
1292
1272
  const focusSegment = (i) => {
1293
1273
  segmentRefs.current[Math.max(0, Math.min(specs.length - 1, i))]?.focus();
1294
1274
  };
1275
+ const focusSegmentRef = useLatest(focusSegment);
1276
+ useEffect(() => {
1277
+ if (!open) return;
1278
+ const handler = (e) => {
1279
+ const t = e.target;
1280
+ if (triggerRef.current?.contains(t) || panelRef.current?.contains(t)) return;
1281
+ setOpen(false);
1282
+ };
1283
+ const keyHandler = (e) => {
1284
+ if (e.key === "Escape" && isTopLayer()) {
1285
+ setOpen(false);
1286
+ focusSegmentRef.current(0);
1287
+ }
1288
+ };
1289
+ document.addEventListener("mousedown", handler);
1290
+ document.addEventListener("keydown", keyHandler);
1291
+ return () => {
1292
+ document.removeEventListener("mousedown", handler);
1293
+ document.removeEventListener("keydown", keyHandler);
1294
+ };
1295
+ }, [open, isTopLayer, focusSegmentRef]);
1295
1296
  const [typedInvalid, setTypedInvalid] = useState(false);
1296
1297
  const commitIfComplete = (next) => {
1297
1298
  if (next.day === null || next.month === null || next.year === null) {
@@ -1319,7 +1320,7 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1319
1320
  if (!d) return;
1320
1321
  const next = { day: d.getDate(), month: d.getMonth() + 1, year: d.getFullYear() };
1321
1322
  setSegs(next);
1322
- buffers.current = {};
1323
+ setBuffers({});
1323
1324
  commitIfComplete(next);
1324
1325
  };
1325
1326
  const onSegmentKeyDown = (e, index, spec) => {
@@ -1327,6 +1328,7 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1327
1328
  if (key === "Tab" || key === "Shift" || key === "Escape" || key.startsWith("F")) return;
1328
1329
  e.preventDefault();
1329
1330
  const k = spec.key;
1331
+ const clearBuffer = () => setBuffers((b) => ({ ...b, [k]: "" }));
1330
1332
  if (key === "ArrowLeft") {
1331
1333
  focusSegment(index - 1);
1332
1334
  return;
@@ -1336,39 +1338,39 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1336
1338
  return;
1337
1339
  }
1338
1340
  if (key === "ArrowUp" || key === "ArrowDown") {
1339
- buffers.current[k] = "";
1341
+ clearBuffer();
1340
1342
  setSegment(k, stepValue(spec, segs[k], key === "ArrowUp" ? 1 : -1));
1341
1343
  return;
1342
1344
  }
1343
1345
  if (key === "Home") {
1344
- buffers.current[k] = "";
1346
+ clearBuffer();
1345
1347
  setSegment(k, spec.min);
1346
1348
  return;
1347
1349
  }
1348
1350
  if (key === "End") {
1349
- buffers.current[k] = "";
1351
+ clearBuffer();
1350
1352
  setSegment(k, spec.max);
1351
1353
  return;
1352
1354
  }
1353
1355
  if (key === "Backspace" || key === "Delete") {
1354
- if (segs[k] === null && !buffers.current[k]) {
1356
+ if (segs[k] === null && !buffers[k]) {
1355
1357
  focusSegment(index - 1);
1356
1358
  return;
1357
1359
  }
1358
- buffers.current[k] = "";
1360
+ clearBuffer();
1359
1361
  setSegment(k, null, false);
1360
1362
  return;
1361
1363
  }
1362
1364
  if (/^\d$/.test(key)) {
1363
- const r = typeDigit(spec, buffers.current[k] ?? "", key);
1364
- buffers.current[k] = r.advance ? "" : r.buffer;
1365
+ const r = typeDigit(spec, buffers[k] ?? "", key);
1366
+ setBuffers((b) => ({ ...b, [k]: r.advance ? "" : r.buffer }));
1365
1367
  setSegment(k, r.value);
1366
1368
  if (r.advance) focusSegment(index + 1);
1367
1369
  }
1368
1370
  };
1369
1371
  const shown = (spec) => {
1370
1372
  const k = spec.key;
1371
- const b = buffers.current[k];
1373
+ const b = buffers[k];
1372
1374
  if (b && segs[k] === null) return b;
1373
1375
  if (segs[k] === null) return spec.placeholder;
1374
1376
  return k === "year" ? String(segs[k]) : pad2(segs[k]);
@@ -1444,7 +1446,7 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1444
1446
  "aria-valuetext": segs[k] === null ? "empty" : String(segs[k]),
1445
1447
  onKeyDown: (e) => onSegmentKeyDown(e, i, spec),
1446
1448
  onClick: (e) => e.stopPropagation(),
1447
- className: cn("rounded-[var(--ds-radius-sm)] px-[2px] outline-none focus:bg-[var(--ds-accent-subtle)] focus:text-[var(--ds-accent-primary)] cursor-text", segs[k] === null && !buffers.current[k] && "text-[var(--ds-text-muted)]"),
1449
+ className: cn("rounded-[var(--ds-radius-sm)] px-[2px] outline-none focus:bg-[var(--ds-accent-subtle)] focus:text-[var(--ds-accent-primary)] cursor-text", segs[k] === null && !buffers[k] && "text-[var(--ds-text-muted)]"),
1448
1450
  children: shown(spec)
1449
1451
  }
1450
1452
  )
@@ -2288,6 +2290,12 @@ function SliderRange({
2288
2290
  )
2289
2291
  ] });
2290
2292
  }
2293
+ function fromValue(v, use24Hour) {
2294
+ const m = /^(\d{1,2}):(\d{2})$/.exec(v);
2295
+ if (!m) return { hour: null, minute: null, period: null };
2296
+ const h24 = Number(m[1]);
2297
+ return use24Hour ? { hour: h24, minute: Number(m[2]), period: null } : { hour: h24 % 12 === 0 ? 12 : h24 % 12, minute: Number(m[2]), period: h24 >= 12 ? "PM" : "AM" };
2298
+ }
2291
2299
  var CONTROL_HEIGHT4 = {
2292
2300
  sm: "h-[var(--ds-control-height-sm)]",
2293
2301
  md: "h-[var(--ds-control-height-md)]",
@@ -2365,25 +2373,6 @@ function TimePicker({
2365
2373
  const minutes = generateMinutes(minuteStep);
2366
2374
  const isTopLayer = useOverlayLayer(open);
2367
2375
  useCloseOnFocusOut(open, triggerRef, panelRef, () => setOpen(false), isTopLayer);
2368
- useEffect(() => {
2369
- if (!open) return;
2370
- const handler = (e) => {
2371
- if (triggerRef.current?.contains(e.target) || panelRef.current?.contains(e.target)) return;
2372
- setOpen(false);
2373
- };
2374
- const keyHandler = (e) => {
2375
- if (e.key === "Escape" && isTopLayer()) {
2376
- setOpen(false);
2377
- focusSegment(0);
2378
- }
2379
- };
2380
- document.addEventListener("mousedown", handler);
2381
- document.addEventListener("keydown", keyHandler);
2382
- return () => {
2383
- document.removeEventListener("mousedown", handler);
2384
- document.removeEventListener("keydown", keyHandler);
2385
- };
2386
- }, [open, isTopLayer]);
2387
2376
  useEffect(() => {
2388
2377
  if (!open) return;
2389
2378
  const id2 = requestAnimationFrame(() => {
@@ -2421,19 +2410,13 @@ function TimePicker({
2421
2410
  onChange(`${pad(h24)}:${pad(minute)}`);
2422
2411
  }
2423
2412
  const specs = timeSegments(use24Hour);
2424
- const fromValue = (v) => {
2425
- const m = /^(\d{1,2}):(\d{2})$/.exec(v);
2426
- if (!m) return { hour: null, minute: null, period: null };
2427
- const h24 = Number(m[1]);
2428
- return use24Hour ? { hour: h24, minute: Number(m[2]), period: null } : { hour: h24 % 12 === 0 ? 12 : h24 % 12, minute: Number(m[2]), period: h24 >= 12 ? "PM" : "AM" };
2429
- };
2430
- const [segs, setSegs] = useState(() => fromValue(value));
2431
- const buffers = useRef({});
2413
+ const [segs, setSegs] = useState(() => fromValue(value, use24Hour));
2414
+ const [buffers, setBuffers] = useState({});
2432
2415
  const lastCommitted = useRef(null);
2433
2416
  useEffect(() => {
2434
2417
  if (value === lastCommitted.current) return;
2435
- setSegs(fromValue(value));
2436
- buffers.current = {};
2418
+ setSegs(fromValue(value, use24Hour));
2419
+ setBuffers({});
2437
2420
  }, [value, use24Hour]);
2438
2421
  const segmentRefs = useRef([]);
2439
2422
  const segmentCount = specs.length + (use24Hour ? 0 : 1);
@@ -2441,6 +2424,26 @@ function TimePicker({
2441
2424
  const el = segmentRefs.current[Math.max(0, Math.min(segmentCount - 1, i))];
2442
2425
  el?.focus();
2443
2426
  };
2427
+ const focusSegmentRef = useLatest(focusSegment);
2428
+ useEffect(() => {
2429
+ if (!open) return;
2430
+ const handler = (e) => {
2431
+ if (triggerRef.current?.contains(e.target) || panelRef.current?.contains(e.target)) return;
2432
+ setOpen(false);
2433
+ };
2434
+ const keyHandler = (e) => {
2435
+ if (e.key === "Escape" && isTopLayer()) {
2436
+ setOpen(false);
2437
+ focusSegmentRef.current(0);
2438
+ }
2439
+ };
2440
+ document.addEventListener("mousedown", handler);
2441
+ document.addEventListener("keydown", keyHandler);
2442
+ return () => {
2443
+ document.removeEventListener("mousedown", handler);
2444
+ document.removeEventListener("keydown", keyHandler);
2445
+ };
2446
+ }, [open, isTopLayer, focusSegmentRef]);
2444
2447
  const commitIfComplete = (next) => {
2445
2448
  if (next.hour === null || next.minute === null) return;
2446
2449
  let h24 = next.hour;
@@ -2462,9 +2465,9 @@ function TimePicker({
2462
2465
  const applyTyped = (text) => {
2463
2466
  const parsed2 = parseTypedTime(text, use24Hour);
2464
2467
  if (!parsed2) return;
2465
- const next = fromValue(parsed2);
2468
+ const next = fromValue(parsed2, use24Hour);
2466
2469
  setSegs(next);
2467
- buffers.current = {};
2470
+ setBuffers({});
2468
2471
  commitIfComplete(next);
2469
2472
  };
2470
2473
  const onSegmentKeyDown = (e, index, spec) => {
@@ -2489,33 +2492,34 @@ function TimePicker({
2489
2492
  return;
2490
2493
  }
2491
2494
  const k = spec.key;
2495
+ const clearBuffer = () => setBuffers((b) => ({ ...b, [k]: "" }));
2492
2496
  if (key === "ArrowUp" || key === "ArrowDown") {
2493
- buffers.current[k] = "";
2497
+ clearBuffer();
2494
2498
  setSegment(k, stepValue(spec, segs[k], key === "ArrowUp" ? 1 : -1));
2495
2499
  return;
2496
2500
  }
2497
2501
  if (key === "Home") {
2498
- buffers.current[k] = "";
2502
+ clearBuffer();
2499
2503
  setSegment(k, spec.min);
2500
2504
  return;
2501
2505
  }
2502
2506
  if (key === "End") {
2503
- buffers.current[k] = "";
2507
+ clearBuffer();
2504
2508
  setSegment(k, spec.max);
2505
2509
  return;
2506
2510
  }
2507
2511
  if (key === "Backspace" || key === "Delete") {
2508
- if (segs[k] === null && !buffers.current[k]) {
2512
+ if (segs[k] === null && !buffers[k]) {
2509
2513
  focusSegment(index - 1);
2510
2514
  return;
2511
2515
  }
2512
- buffers.current[k] = "";
2516
+ clearBuffer();
2513
2517
  setSegment(k, null, false);
2514
2518
  return;
2515
2519
  }
2516
2520
  if (/^\d$/.test(key)) {
2517
- const r = typeDigit(spec, buffers.current[k] ?? "", key);
2518
- buffers.current[k] = r.advance ? "" : r.buffer;
2521
+ const r = typeDigit(spec, buffers[k] ?? "", key);
2522
+ setBuffers((b) => ({ ...b, [k]: r.advance ? "" : r.buffer }));
2519
2523
  setSegment(k, r.value);
2520
2524
  if (r.advance) focusSegment(index + 1);
2521
2525
  return;
@@ -2526,7 +2530,7 @@ function TimePicker({
2526
2530
  }
2527
2531
  };
2528
2532
  const shown = (k, spec) => {
2529
- const b = buffers.current[k];
2533
+ const b = buffers[k];
2530
2534
  if (b && segs[k] === null) return b;
2531
2535
  return segs[k] === null ? spec.placeholder : pad2(segs[k]);
2532
2536
  };
@@ -2585,7 +2589,7 @@ function TimePicker({
2585
2589
  "aria-valuetext": segs[k] === null ? "empty" : pad2(segs[k]),
2586
2590
  onKeyDown: (e) => onSegmentKeyDown(e, i, spec),
2587
2591
  onClick: (e) => e.stopPropagation(),
2588
- className: cn("rounded-[var(--ds-radius-sm)] px-[2px] outline-none focus:bg-[var(--ds-accent-subtle)] focus:text-[var(--ds-accent-primary)] cursor-text", segs[k] === null && !buffers.current[k] && "text-[var(--ds-text-muted)]"),
2592
+ className: cn("rounded-[var(--ds-radius-sm)] px-[2px] outline-none focus:bg-[var(--ds-accent-subtle)] focus:text-[var(--ds-accent-primary)] cursor-text", segs[k] === null && !buffers[k] && "text-[var(--ds-text-muted)]"),
2589
2593
  children: shown(k, spec)
2590
2594
  }
2591
2595
  )