twico-ui 1.7.0 → 1.8.1

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.mjs CHANGED
@@ -11,17 +11,21 @@ function useScopedStyles(id, css) {
11
11
  const nonce = React36.useContext(NonceContext);
12
12
  React36.useInsertionEffect(() => {
13
13
  if (SUPPORTS_STYLE_HOIST) return;
14
- if (typeof document === "undefined" || document.getElementById(id)) return;
15
- const el = document.createElement("style");
16
- el.id = id;
17
- if (nonce) {
18
- el.setAttribute("nonce", nonce);
19
- el.nonce = nonce;
20
- }
21
- el.textContent = css;
22
- document.head.appendChild(el);
14
+ if (typeof document === "undefined") return;
15
+ if (!css) return;
16
+ let el = document.getElementById(id);
17
+ if (!el) {
18
+ el = document.createElement("style");
19
+ el.id = id;
20
+ if (nonce) {
21
+ el.setAttribute("nonce", nonce);
22
+ el.nonce = nonce;
23
+ }
24
+ document.head.appendChild(el);
25
+ }
26
+ if (el.textContent !== css) el.textContent = css;
23
27
  }, [id, css, nonce]);
24
- return SUPPORTS_STYLE_HOIST ? React36.createElement("style", { href: id, precedence: "twc-ui", nonce }, css) : null;
28
+ return SUPPORTS_STYLE_HOIST && css ? React36.createElement("style", { href: id, precedence: "twc-ui", nonce }, css) : null;
25
29
  }
26
30
 
27
31
  // components/TwicoProvider.jsx
@@ -207,10 +211,13 @@ function Portal({ children }) {
207
211
  const render = usePortal();
208
212
  return render(children);
209
213
  }
214
+ function encodeScriptLiteral(value) {
215
+ return JSON.stringify(value).replace(/[<>&\u2028\u2029]/g, (c) => "\\u" + c.charCodeAt(0).toString(16).padStart(4, "0"));
216
+ }
210
217
  function getColorSchemeScript({ storageKey = "twico-theme", attribute = "class", defaultScheme } = {}) {
211
- const k = JSON.stringify(storageKey);
212
- const a = JSON.stringify(attribute);
213
- const d = defaultScheme === "dark" || defaultScheme === "light" ? JSON.stringify(defaultScheme) : "null";
218
+ const k = encodeScriptLiteral(storageKey);
219
+ const a = encodeScriptLiteral(attribute);
220
+ const d = defaultScheme === "dark" || defaultScheme === "light" ? encodeScriptLiteral(defaultScheme) : "null";
214
221
  return `(function(){try{var k=${k},a=${a},d=${d},t=localStorage.getItem(k);if(t!=='light'){if(t!=='dark'){t=d?d:(window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light');}}if(a==='class'){document.documentElement.classList.toggle('dark',t==='dark');}else{document.documentElement.setAttribute(a,t);}}catch(e){}})();`;
215
222
  }
216
223
  function ColorSchemeScript({ storageKey = "twico-theme", attribute = "class", nonce, defaultScheme }) {
@@ -1193,6 +1200,122 @@ function Button({
1193
1200
  }
1194
1201
  );
1195
1202
  }
1203
+ var UNITLESS = /* @__PURE__ */ new Set([
1204
+ "animationIterationCount",
1205
+ "aspectRatio",
1206
+ "borderImageOutset",
1207
+ "borderImageSlice",
1208
+ "borderImageWidth",
1209
+ "boxFlex",
1210
+ "boxFlexGroup",
1211
+ "boxOrdinalGroup",
1212
+ "columnCount",
1213
+ "columns",
1214
+ "flex",
1215
+ "flexGrow",
1216
+ "flexShrink",
1217
+ "flexPositive",
1218
+ "flexNegative",
1219
+ "flexOrder",
1220
+ "gridArea",
1221
+ "gridRow",
1222
+ "gridRowEnd",
1223
+ "gridRowSpan",
1224
+ "gridRowStart",
1225
+ "gridColumn",
1226
+ "gridColumnEnd",
1227
+ "gridColumnSpan",
1228
+ "gridColumnStart",
1229
+ "fontWeight",
1230
+ "lineClamp",
1231
+ "lineHeight",
1232
+ "opacity",
1233
+ "order",
1234
+ "orphans",
1235
+ "tabSize",
1236
+ "widows",
1237
+ "zIndex",
1238
+ "zoom",
1239
+ "fillOpacity",
1240
+ "floodOpacity",
1241
+ "stopOpacity",
1242
+ "strokeDasharray",
1243
+ "strokeDashoffset",
1244
+ "strokeMiterlimit",
1245
+ "strokeOpacity",
1246
+ "strokeWidth",
1247
+ "scale"
1248
+ ]);
1249
+ var isObject = (v) => v != null && typeof v === "object" && !Array.isArray(v);
1250
+ var isNestedKey = (k, v) => isObject(v) || k.charCodeAt(0) === 38 || k.charCodeAt(0) === 64 || k.charCodeAt(0) === 58;
1251
+ function kebab(prop) {
1252
+ if (prop.startsWith("--")) return prop;
1253
+ return prop.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
1254
+ }
1255
+ function cssValue(key, value) {
1256
+ if (typeof value !== "number") return String(value);
1257
+ if (value === 0 || key.startsWith("--") || UNITLESS.has(key)) return String(value);
1258
+ return `${value}px`;
1259
+ }
1260
+ function resolveSelector(key, scope) {
1261
+ if (key.includes("&")) return key.replace(/&/g, scope);
1262
+ if (key.charCodeAt(0) === 58) return `${scope}${key}`;
1263
+ return `${scope} ${key}`;
1264
+ }
1265
+ function compileSx(obj, selector) {
1266
+ if (!isObject(obj)) return "";
1267
+ let decls = "";
1268
+ let rules = "";
1269
+ for (const key of Object.keys(obj)) {
1270
+ const value = obj[key];
1271
+ if (isNestedKey(key, value)) {
1272
+ if (!isObject(value)) continue;
1273
+ if (key.charCodeAt(0) === 64) {
1274
+ const inner = compileSx(value, selector);
1275
+ if (inner) rules += `${key} { ${inner.trim()} }
1276
+ `;
1277
+ } else {
1278
+ rules += compileSx(value, resolveSelector(key, selector));
1279
+ }
1280
+ } else {
1281
+ decls += `${kebab(key)}: ${cssValue(key, value)}; `;
1282
+ }
1283
+ }
1284
+ let out = "";
1285
+ if (decls) out += `${selector} { ${decls.trimEnd()} }
1286
+ `;
1287
+ out += rules;
1288
+ return out;
1289
+ }
1290
+ function buildSx(sx, uid) {
1291
+ if (!isObject(sx)) return { flatStyle: void 0, css: "" };
1292
+ const scope = `[data-twc-sx="${uid}"]`;
1293
+ let flatStyle;
1294
+ let css = "";
1295
+ for (const key of Object.keys(sx)) {
1296
+ const value = sx[key];
1297
+ if (isNestedKey(key, value)) {
1298
+ if (!isObject(value)) continue;
1299
+ if (key.charCodeAt(0) === 64) {
1300
+ const inner = compileSx(value, scope);
1301
+ if (inner) css += `${key} { ${inner.trim()} }
1302
+ `;
1303
+ } else {
1304
+ css += compileSx(value, resolveSelector(key, scope));
1305
+ }
1306
+ } else {
1307
+ (flatStyle || (flatStyle = {}))[key] = value;
1308
+ }
1309
+ }
1310
+ return { flatStyle, css };
1311
+ }
1312
+ function useSx(sx) {
1313
+ const rawId = React36__default.useId();
1314
+ const uid = `sx${rawId.replace(/[^a-zA-Z0-9]/g, "")}`;
1315
+ const { flatStyle, css } = React36__default.useMemo(() => buildSx(sx, uid), [sx, uid]);
1316
+ const styleNode = useScopedStyles(`twc-sx-${uid}`, css);
1317
+ return { flatStyle, styleNode, sxAttr: css ? uid : void 0 };
1318
+ }
1196
1319
  var CARD_CSS = `
1197
1320
  .twc-card {
1198
1321
  display: flex; flex-direction: column;
@@ -1232,10 +1355,13 @@ function Card({
1232
1355
  padding = "md",
1233
1356
  interactive = false,
1234
1357
  fullHeight = false,
1358
+ sx,
1235
1359
  className = "",
1360
+ style,
1236
1361
  ...rest
1237
1362
  }) {
1238
1363
  const __twcStyles = useScopedStyles("twc-card-styles", CARD_CSS);
1364
+ const { flatStyle, styleNode, sxAttr } = useSx(sx);
1239
1365
  return /* @__PURE__ */ jsxs(
1240
1366
  "div",
1241
1367
  {
@@ -1244,9 +1370,12 @@ function Card({
1244
1370
  "data-pad": padding,
1245
1371
  "data-interactive": interactive || void 0,
1246
1372
  "data-full": fullHeight || void 0,
1373
+ "data-twc-sx": sxAttr,
1374
+ style: style || flatStyle ? { ...style, ...flatStyle } : void 0,
1247
1375
  ...rest,
1248
1376
  children: [
1249
1377
  __twcStyles,
1378
+ styleNode,
1250
1379
  title || subtitle || actions ? /* @__PURE__ */ jsxs("div", { className: "twc-card__header", children: [
1251
1380
  /* @__PURE__ */ jsxs("div", { className: "twc-card__heading", children: [
1252
1381
  title ? /* @__PURE__ */ jsx("div", { className: "twc-card__title", children: title }) : null,
@@ -1665,6 +1794,10 @@ var COLORPICKER_CSS = `
1665
1794
  .twc-cp__hue { position: relative; height: 12px; margin-top: var(--space-3); border-radius: var(--radius-full); cursor: pointer; touch-action: none;
1666
1795
  background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%); }
1667
1796
  .twc-cp__hue-knob { position: absolute; top: 50%; width: 16px; height: 16px; border-radius: var(--radius-full); background: #fff; box-shadow: var(--shadow-sm), inset 0 0 0 1px rgb(0 0 0 / 0.2); transform: translate(-50%, -50%); pointer-events: none; }
1797
+ .twc-cp__alpha { position: relative; height: 12px; margin-top: var(--space-2); border-radius: var(--radius-full); cursor: pointer; touch-action: none;
1798
+ background-image: linear-gradient(to right, transparent, var(--_solid, #000)), conic-gradient(#c8c8c8 0 25%, #fff 0 50%, #c8c8c8 0 75%, #fff 0);
1799
+ background-size: 100% 100%, 10px 10px; }
1800
+ .twc-cp__alpha:focus-visible { outline: none; box-shadow: var(--ring); }
1668
1801
  .twc-cp__foot { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-3); }
1669
1802
  .twc-cp__hex { flex: 1; min-width: 0; height: 32px; padding: 0 8px; font-family: var(--font-mono); font-size: var(--text-xs); text-transform: uppercase;
1670
1803
  color: var(--color-text); background: var(--color-surface); border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-sm); outline: none; }
@@ -1702,11 +1835,17 @@ function rgbToHex(r, g, b) {
1702
1835
  function hsvToHex(h, s, v) {
1703
1836
  return rgbToHex(...hsvToRgb(h, s, v));
1704
1837
  }
1838
+ function hsvaToHex8(h, s, v, a) {
1839
+ return rgbToHex(...hsvToRgb(h, s, v)) + Math.round(Math.min(1, Math.max(0, a)) * 255).toString(16).padStart(2, "0");
1840
+ }
1705
1841
  function hexToHsv(hex) {
1706
- const m = /^#?([0-9a-f]{6})$/i.exec(hex || "");
1842
+ let str = String(hex || "").replace(/^#/, "");
1843
+ if (/^[0-9a-f]{3}$/i.test(str)) str = str.split("").map((c) => c + c).join("");
1844
+ const m = /^([0-9a-f]{6})([0-9a-f]{2})?$/i.exec(str);
1707
1845
  if (!m) return null;
1708
1846
  const int = parseInt(m[1], 16);
1709
1847
  const r = (int >> 16) / 255, g = (int >> 8 & 255) / 255, b = (int & 255) / 255;
1848
+ const a = m[2] != null ? parseInt(m[2], 16) / 255 : 1;
1710
1849
  const max = Math.max(r, g, b), min = Math.min(r, g, b), d = max - min;
1711
1850
  let h = 0;
1712
1851
  if (d) {
@@ -1716,7 +1855,7 @@ function hexToHsv(hex) {
1716
1855
  h *= 60;
1717
1856
  if (h < 0) h += 360;
1718
1857
  }
1719
- return { h, s: max ? d / max * 100 : 0, v: max * 100 };
1858
+ return { h, s: max ? d / max * 100 : 0, v: max * 100, a };
1720
1859
  }
1721
1860
  var DEFAULT_PRESETS = ["#6366F1", "#0EA5E9", "#14B8A6", "#22C55E", "#F59E0B", "#F43F5E", "#8B5CF6", "#0F172A"];
1722
1861
  function ColorPicker({
@@ -1728,6 +1867,7 @@ function ColorPicker({
1728
1867
  defaultValue = "#6366F1",
1729
1868
  presets = DEFAULT_PRESETS,
1730
1869
  disabled = false,
1870
+ alpha = false,
1731
1871
  tone = "primary",
1732
1872
  onChange,
1733
1873
  className = "",
@@ -1737,16 +1877,18 @@ function ColorPicker({
1737
1877
  const __twcStyles1 = useScopedStyles("twc-colorpicker-styles", COLORPICKER_CSS);
1738
1878
  const [internal, setInternal] = React36__default.useState(defaultValue);
1739
1879
  const hex = (value !== void 0 ? value : internal) || "#000000";
1880
+ const hexRe = alpha ? /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i : /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
1740
1881
  const lastValidRef = React36__default.useRef(hex);
1741
- if (/^#[0-9a-f]{6}$/i.test(hex)) lastValidRef.current = hex;
1882
+ if (hexRe.test(hex)) lastValidRef.current = hex;
1742
1883
  const [open, setOpen] = React36__default.useState(false);
1743
- const [hsv, setHsv] = React36__default.useState(() => hexToHsv(hex) || { h: 239, s: 60, v: 94 });
1884
+ const [hsv, setHsv] = React36__default.useState(() => hexToHsv(hex) || { h: 239, s: 60, v: 94, a: 1 });
1744
1885
  const [coords, setCoords] = React36__default.useState(null);
1745
1886
  const wrapRef = React36__default.useRef(null);
1746
1887
  const triggerRef = React36__default.useRef(null);
1747
1888
  const popRef = React36__default.useRef(null);
1748
1889
  const areaRef = React36__default.useRef(null);
1749
1890
  const hueRef = React36__default.useRef(null);
1891
+ const alphaRef = React36__default.useRef(null);
1750
1892
  const POP_W = 240;
1751
1893
  const place = React36__default.useCallback(() => {
1752
1894
  const el = triggerRef.current;
@@ -1799,10 +1941,10 @@ function ColorPicker({
1799
1941
  }, [open]);
1800
1942
  useFocusTrap(popRef, open && !!coords, { initialFocus: areaRef });
1801
1943
  const commit = (next) => {
1802
- const h = hsvToHex(next.h, next.s, next.v);
1944
+ const out = alpha ? hsvaToHex8(next.h, next.s, next.v, next.a ?? 1) : hsvToHex(next.h, next.s, next.v);
1803
1945
  setHsv(next);
1804
- if (value === void 0) setInternal(h);
1805
- onChange?.(h);
1946
+ if (value === void 0) setInternal(out);
1947
+ onChange?.(out);
1806
1948
  };
1807
1949
  const dragArea = (e) => {
1808
1950
  const r = areaRef.current.getBoundingClientRect();
@@ -1846,6 +1988,21 @@ function ColorPicker({
1846
1988
  e.preventDefault();
1847
1989
  commit({ ...hsv, h });
1848
1990
  };
1991
+ const dragAlpha = (e) => {
1992
+ const r = alphaRef.current.getBoundingClientRect();
1993
+ const a = Math.min(1, Math.max(0, (e.clientX - r.left) / r.width));
1994
+ commit({ ...hsv, a });
1995
+ };
1996
+ const onAlphaKeyDown = (e) => {
1997
+ let a = hsv.a ?? 1;
1998
+ if (e.key === "ArrowRight" || e.key === "ArrowUp") a = Math.min(1, a + 0.01);
1999
+ else if (e.key === "ArrowLeft" || e.key === "ArrowDown") a = Math.max(0, a - 0.01);
2000
+ else if (e.key === "Home") a = 0;
2001
+ else if (e.key === "End") a = 1;
2002
+ else return;
2003
+ e.preventDefault();
2004
+ commit({ ...hsv, a: Math.round(a * 100) / 100 });
2005
+ };
1849
2006
  return /* @__PURE__ */ jsxs("div", { className: `twc-cp twc-field ${className}`, ref: wrapRef, ...rest, children: [
1850
2007
  __twcStyles0,
1851
2008
  __twcStyles1,
@@ -1932,6 +2089,23 @@ function ColorPicker({
1932
2089
  children: /* @__PURE__ */ jsx("span", { className: "twc-cp__hue-knob", style: { left: `${hsv.h / 360 * 100}%` } })
1933
2090
  }
1934
2091
  ),
2092
+ alpha ? /* @__PURE__ */ jsx(
2093
+ "div",
2094
+ {
2095
+ className: "twc-cp__alpha",
2096
+ ref: alphaRef,
2097
+ onPointerDown: startDrag(dragAlpha),
2098
+ role: "slider",
2099
+ tabIndex: 0,
2100
+ "aria-label": "Alpha",
2101
+ "aria-valuemin": 0,
2102
+ "aria-valuemax": 100,
2103
+ "aria-valuenow": Math.round((hsv.a ?? 1) * 100),
2104
+ onKeyDown: onAlphaKeyDown,
2105
+ style: { "--_solid": hsvToHex(hsv.h, hsv.s, hsv.v) },
2106
+ children: /* @__PURE__ */ jsx("span", { className: "twc-cp__hue-knob", style: { left: `${(hsv.a ?? 1) * 100}%` } })
2107
+ }
2108
+ ) : null,
1935
2109
  /* @__PURE__ */ jsxs("div", { className: "twc-cp__foot", children: [
1936
2110
  /* @__PURE__ */ jsx("span", { className: "twc-cp__swatch", style: { background: hex } }),
1937
2111
  /* @__PURE__ */ jsx(
@@ -1940,10 +2114,10 @@ function ColorPicker({
1940
2114
  className: "twc-cp__hex",
1941
2115
  "data-tone": tone,
1942
2116
  value: hex,
1943
- maxLength: 7,
2117
+ maxLength: alpha ? 9 : 7,
1944
2118
  "aria-label": "Hex color",
1945
- "aria-invalid": !/^#[0-9a-f]{6}$/i.test(hex) || void 0,
1946
- "data-invalid": !/^#[0-9a-f]{6}$/i.test(hex) || void 0,
2119
+ "aria-invalid": !hexRe.test(hex) || void 0,
2120
+ "data-invalid": !hexRe.test(hex) || void 0,
1947
2121
  onChange: (e) => {
1948
2122
  let v = e.target.value;
1949
2123
  if (!v.startsWith("#")) v = "#" + v;
@@ -1953,7 +2127,7 @@ function ColorPicker({
1953
2127
  if (p) setHsv(p);
1954
2128
  },
1955
2129
  onBlur: () => {
1956
- if (/^#[0-9a-f]{6}$/i.test(hex)) return;
2130
+ if (hexRe.test(hex)) return;
1957
2131
  const revert = lastValidRef.current;
1958
2132
  if (revert === hex) return;
1959
2133
  if (value === void 0) setInternal(revert);
@@ -2111,6 +2285,8 @@ function Combobox({
2111
2285
  loading = false,
2112
2286
  emptyText = "No results found",
2113
2287
  name,
2288
+ virtualized = false,
2289
+ overscan = 8,
2114
2290
  id,
2115
2291
  className = "",
2116
2292
  onFocus,
@@ -2129,6 +2305,8 @@ function Combobox({
2129
2305
  const [query, setQuery] = React36__default.useState("");
2130
2306
  const [active, setActive] = React36__default.useState(0);
2131
2307
  const [coords, setCoords] = React36__default.useState(null);
2308
+ const [scrollTop, setScrollTop] = React36__default.useState(0);
2309
+ const [listH, setListH] = React36__default.useState(260);
2132
2310
  const wrapRef = React36__default.useRef(null);
2133
2311
  const controlRef = React36__default.useRef(null);
2134
2312
  const inputRef = React36__default.useRef(null);
@@ -2141,6 +2319,26 @@ function Combobox({
2141
2319
  return groups.map((g) => ({ group: g.group, options: fn ? g.options.filter((o) => fn(o, q)) : g.options })).filter((g) => g.options.length);
2142
2320
  }, [groups, query, filter]);
2143
2321
  const visible = React36__default.useMemo(() => fGroups.flatMap((g) => g.options), [fGroups]);
2322
+ const rowH = React36__default.useMemo(() => visible.some((o) => o.description) ? 48 : 36, [visible]);
2323
+ const GROUP_H = 30;
2324
+ const { rows, totalH } = React36__default.useMemo(() => {
2325
+ if (!virtualized) return { rows: [], totalH: 0 };
2326
+ const rows2 = [];
2327
+ let off = 0;
2328
+ let optIdx = -1;
2329
+ fGroups.forEach((g, gi) => {
2330
+ if (g.group) {
2331
+ rows2.push({ kind: "group", gi, label: g.group, top: off, h: GROUP_H });
2332
+ off += GROUP_H;
2333
+ }
2334
+ g.options.forEach((o) => {
2335
+ optIdx += 1;
2336
+ rows2.push({ kind: "option", o, idx: optIdx, top: off, h: rowH });
2337
+ off += rowH;
2338
+ });
2339
+ });
2340
+ return { rows: rows2, totalH: off };
2341
+ }, [virtualized, fGroups, rowH]);
2144
2342
  const nextEnabled = (from, dir) => {
2145
2343
  let i = from + dir;
2146
2344
  while (i >= 0 && i < visible.length) {
@@ -2194,8 +2392,27 @@ function Combobox({
2194
2392
  setActive(idx >= 0 ? idx : 0);
2195
2393
  }, [open]);
2196
2394
  React36__default.useEffect(() => {
2197
- if (open) ensureVisible(listRef.current, activeRef.current);
2198
- }, [active, open]);
2395
+ if (!open || !virtualized) return;
2396
+ const el = listRef.current;
2397
+ if (el && el.clientHeight) setListH(el.clientHeight);
2398
+ }, [open, virtualized]);
2399
+ React36__default.useEffect(() => {
2400
+ if (!open) return;
2401
+ if (!virtualized) {
2402
+ ensureVisible(listRef.current, activeRef.current);
2403
+ return;
2404
+ }
2405
+ const el = listRef.current;
2406
+ if (!el) return;
2407
+ const row = rows.find((r) => r.kind === "option" && r.idx === active);
2408
+ if (!row) return;
2409
+ const vh = el.clientHeight || listH;
2410
+ let next = el.scrollTop;
2411
+ if (row.top < el.scrollTop) next = row.top;
2412
+ else if (row.top + row.h > el.scrollTop + vh) next = row.top + row.h - vh;
2413
+ if (next !== el.scrollTop) el.scrollTop = next;
2414
+ setScrollTop(next);
2415
+ }, [active, open, virtualized, rows, listH]);
2199
2416
  function openMenu() {
2200
2417
  if (disabled) return;
2201
2418
  setQuery("");
@@ -2240,45 +2457,60 @@ function Combobox({
2240
2457
  const activeId = open && visible[active] ? optionId(active) : void 0;
2241
2458
  const descId = `${fieldId}-desc`;
2242
2459
  const describedBy = error || hint ? descId : void 0;
2460
+ const renderOption = (o, idx) => {
2461
+ const isSel = o.value === current;
2462
+ return /* @__PURE__ */ jsxs(
2463
+ "button",
2464
+ {
2465
+ id: optionId(idx),
2466
+ type: "button",
2467
+ className: "twc-opt",
2468
+ role: "option",
2469
+ "aria-selected": isSel,
2470
+ ref: idx === active ? activeRef : null,
2471
+ disabled: o.disabled || void 0,
2472
+ "aria-disabled": o.disabled || void 0,
2473
+ "data-disabled": o.disabled || void 0,
2474
+ "data-selected": isSel || void 0,
2475
+ "data-active": idx === active || void 0,
2476
+ onMouseEnter: () => {
2477
+ if (!o.disabled) setActive(idx);
2478
+ },
2479
+ onMouseDown: (e) => e.preventDefault(),
2480
+ onClick: () => commit(o.value),
2481
+ children: [
2482
+ /* @__PURE__ */ jsxs("span", { className: "twc-opt__main", children: [
2483
+ /* @__PURE__ */ jsx("span", { className: "twc-opt__label", children: o.label }),
2484
+ o.description ? /* @__PURE__ */ jsx("span", { className: "twc-opt__desc", children: o.description }) : null
2485
+ ] }),
2486
+ isSel ? /* @__PURE__ */ jsx("span", { className: "twc-opt__check", "aria-hidden": "true", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.6", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) }) }) : null
2487
+ ]
2488
+ },
2489
+ o.value
2490
+ );
2491
+ };
2492
+ let vTop = 0, vBottom = 0, vShown = rows;
2493
+ if (virtualized && rows.length) {
2494
+ const winTop = scrollTop - overscan * rowH;
2495
+ const winBottom = scrollTop + listH + overscan * rowH;
2496
+ vShown = rows.filter((r) => r.top + r.h > winTop && r.top < winBottom);
2497
+ vTop = vShown.length ? vShown[0].top : 0;
2498
+ vBottom = vShown.length ? totalH - (vShown[vShown.length - 1].top + vShown[vShown.length - 1].h) : totalH;
2499
+ }
2243
2500
  let counter = -1;
2244
- const popInner = /* @__PURE__ */ jsx("div", { className: "twc-pop__list", ref: listRef, children: loading ? /* @__PURE__ */ jsxs("div", { className: "twc-pop__loading", role: "status", children: [
2501
+ const popInner = /* @__PURE__ */ jsx("div", { className: "twc-pop__list", ref: listRef, onScroll: virtualized ? (e) => setScrollTop(e.currentTarget.scrollTop) : void 0, children: loading ? /* @__PURE__ */ jsxs("div", { className: "twc-pop__loading", role: "status", children: [
2245
2502
  /* @__PURE__ */ jsx("span", { className: "twc-pop__spinner", "aria-hidden": "true" }),
2246
2503
  "Loading\u2026"
2247
- ] }) : visible.length === 0 ? /* @__PURE__ */ jsx("div", { className: "twc-pop__empty", children: emptyText }) : fGroups.map((g, gi) => /* @__PURE__ */ jsxs(React36__default.Fragment, { children: [
2504
+ ] }) : visible.length === 0 ? /* @__PURE__ */ jsx("div", { className: "twc-pop__empty", children: emptyText }) : virtualized ? /* @__PURE__ */ jsxs(Fragment, { children: [
2505
+ vTop > 0 ? /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { height: vTop } }) : null,
2506
+ vShown.map((r) => r.kind === "group" ? /* @__PURE__ */ jsx("div", { className: "twc-pop__group", children: r.label }, `g${r.gi}`) : renderOption(r.o, r.idx)),
2507
+ vBottom > 0 ? /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { height: vBottom } }) : null
2508
+ ] }) : fGroups.map((g, gi) => /* @__PURE__ */ jsxs(React36__default.Fragment, { children: [
2248
2509
  g.group ? /* @__PURE__ */ jsx("div", { className: "twc-pop__group", children: g.group }) : null,
2249
2510
  g.options.map((o) => {
2250
2511
  counter += 1;
2251
2512
  const idx = counter;
2252
- const isSel = o.value === current;
2253
- return /* @__PURE__ */ jsxs(
2254
- "button",
2255
- {
2256
- id: optionId(idx),
2257
- type: "button",
2258
- className: "twc-opt",
2259
- role: "option",
2260
- "aria-selected": isSel,
2261
- ref: idx === active ? activeRef : null,
2262
- disabled: o.disabled || void 0,
2263
- "aria-disabled": o.disabled || void 0,
2264
- "data-disabled": o.disabled || void 0,
2265
- "data-selected": isSel || void 0,
2266
- "data-active": idx === active || void 0,
2267
- onMouseEnter: () => {
2268
- if (!o.disabled) setActive(idx);
2269
- },
2270
- onMouseDown: (e) => e.preventDefault(),
2271
- onClick: () => commit(o.value),
2272
- children: [
2273
- /* @__PURE__ */ jsxs("span", { className: "twc-opt__main", children: [
2274
- /* @__PURE__ */ jsx("span", { className: "twc-opt__label", children: o.label }),
2275
- o.description ? /* @__PURE__ */ jsx("span", { className: "twc-opt__desc", children: o.description }) : null
2276
- ] }),
2277
- isSel ? /* @__PURE__ */ jsx("span", { className: "twc-opt__check", "aria-hidden": "true", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.6", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) }) }) : null
2278
- ]
2279
- },
2280
- o.value
2281
- );
2513
+ return renderOption(o, idx);
2282
2514
  })
2283
2515
  ] }, gi)) });
2284
2516
  const RD = { createPortal: typeof createPortal === "function" ? createPortal : typeof window !== "undefined" && window.ReactDOM && window.ReactDOM.createPortal };
@@ -2875,6 +3107,8 @@ function Select({
2875
3107
  loading = false,
2876
3108
  emptyText = "No results found",
2877
3109
  name,
3110
+ virtualized = false,
3111
+ overscan = 8,
2878
3112
  id,
2879
3113
  className = "",
2880
3114
  onClick,
@@ -2895,6 +3129,8 @@ function Select({
2895
3129
  const [active, setActive] = React36__default.useState(0);
2896
3130
  const [coords, setCoords] = React36__default.useState(null);
2897
3131
  const [query, setQuery] = React36__default.useState("");
3132
+ const [scrollTop, setScrollTop] = React36__default.useState(0);
3133
+ const [listH, setListH] = React36__default.useState(260);
2898
3134
  const wrapRef = React36__default.useRef(null);
2899
3135
  const triggerRef = React36__default.useRef(null);
2900
3136
  const popRef = React36__default.useRef(null);
@@ -2908,6 +3144,26 @@ function Select({
2908
3144
  return groups.map((g) => ({ group: g.group, options: g.options.filter((o) => o.label.toLowerCase().includes(q) || o.description && o.description.toLowerCase().includes(q)) })).filter((g) => g.options.length);
2909
3145
  }, [groups, query]);
2910
3146
  const visible = React36__default.useMemo(() => fGroups.flatMap((g) => g.options), [fGroups]);
3147
+ const rowH = React36__default.useMemo(() => visible.some((o) => o.description) ? 48 : 36, [visible]);
3148
+ const GROUP_H = 30;
3149
+ const { rows, totalH } = React36__default.useMemo(() => {
3150
+ if (!virtualized) return { rows: [], totalH: 0 };
3151
+ const rows2 = [];
3152
+ let off = 0;
3153
+ let optIdx = -1;
3154
+ fGroups.forEach((g, gi) => {
3155
+ if (g.group) {
3156
+ rows2.push({ kind: "group", gi, label: g.group, top: off, h: GROUP_H });
3157
+ off += GROUP_H;
3158
+ }
3159
+ g.options.forEach((o) => {
3160
+ optIdx += 1;
3161
+ rows2.push({ kind: "option", o, idx: optIdx, top: off, h: rowH });
3162
+ off += rowH;
3163
+ });
3164
+ });
3165
+ return { rows: rows2, totalH: off };
3166
+ }, [virtualized, fGroups, rowH]);
2911
3167
  const selected = flat.find((o) => o.value === current);
2912
3168
  const nextEnabled = (from, dir) => {
2913
3169
  let i = from + dir;
@@ -2964,8 +3220,27 @@ function Select({
2964
3220
  setActive(0);
2965
3221
  }, [query]);
2966
3222
  React36__default.useEffect(() => {
2967
- if (open) ensureVisible2(listRef.current, activeRef.current);
2968
- }, [active, open]);
3223
+ if (!open || !virtualized) return;
3224
+ const el = listRef.current;
3225
+ if (el && el.clientHeight) setListH(el.clientHeight);
3226
+ }, [open, virtualized, render]);
3227
+ React36__default.useEffect(() => {
3228
+ if (!open) return;
3229
+ if (!virtualized) {
3230
+ ensureVisible2(listRef.current, activeRef.current);
3231
+ return;
3232
+ }
3233
+ const el = listRef.current;
3234
+ if (!el) return;
3235
+ const row = rows.find((r) => r.kind === "option" && r.idx === active);
3236
+ if (!row) return;
3237
+ const vh = el.clientHeight || listH;
3238
+ let next = el.scrollTop;
3239
+ if (row.top < el.scrollTop) next = row.top;
3240
+ else if (row.top + row.h > el.scrollTop + vh) next = row.top + row.h - vh;
3241
+ if (next !== el.scrollTop) el.scrollTop = next;
3242
+ setScrollTop(next);
3243
+ }, [active, open, virtualized, rows, listH]);
2969
3244
  React36__default.useEffect(() => {
2970
3245
  if (open) {
2971
3246
  setRender(true);
@@ -3039,6 +3314,45 @@ function Select({
3039
3314
  const activeId = open && visible[active] ? optionId(active) : void 0;
3040
3315
  const descId = `${fieldId}-desc`;
3041
3316
  const describedBy = error || hint ? descId : void 0;
3317
+ const renderOption = (o, idx) => {
3318
+ const isSel = o.value === current;
3319
+ return /* @__PURE__ */ jsxs(
3320
+ "button",
3321
+ {
3322
+ id: optionId(idx),
3323
+ type: "button",
3324
+ className: "twc-opt",
3325
+ role: "option",
3326
+ "aria-selected": isSel,
3327
+ ref: idx === active ? activeRef : null,
3328
+ disabled: o.disabled || void 0,
3329
+ "aria-disabled": o.disabled || void 0,
3330
+ "data-disabled": o.disabled || void 0,
3331
+ "data-selected": isSel || void 0,
3332
+ "data-active": idx === active || void 0,
3333
+ onMouseEnter: () => {
3334
+ if (!o.disabled) setActive(idx);
3335
+ },
3336
+ onClick: () => commit(o.value),
3337
+ children: [
3338
+ /* @__PURE__ */ jsxs("span", { className: "twc-opt__main", children: [
3339
+ /* @__PURE__ */ jsx("span", { className: "twc-opt__label", children: o.label }),
3340
+ o.description ? /* @__PURE__ */ jsx("span", { className: "twc-opt__desc", children: o.description }) : null
3341
+ ] }),
3342
+ isSel ? /* @__PURE__ */ jsx("span", { className: "twc-opt__check", "aria-hidden": "true", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.6", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) }) }) : null
3343
+ ]
3344
+ },
3345
+ o.value
3346
+ );
3347
+ };
3348
+ let vTop = 0, vBottom = 0, vShown = rows;
3349
+ if (virtualized && rows.length) {
3350
+ const winTop = scrollTop - overscan * rowH;
3351
+ const winBottom = scrollTop + listH + overscan * rowH;
3352
+ vShown = rows.filter((r) => r.top + r.h > winTop && r.top < winBottom);
3353
+ vTop = vShown.length ? vShown[0].top : 0;
3354
+ vBottom = vShown.length ? totalH - (vShown[vShown.length - 1].top + vShown[vShown.length - 1].h) : totalH;
3355
+ }
3042
3356
  let counter = -1;
3043
3357
  const popInner = /* @__PURE__ */ jsxs(Fragment, { children: [
3044
3358
  showSearch ? /* @__PURE__ */ jsxs("div", { className: "twc-pop__search", children: [
@@ -3066,43 +3380,19 @@ function Select({
3066
3380
  searchRef.current?.focus();
3067
3381
  }, children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }) }) : null
3068
3382
  ] }) : null,
3069
- /* @__PURE__ */ jsx("div", { className: "twc-pop__list", ref: listRef, children: loading ? /* @__PURE__ */ jsxs("div", { className: "twc-pop__loading", role: "status", children: [
3383
+ /* @__PURE__ */ jsx("div", { className: "twc-pop__list", ref: listRef, onScroll: virtualized ? (e) => setScrollTop(e.currentTarget.scrollTop) : void 0, children: loading ? /* @__PURE__ */ jsxs("div", { className: "twc-pop__loading", role: "status", children: [
3070
3384
  /* @__PURE__ */ jsx("span", { className: "twc-pop__spinner", "aria-hidden": "true" }),
3071
3385
  "Loading\u2026"
3072
- ] }) : visible.length === 0 ? /* @__PURE__ */ jsx("div", { className: "twc-pop__empty", children: emptyText }) : fGroups.map((g, gi) => /* @__PURE__ */ jsxs(React36__default.Fragment, { children: [
3386
+ ] }) : visible.length === 0 ? /* @__PURE__ */ jsx("div", { className: "twc-pop__empty", children: emptyText }) : virtualized ? /* @__PURE__ */ jsxs(Fragment, { children: [
3387
+ vTop > 0 ? /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { height: vTop } }) : null,
3388
+ vShown.map((r) => r.kind === "group" ? /* @__PURE__ */ jsx("div", { className: "twc-pop__group", children: r.label }, `g${r.gi}`) : renderOption(r.o, r.idx)),
3389
+ vBottom > 0 ? /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { height: vBottom } }) : null
3390
+ ] }) : fGroups.map((g, gi) => /* @__PURE__ */ jsxs(React36__default.Fragment, { children: [
3073
3391
  g.group ? /* @__PURE__ */ jsx("div", { className: "twc-pop__group", children: g.group }) : null,
3074
3392
  g.options.map((o) => {
3075
3393
  counter += 1;
3076
3394
  const idx = counter;
3077
- const isSel = o.value === current;
3078
- return /* @__PURE__ */ jsxs(
3079
- "button",
3080
- {
3081
- id: optionId(idx),
3082
- type: "button",
3083
- className: "twc-opt",
3084
- role: "option",
3085
- "aria-selected": isSel,
3086
- ref: idx === active ? activeRef : null,
3087
- disabled: o.disabled || void 0,
3088
- "aria-disabled": o.disabled || void 0,
3089
- "data-disabled": o.disabled || void 0,
3090
- "data-selected": isSel || void 0,
3091
- "data-active": idx === active || void 0,
3092
- onMouseEnter: () => {
3093
- if (!o.disabled) setActive(idx);
3094
- },
3095
- onClick: () => commit(o.value),
3096
- children: [
3097
- /* @__PURE__ */ jsxs("span", { className: "twc-opt__main", children: [
3098
- /* @__PURE__ */ jsx("span", { className: "twc-opt__label", children: o.label }),
3099
- o.description ? /* @__PURE__ */ jsx("span", { className: "twc-opt__desc", children: o.description }) : null
3100
- ] }),
3101
- isSel ? /* @__PURE__ */ jsx("span", { className: "twc-opt__check", "aria-hidden": "true", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.6", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) }) }) : null
3102
- ]
3103
- },
3104
- o.value
3105
- );
3395
+ return renderOption(o, idx);
3106
3396
  })
3107
3397
  ] }, gi)) }),
3108
3398
  /* @__PURE__ */ jsx("div", { className: "twc-sel__sr", role: "status", "aria-live": "polite", children: loading ? "Loading\u2026" : visible.length === 0 ? emptyText : `${visible.length} result${visible.length === 1 ? "" : "s"}` })
@@ -3648,6 +3938,8 @@ function MultiSelect({
3648
3938
  name,
3649
3939
  max,
3650
3940
  maxTagCount,
3941
+ virtualized = false,
3942
+ overscan = 8,
3651
3943
  id,
3652
3944
  className = "",
3653
3945
  onFocus,
@@ -3665,6 +3957,8 @@ function MultiSelect({
3665
3957
  const [query, setQuery] = React36__default.useState("");
3666
3958
  const [active, setActive] = React36__default.useState(0);
3667
3959
  const [coords, setCoords] = React36__default.useState(null);
3960
+ const [scrollTop, setScrollTop] = React36__default.useState(0);
3961
+ const [listH, setListH] = React36__default.useState(260);
3668
3962
  const wrapRef = React36__default.useRef(null);
3669
3963
  const controlRef = React36__default.useRef(null);
3670
3964
  const inputRef = React36__default.useRef(null);
@@ -3676,6 +3970,26 @@ function MultiSelect({
3676
3970
  [groups, query]
3677
3971
  );
3678
3972
  const visible = React36__default.useMemo(() => fGroups.flatMap((g) => g.options), [fGroups]);
3973
+ const rowH = React36__default.useMemo(() => visible.some((o) => o.description) ? 48 : 36, [visible]);
3974
+ const GROUP_H = 30;
3975
+ const { rows, totalH } = React36__default.useMemo(() => {
3976
+ if (!virtualized) return { rows: [], totalH: 0 };
3977
+ const rows2 = [];
3978
+ let off = 0;
3979
+ let optIdx = -1;
3980
+ fGroups.forEach((g, gi) => {
3981
+ if (g.group) {
3982
+ rows2.push({ kind: "group", gi, label: g.group, top: off, h: GROUP_H });
3983
+ off += GROUP_H;
3984
+ }
3985
+ g.options.forEach((o) => {
3986
+ optIdx += 1;
3987
+ rows2.push({ kind: "option", o, idx: optIdx, top: off, h: rowH });
3988
+ off += rowH;
3989
+ });
3990
+ });
3991
+ return { rows: rows2, totalH: off };
3992
+ }, [virtualized, fGroups, rowH]);
3679
3993
  const capReached = max != null && (value !== void 0 ? value : internal).length >= max;
3680
3994
  const isOptDisabled = (o) => o.disabled || capReached && !(value !== void 0 ? value : internal).includes(o.value);
3681
3995
  const nextEnabled = (from, dir) => {
@@ -3727,8 +4041,27 @@ function MultiSelect({
3727
4041
  setActive(0);
3728
4042
  }, [query]);
3729
4043
  React36__default.useEffect(() => {
3730
- if (open) ensureVisible3(listRef.current, activeRef.current);
3731
- }, [active, open]);
4044
+ if (!open || !virtualized) return;
4045
+ const el = listRef.current;
4046
+ if (el && el.clientHeight) setListH(el.clientHeight);
4047
+ }, [open, virtualized]);
4048
+ React36__default.useEffect(() => {
4049
+ if (!open) return;
4050
+ if (!virtualized) {
4051
+ ensureVisible3(listRef.current, activeRef.current);
4052
+ return;
4053
+ }
4054
+ const el = listRef.current;
4055
+ if (!el) return;
4056
+ const row = rows.find((r) => r.kind === "option" && r.idx === active);
4057
+ if (!row) return;
4058
+ const vh = el.clientHeight || listH;
4059
+ let next = el.scrollTop;
4060
+ if (row.top < el.scrollTop) next = row.top;
4061
+ else if (row.top + row.h > el.scrollTop + vh) next = row.top + row.h - vh;
4062
+ if (next !== el.scrollTop) el.scrollTop = next;
4063
+ setScrollTop(next);
4064
+ }, [active, open, virtualized, rows, listH]);
3732
4065
  function commit(next) {
3733
4066
  if (value === void 0) setInternal(next);
3734
4067
  onChange?.(next);
@@ -3765,48 +4098,74 @@ function MultiSelect({
3765
4098
  const activeId = open && visible[active] ? optionId(active) : void 0;
3766
4099
  const descId = `${fieldId}-desc`;
3767
4100
  const describedBy = error || hint ? descId : void 0;
3768
- let counter = -1;
3769
- const popInner = /* @__PURE__ */ jsx("div", { className: "twc-pop__list", id: listboxId, ref: listRef, role: "listbox", "aria-multiselectable": "true", children: loading ? /* @__PURE__ */ jsxs("div", { className: "twc-pop__loading", role: "status", children: [
3770
- /* @__PURE__ */ jsx("span", { className: "twc-pop__spinner", "aria-hidden": "true" }),
3771
- "Loading\u2026"
3772
- ] }) : visible.length === 0 ? /* @__PURE__ */ jsx("div", { className: "twc-pop__empty", children: emptyText }) : fGroups.map((g, gi) => /* @__PURE__ */ jsxs(React36__default.Fragment, { children: [
3773
- g.group ? /* @__PURE__ */ jsx("div", { className: "twc-pop__group", children: g.group }) : null,
3774
- g.options.map((o) => {
3775
- counter += 1;
3776
- const idx = counter;
3777
- const isSel = selected.includes(o.value);
3778
- const optDisabled = isOptDisabled(o);
3779
- return /* @__PURE__ */ jsxs(
3780
- "button",
3781
- {
3782
- id: optionId(idx),
3783
- type: "button",
3784
- className: "twc-opt",
3785
- role: "option",
3786
- "aria-selected": isSel,
3787
- ref: idx === active ? activeRef : null,
3788
- disabled: optDisabled || void 0,
3789
- "aria-disabled": optDisabled || void 0,
3790
- "data-disabled": optDisabled || void 0,
3791
- "data-selected": isSel || void 0,
3792
- "data-active": idx === active || void 0,
3793
- onMouseEnter: () => {
3794
- if (!optDisabled) setActive(idx);
3795
- },
3796
- onMouseDown: (e) => e.preventDefault(),
3797
- onClick: () => toggle(o.value),
3798
- children: [
3799
- /* @__PURE__ */ jsx("span", { className: "twc-opt__box", "aria-hidden": "true", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3.2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) }) }),
3800
- /* @__PURE__ */ jsxs("span", { className: "twc-opt__main", children: [
3801
- /* @__PURE__ */ jsx("span", { className: "twc-opt__label", children: o.label }),
3802
- o.description ? /* @__PURE__ */ jsx("span", { className: "twc-opt__desc", children: o.description }) : null
3803
- ] })
3804
- ]
4101
+ const renderOption = (o, idx) => {
4102
+ const isSel = selected.includes(o.value);
4103
+ const optDisabled = isOptDisabled(o);
4104
+ return /* @__PURE__ */ jsxs(
4105
+ "button",
4106
+ {
4107
+ id: optionId(idx),
4108
+ type: "button",
4109
+ className: "twc-opt",
4110
+ role: "option",
4111
+ "aria-selected": isSel,
4112
+ ref: idx === active ? activeRef : null,
4113
+ disabled: optDisabled || void 0,
4114
+ "aria-disabled": optDisabled || void 0,
4115
+ "data-disabled": optDisabled || void 0,
4116
+ "data-selected": isSel || void 0,
4117
+ "data-active": idx === active || void 0,
4118
+ onMouseEnter: () => {
4119
+ if (!optDisabled) setActive(idx);
3805
4120
  },
3806
- o.value
3807
- );
3808
- })
3809
- ] }, gi)) });
4121
+ onMouseDown: (e) => e.preventDefault(),
4122
+ onClick: () => toggle(o.value),
4123
+ children: [
4124
+ /* @__PURE__ */ jsx("span", { className: "twc-opt__box", "aria-hidden": "true", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3.2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) }) }),
4125
+ /* @__PURE__ */ jsxs("span", { className: "twc-opt__main", children: [
4126
+ /* @__PURE__ */ jsx("span", { className: "twc-opt__label", children: o.label }),
4127
+ o.description ? /* @__PURE__ */ jsx("span", { className: "twc-opt__desc", children: o.description }) : null
4128
+ ] })
4129
+ ]
4130
+ },
4131
+ o.value
4132
+ );
4133
+ };
4134
+ let vTop = 0, vBottom = 0, vShown = rows;
4135
+ if (virtualized && rows.length) {
4136
+ const winTop = scrollTop - overscan * rowH;
4137
+ const winBottom = scrollTop + listH + overscan * rowH;
4138
+ vShown = rows.filter((r) => r.top + r.h > winTop && r.top < winBottom);
4139
+ vTop = vShown.length ? vShown[0].top : 0;
4140
+ vBottom = vShown.length ? totalH - (vShown[vShown.length - 1].top + vShown[vShown.length - 1].h) : totalH;
4141
+ }
4142
+ let counter = -1;
4143
+ const popInner = /* @__PURE__ */ jsx(
4144
+ "div",
4145
+ {
4146
+ className: "twc-pop__list",
4147
+ id: listboxId,
4148
+ ref: listRef,
4149
+ role: "listbox",
4150
+ "aria-multiselectable": "true",
4151
+ onScroll: virtualized ? (e) => setScrollTop(e.currentTarget.scrollTop) : void 0,
4152
+ children: loading ? /* @__PURE__ */ jsxs("div", { className: "twc-pop__loading", role: "status", children: [
4153
+ /* @__PURE__ */ jsx("span", { className: "twc-pop__spinner", "aria-hidden": "true" }),
4154
+ "Loading\u2026"
4155
+ ] }) : visible.length === 0 ? /* @__PURE__ */ jsx("div", { className: "twc-pop__empty", children: emptyText }) : virtualized ? /* @__PURE__ */ jsxs(Fragment, { children: [
4156
+ vTop > 0 ? /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { height: vTop } }) : null,
4157
+ vShown.map((r) => r.kind === "group" ? /* @__PURE__ */ jsx("div", { className: "twc-pop__group", children: r.label }, `g${r.gi}`) : renderOption(r.o, r.idx)),
4158
+ vBottom > 0 ? /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { height: vBottom } }) : null
4159
+ ] }) : fGroups.map((g, gi) => /* @__PURE__ */ jsxs(React36__default.Fragment, { children: [
4160
+ g.group ? /* @__PURE__ */ jsx("div", { className: "twc-pop__group", children: g.group }) : null,
4161
+ g.options.map((o) => {
4162
+ counter += 1;
4163
+ const idx = counter;
4164
+ return renderOption(o, idx);
4165
+ })
4166
+ ] }, gi))
4167
+ }
4168
+ );
3810
4169
  const RD = { createPortal: typeof createPortal === "function" ? createPortal : typeof window !== "undefined" && window.ReactDOM && window.ReactDOM.createPortal };
3811
4170
  const canPortal = portal && RD && RD.createPortal;
3812
4171
  let popEl = null;
@@ -4980,6 +5339,9 @@ function Datatable({
4980
5339
  density: densityProp = "comfortable",
4981
5340
  pageSize = 10,
4982
5341
  pageSizeOptions = [5, 10, 25, 50],
5342
+ page,
5343
+ onPageChange,
5344
+ onPageSizeChange,
4983
5345
  height = 440,
4984
5346
  serverMode = false,
4985
5347
  rowCount,
@@ -5094,8 +5456,21 @@ function Datatable({
5094
5456
  const [selected, setSelected] = React36__default.useState(() => /* @__PURE__ */ new Set());
5095
5457
  const [activeRow, setActiveRow] = React36__default.useState(null);
5096
5458
  const [activeCell, setActiveCell] = React36__default.useState(null);
5097
- const [page, setPage] = React36__default.useState(0);
5098
- const [rowsPerPage, setRowsPerPage] = React36__default.useState(pageSize > 0 ? pageSize : 10);
5459
+ const [internalPage, setInternalPage] = React36__default.useState(0);
5460
+ const [internalRpp, setInternalRpp] = React36__default.useState(pageSize > 0 ? pageSize : 10);
5461
+ const pageControlled = page !== void 0;
5462
+ const pageSizeControlled = onPageSizeChange !== void 0;
5463
+ const pageVal = pageControlled ? page : internalPage;
5464
+ const sizeVal = pageSizeControlled ? pageSize > 0 ? pageSize : 10 : internalRpp;
5465
+ const commitPage = (next) => {
5466
+ onPageChange?.(next);
5467
+ if (!pageControlled) setInternalPage(next);
5468
+ };
5469
+ const commitPageSize = (next) => {
5470
+ onPageSizeChange?.(next);
5471
+ if (!pageSizeControlled) setInternalRpp(next);
5472
+ commitPage(0);
5473
+ };
5099
5474
  const [colQuery, setColQuery] = React36__default.useState("");
5100
5475
  const [widths, setWidths] = React36__default.useState({});
5101
5476
  const [order, setOrder] = React36__default.useState(() => columns.map((c) => c.field));
@@ -5161,6 +5536,11 @@ function Datatable({
5161
5536
  React36__default.useEffect(() => {
5162
5537
  setAggOn(showAggregation);
5163
5538
  }, [showAggregation]);
5539
+ React36__default.useEffect(() => {
5540
+ if (pageSizeControlled) return;
5541
+ setInternalRpp(pageSize > 0 ? pageSize : 10);
5542
+ setInternalPage(0);
5543
+ }, [pageSize]);
5164
5544
  const rowGroupingKey = (rowGrouping || []).join("\0");
5165
5545
  const rowGroupingKeyRef = React36__default.useRef(rowGroupingKey);
5166
5546
  React36__default.useEffect(() => {
@@ -5344,10 +5724,10 @@ function Datatable({
5344
5724
  const paginated = pageSize > 0;
5345
5725
  const serverTotal = rowCount == null ? processed.length : rowCount;
5346
5726
  const totalRows = serverMode ? serverTotal : processed.length;
5347
- const totalPages = paginated ? Math.max(1, Math.ceil(totalRows / rowsPerPage)) : 1;
5348
- const paged = !paginated || serverMode ? processed : processed.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
5727
+ const totalPages = paginated ? Math.max(1, Math.ceil(totalRows / sizeVal)) : 1;
5728
+ const paged = !paginated || serverMode ? processed : processed.slice(pageVal * sizeVal, pageVal * sizeVal + sizeVal);
5349
5729
  React36__default.useEffect(() => {
5350
- if (page > totalPages - 1) setPage(0);
5730
+ if (pageVal > totalPages - 1) commitPage(0);
5351
5731
  }, [totalPages]);
5352
5732
  const activeGroupBy = React36__default.useMemo(() => groupBy.filter((f) => colByField[f]), [groupBy, colByField]);
5353
5733
  function subtotalText(rowsIn) {
@@ -5610,15 +5990,15 @@ function Datatable({
5610
5990
  if (!serverMode) return;
5611
5991
  const t = setTimeout(() => {
5612
5992
  onServerChangeRef.current?.({
5613
- page,
5614
- pageSize: rowsPerPage,
5993
+ page: pageVal,
5994
+ pageSize: sizeVal,
5615
5995
  sort,
5616
5996
  filters: filters.map(({ id, ...f }) => f),
5617
5997
  quickFilter: quick.trim()
5618
5998
  });
5619
5999
  }, 250);
5620
6000
  return () => clearTimeout(t);
5621
- }, [serverMode, page, rowsPerPage, sort, filters, quick]);
6001
+ }, [serverMode, pageVal, sizeVal, sort, filters, quick]);
5622
6002
  function cycleSort(field) {
5623
6003
  setSort((s) => !s || s.field !== field ? { field, dir: "asc" } : s.dir === "asc" ? { field, dir: "desc" } : null);
5624
6004
  }
@@ -5899,7 +6279,7 @@ function Datatable({
5899
6279
  }
5900
6280
  React36__default.useEffect(() => {
5901
6281
  setFocus((f) => ({ r: Math.min(f.r, Math.max(0, leafRows.length - 1)), c: Math.min(f.c, Math.max(0, ordered.length - 1)) }));
5902
- }, [leafRows.length, ordered.length, page]);
6282
+ }, [leafRows.length, ordered.length, pageVal]);
5903
6283
  const selectedRows = React36__default.useMemo(() => rows.filter((r, i) => selected.has(keyOf(r, i))), [rows, selected]);
5904
6284
  function clearSelection() {
5905
6285
  setSelected(/* @__PURE__ */ new Set());
@@ -6144,7 +6524,7 @@ function Datatable({
6144
6524
  {
6145
6525
  className: "twc-dt__row",
6146
6526
  role: "row",
6147
- "aria-rowindex": (paginated && !serverMode ? page * rowsPerPage : 0) + ri + 2,
6527
+ "aria-rowindex": (paginated && !serverMode ? pageVal * sizeVal : 0) + ri + 2,
6148
6528
  "aria-selected": (checkboxSelection ? sel : rowActive) || void 0,
6149
6529
  "data-selected": sel || void 0,
6150
6530
  "data-active": rowActive || void 0,
@@ -6202,7 +6582,7 @@ function Datatable({
6202
6582
  ] }),
6203
6583
  rowResize && !pinSide ? /* @__PURE__ */ jsx("span", { className: "twc-dt__row-resizer", title: "Drag to resize row", onPointerDown: (e) => startRowResize(e, k, e.currentTarget.closest("tr")), onClick: (e) => e.stopPropagation() }) : null
6204
6584
  ] }) : null,
6205
- showRowNum ? /* @__PURE__ */ jsx("td", { className: "twc-dt__td twc-dt__rownum", role: "gridcell", "aria-hidden": "true", "data-pin": "left", "data-pin-edge": pins.left.length ? void 0 : "left", style: { insetInlineStart: numLeft, width: NUM_W }, children: typeof ri === "number" ? (paginated || serverMode ? page * rowsPerPage : 0) + ri + 1 : "" }) : null,
6585
+ showRowNum ? /* @__PURE__ */ jsx("td", { className: "twc-dt__td twc-dt__rownum", role: "gridcell", "aria-hidden": "true", "data-pin": "left", "data-pin-edge": pins.left.length ? void 0 : "left", style: { insetInlineStart: numLeft, width: NUM_W }, children: typeof ri === "number" ? (paginated || serverMode ? pageVal * sizeVal : 0) + ri + 1 : "" }) : null,
6206
6586
  ordered.map((c, ci) => {
6207
6587
  const st = stickyOf(c.field);
6208
6588
  const val = row[c.field];
@@ -6530,7 +6910,7 @@ function Datatable({
6530
6910
  /* @__PURE__ */ jsx(Svg, { d: I.search }),
6531
6911
  /* @__PURE__ */ jsx("input", { placeholder: "Search\u2026", "aria-label": "Search rows", value: quick, onChange: (e) => {
6532
6912
  setQuick(e.target.value);
6533
- setPage(0);
6913
+ commitPage(0);
6534
6914
  } })
6535
6915
  ] })
6536
6916
  ] }),
@@ -6732,7 +7112,7 @@ function Datatable({
6732
7112
  );
6733
7113
  })
6734
7114
  ] }) }),
6735
- /* @__PURE__ */ jsx("tbody", { children: loading ? Array.from({ length: paginated ? Math.min(rowsPerPage, 8) : 8 }).map((_, ri) => /* @__PURE__ */ jsxs("tr", { className: "twc-dt__row", role: "row", children: [
7115
+ /* @__PURE__ */ jsx("tbody", { children: loading ? Array.from({ length: paginated ? Math.min(sizeVal, 8) : 8 }).map((_, ri) => /* @__PURE__ */ jsxs("tr", { className: "twc-dt__row", role: "row", children: [
6736
7116
  checkboxSelection ? /* @__PURE__ */ jsx("td", { className: "twc-dt__td", role: "gridcell", "data-pin": "left", style: { insetInlineStart: 0, width: 44 }, children: /* @__PURE__ */ jsx("span", { className: "twc-dt__sk", "aria-hidden": "true", style: { "--_w": "18px", height: 18, borderRadius: 4 } }) }) : null,
6737
7117
  showRowNum ? /* @__PURE__ */ jsx("td", { className: "twc-dt__td twc-dt__rownum", role: "gridcell", "aria-hidden": "true", "data-pin": "left", style: { insetInlineStart: numLeft, width: NUM_W }, children: /* @__PURE__ */ jsx("span", { className: "twc-dt__sk", "aria-hidden": "true", style: { "--_w": "16px", height: 14, borderRadius: 4 } }) }) : null,
6738
7118
  ordered.map((c, ci) => {
@@ -6776,7 +7156,7 @@ function Datatable({
6776
7156
  const nf = (n) => n.toLocaleString();
6777
7157
  if (totalRows === 0) return "No rows";
6778
7158
  const shown = paged.length;
6779
- const start = paginated ? page * rowsPerPage + 1 : 1;
7159
+ const start = paginated ? pageVal * sizeVal + 1 : 1;
6780
7160
  const end = paginated ? start + shown - 1 : shown;
6781
7161
  return /* @__PURE__ */ jsxs(Fragment, { children: [
6782
7162
  paginated ? /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -6804,13 +7184,10 @@ function Datatable({
6804
7184
  })() }),
6805
7185
  paginated ? /* @__PURE__ */ jsxs("div", { className: "twc-dt__rpp", children: [
6806
7186
  /* @__PURE__ */ jsx("span", { className: "twc-dt__rpp-label", children: "Rows per page" }),
6807
- /* @__PURE__ */ jsx("div", { style: { width: 78 }, children: /* @__PURE__ */ jsx(Select, { size: "sm", portal: true, searchable: false, value: String(rowsPerPage), options: rppOptions, placement: "top", onChange: (v) => {
6808
- setRowsPerPage(Number(v));
6809
- setPage(0);
6810
- } }) })
7187
+ /* @__PURE__ */ jsx("div", { style: { width: 78 }, children: /* @__PURE__ */ jsx(Select, { size: "sm", portal: true, searchable: false, value: String(sizeVal), options: rppOptions, placement: "top", onChange: (v) => commitPageSize(Number(v)) }) })
6811
7188
  ] }) : null
6812
7189
  ] }),
6813
- paginated ? /* @__PURE__ */ jsx("div", { className: "twc-dt__footer-row", "data-pager": "true", children: /* @__PURE__ */ jsx(Pagination, { size: "sm", page: page + 1, total: totalPages, boundaries: 3, showPageJumper: showPageJumper && totalPages > 5, onChange: (p) => setPage(p - 1) }) }) : null
7190
+ paginated ? /* @__PURE__ */ jsx("div", { className: "twc-dt__footer-row", "data-pager": "true", children: /* @__PURE__ */ jsx(Pagination, { size: "sm", page: pageVal + 1, total: totalPages, boundaries: 3, showPageJumper: showPageJumper && totalPages > 5, onChange: (p) => commitPage(p - 1) }) }) : null
6814
7191
  ] }) : null,
6815
7192
  exportOpen && exportPos ? /* @__PURE__ */ jsxs(
6816
7193
  "div",
@@ -7459,6 +7836,18 @@ var DATEPICKER_CSS = `
7459
7836
  .twc-dp__clear { position: absolute; inset-inline-end: 10px; top: 50%; transform: translateY(-50%); display: inline-grid; place-items: center; width: 20px; height: 20px; border: none; background: transparent; color: var(--color-text-subtle); cursor: pointer; border-radius: var(--radius-full); }
7460
7837
  .twc-dp__clear:hover { background: var(--color-surface-sunken); color: var(--color-text); }
7461
7838
  .twc-dp__clear svg { width: 14px; height: 14px; }
7839
+ /* #105: editable (typed entry) variant \u2014 the control wraps a real text input + a trailing toggle. */
7840
+ .twc-dp__control--editable { cursor: text; gap: 0; padding-inline-end: var(--space-1); }
7841
+ .twc-dp__input { flex: 1; min-width: 0; height: 100%; border: none; background: transparent; outline: none; padding: 0;
7842
+ font-family: inherit; font-size: var(--text-sm); color: var(--color-text); }
7843
+ .twc-dp__input::placeholder { color: var(--color-text-subtle); }
7844
+ .twc-dp__input:disabled { cursor: not-allowed; }
7845
+ .twc-dp__clear--inline { position: static; transform: none; flex: none; }
7846
+ .twc-dp__toggle { flex: none; display: inline-grid; place-items: center; width: 26px; height: 26px; border: none; background: transparent;
7847
+ color: var(--color-text-subtle); cursor: pointer; border-radius: var(--radius-md); transition: background-color var(--duration-fast), color var(--duration-fast); }
7848
+ .twc-dp__toggle:hover:not(:disabled) { background: var(--color-surface-sunken); color: var(--color-text); }
7849
+ .twc-dp__toggle:disabled { cursor: not-allowed; }
7850
+ .twc-dp__toggle svg { width: 17px; height: 17px; }
7462
7851
 
7463
7852
  .twc-dp__pop {
7464
7853
  position: fixed; z-index: var(--z-popover); width: 280px;
@@ -7518,6 +7907,8 @@ function DatePicker({
7518
7907
  disabled = false,
7519
7908
  tone = "primary",
7520
7909
  clearable = true,
7910
+ editable = false,
7911
+ parse,
7521
7912
  format,
7522
7913
  locale,
7523
7914
  weekStartsOn = 0,
@@ -7539,6 +7930,7 @@ function DatePicker({
7539
7930
  const [mode, setMode] = React36__default.useState("days");
7540
7931
  const [focusDate, setFocusDate] = React36__default.useState(null);
7541
7932
  const [focusMonth, setFocusMonth] = React36__default.useState(null);
7933
+ const [editText, setEditText] = React36__default.useState(null);
7542
7934
  const [coords, setCoords] = React36__default.useState(null);
7543
7935
  const wrapRef = React36__default.useRef(null);
7544
7936
  const triggerRef = React36__default.useRef(null);
@@ -7602,8 +7994,33 @@ function DatePicker({
7602
7994
  const pick = (d) => {
7603
7995
  if (value === void 0) setInternal(d);
7604
7996
  onChange?.(d);
7997
+ setEditText(null);
7605
7998
  setOpen(false);
7606
7999
  };
8000
+ const parseTyped = (s) => {
8001
+ const str = s.trim();
8002
+ if (str === "") return null;
8003
+ if (parse) {
8004
+ const d = parse(str);
8005
+ return d instanceof Date && !Number.isNaN(d.getTime()) ? d : void 0;
8006
+ }
8007
+ const t = Date.parse(str);
8008
+ return Number.isNaN(t) ? void 0 : new Date(t);
8009
+ };
8010
+ const commitText = () => {
8011
+ if (editText == null) return;
8012
+ const parsed = parseTyped(editText);
8013
+ if (parsed === null) {
8014
+ setEditText(null);
8015
+ if (selected) pick(null);
8016
+ return;
8017
+ }
8018
+ if (parsed === void 0 || outOfRange(parsed)) {
8019
+ setEditText(null);
8020
+ return;
8021
+ }
8022
+ pick(parsed);
8023
+ };
7607
8024
  const y = view.getFullYear(), m = view.getMonth();
7608
8025
  const first = new Date(y, m, 1);
7609
8026
  const startOffset = (first.getDay() - weekStartsOn + 7) % 7;
@@ -7679,7 +8096,82 @@ function DatePicker({
7679
8096
  label,
7680
8097
  required ? /* @__PURE__ */ jsx("span", { className: "twc-field__req", children: "*" }) : null
7681
8098
  ] }) : null,
7682
- /* @__PURE__ */ jsxs("div", { className: "twc-dp__field", children: [
8099
+ /* @__PURE__ */ jsx("div", { className: "twc-dp__field", children: editable ? (
8100
+ // #105: typed entry — the trigger is a real text input with a trailing
8101
+ // calendar toggle button; typing commits via `parse` on Enter/blur.
8102
+ /* @__PURE__ */ jsxs(
8103
+ "div",
8104
+ {
8105
+ className: "twc-dp__control twc-dp__control--editable",
8106
+ "data-open": open || void 0,
8107
+ "data-disabled": disabled || void 0,
8108
+ "data-tone": tone,
8109
+ "data-invalid": invalid || void 0,
8110
+ children: [
8111
+ /* @__PURE__ */ jsx(
8112
+ "input",
8113
+ {
8114
+ ref: triggerRef,
8115
+ className: "twc-dp__input",
8116
+ id: fieldId,
8117
+ type: "text",
8118
+ inputMode: "numeric",
8119
+ autoComplete: "off",
8120
+ disabled,
8121
+ placeholder,
8122
+ value: editText != null ? editText : selected ? fmt(selected) : "",
8123
+ "aria-haspopup": "dialog",
8124
+ "aria-expanded": open,
8125
+ "aria-labelledby": label ? `${fieldId}-label` : void 0,
8126
+ "aria-invalid": invalid || void 0,
8127
+ "aria-describedby": error || hint ? descId : void 0,
8128
+ onChange: (e) => setEditText(e.target.value),
8129
+ onKeyDown: (e) => {
8130
+ if (e.key === "Enter") {
8131
+ e.preventDefault();
8132
+ commitText();
8133
+ } else if (e.key === "ArrowDown" && (e.altKey || !open)) {
8134
+ e.preventDefault();
8135
+ setOpen(true);
8136
+ } else if (e.key === "Escape" && open) setOpen(false);
8137
+ },
8138
+ onBlur: (e) => {
8139
+ if (popRef.current && popRef.current.contains(e.relatedTarget)) return;
8140
+ commitText();
8141
+ }
8142
+ }
8143
+ ),
8144
+ clearable && selected && !disabled ? /* @__PURE__ */ jsx(
8145
+ "button",
8146
+ {
8147
+ type: "button",
8148
+ className: "twc-dp__clear twc-dp__clear--inline",
8149
+ "aria-label": "Clear",
8150
+ tabIndex: -1,
8151
+ onMouseDown: (e) => e.preventDefault(),
8152
+ onClick: () => pick(null),
8153
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) })
8154
+ }
8155
+ ) : null,
8156
+ /* @__PURE__ */ jsx(
8157
+ "button",
8158
+ {
8159
+ type: "button",
8160
+ className: "twc-dp__toggle",
8161
+ "aria-label": "Open calendar",
8162
+ tabIndex: -1,
8163
+ disabled,
8164
+ onClick: () => !disabled && setOpen((o) => !o),
8165
+ children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
8166
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
8167
+ /* @__PURE__ */ jsx("path", { d: "M16 2v4M8 2v4M3 10h18" })
8168
+ ] })
8169
+ }
8170
+ )
8171
+ ]
8172
+ }
8173
+ )
8174
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
7683
8175
  /* @__PURE__ */ jsxs(
7684
8176
  "div",
7685
8177
  {
@@ -7718,7 +8210,7 @@ function DatePicker({
7718
8210
  e.stopPropagation();
7719
8211
  pick(null);
7720
8212
  }, children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }) }) : null
7721
- ] }),
8213
+ ] }) }),
7722
8214
  open && coords ? createPortal(
7723
8215
  /* @__PURE__ */ jsxs(
7724
8216
  "div",
@@ -7817,6 +8309,17 @@ var RANGE_CSS = `
7817
8309
  .twc-drp__ic svg { width: 17px; height: 17px; }
7818
8310
  .twc-drp__text { flex: 1; font-size: var(--text-sm); color: var(--color-text); white-space: nowrap; }
7819
8311
  .twc-drp__text[data-placeholder="true"] { color: var(--color-text-subtle); }
8312
+ /* #105: editable (typed entry) variant. */
8313
+ .twc-drp__control--editable { cursor: text; gap: 0; padding-inline-end: var(--space-1); }
8314
+ .twc-drp__input { flex: 1; min-width: 0; height: 100%; border: none; background: transparent; outline: none; padding: 0;
8315
+ font-family: inherit; font-size: var(--text-sm); color: var(--color-text); }
8316
+ .twc-drp__input::placeholder { color: var(--color-text-subtle); }
8317
+ .twc-drp__input:disabled { cursor: not-allowed; }
8318
+ .twc-drp__toggle { flex: none; display: inline-grid; place-items: center; width: 26px; height: 26px; border: none; background: transparent;
8319
+ color: var(--color-text-subtle); cursor: pointer; border-radius: var(--radius-md); transition: background-color var(--duration-fast), color var(--duration-fast); }
8320
+ .twc-drp__toggle:hover:not(:disabled) { background: var(--color-surface-sunken); color: var(--color-text); }
8321
+ .twc-drp__toggle:disabled { cursor: not-allowed; }
8322
+ .twc-drp__toggle svg { width: 17px; height: 17px; }
7820
8323
  .twc-drp__pop { position: fixed; z-index: var(--z-popover); display: flex;
7821
8324
  background: var(--color-surface-raised); border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-lg);
7822
8325
  box-shadow: var(--shadow-lg); animation: twico-scale-in var(--duration-fast) var(--ease-spring); transform-origin: top; }
@@ -7871,6 +8374,8 @@ function DateRangePicker({
7871
8374
  disabledDate,
7872
8375
  disabled = false,
7873
8376
  tone = "primary",
8377
+ editable = false,
8378
+ parse,
7874
8379
  onChange,
7875
8380
  className = "",
7876
8381
  ...rest
@@ -7883,6 +8388,7 @@ function DateRangePicker({
7883
8388
  const [view, setView] = React36__default.useState(range.start || /* @__PURE__ */ new Date());
7884
8389
  const [hover, setHover] = React36__default.useState(null);
7885
8390
  const [focusDate, setFocusDate] = React36__default.useState(null);
8391
+ const [editText, setEditText] = React36__default.useState(null);
7886
8392
  const [coords, setCoords] = React36__default.useState(null);
7887
8393
  const wrapRef = React36__default.useRef(null);
7888
8394
  const triggerRef = React36__default.useRef(null);
@@ -7938,6 +8444,40 @@ function DateRangePicker({
7938
8444
  if (value === void 0) setInternal(r);
7939
8445
  onChange?.(r);
7940
8446
  };
8447
+ const parseOne = (s) => {
8448
+ const str = s.trim();
8449
+ if (str === "") return null;
8450
+ if (parse) {
8451
+ const d = parse(str);
8452
+ return d instanceof Date && !Number.isNaN(d.getTime()) ? d : void 0;
8453
+ }
8454
+ const t = Date.parse(str);
8455
+ return Number.isNaN(t) ? void 0 : new Date(t);
8456
+ };
8457
+ const commitText = () => {
8458
+ if (editText == null) return;
8459
+ const raw = editText.trim();
8460
+ if (raw === "") {
8461
+ setEditText(null);
8462
+ if (range.start || range.end) set({ start: null, end: null });
8463
+ return;
8464
+ }
8465
+ const parts = raw.split(/\s*(?:–|—|\bto\b|\.\.+)\s*/i).map((p) => p.trim());
8466
+ let a = parseOne(parts[0] || "");
8467
+ let b = parts.length > 1 ? parseOne(parts[1] || "") : null;
8468
+ if (a === void 0 || b === void 0 || a && outOfRange(a) || b && outOfRange(b)) {
8469
+ setEditText(null);
8470
+ return;
8471
+ }
8472
+ if (a && b && ymd(b) < ymd(a)) {
8473
+ const t = a;
8474
+ a = b;
8475
+ b = t;
8476
+ }
8477
+ set({ start: a || null, end: b || null });
8478
+ setEditText(null);
8479
+ setOpen(false);
8480
+ };
7941
8481
  useFocusTrap(popRef, open && !!coords, { initialFocus: () => gridRef.current?.querySelector('[tabindex="0"]') });
7942
8482
  const outOfRange = (d) => min && ymd(d) < ymd(min) || max && ymd(d) > ymd(max) || Boolean(disabledDate && disabledDate(d));
7943
8483
  const sameDay2 = (a, b) => a && b && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
@@ -8033,7 +8573,57 @@ function DateRangePicker({
8033
8573
  label,
8034
8574
  required ? /* @__PURE__ */ jsx("span", { className: "twc-field__req", children: "*" }) : null
8035
8575
  ] }) : null,
8036
- /* @__PURE__ */ jsxs(
8576
+ editable ? (
8577
+ // #105: typed entry — a text input accepting "start – end"; commits via `parse` on Enter/blur.
8578
+ /* @__PURE__ */ jsxs("div", { className: "twc-drp__control twc-drp__control--editable", "data-open": open || void 0, "data-disabled": disabled || void 0, "data-invalid": invalid || void 0, "data-tone": tone, children: [
8579
+ /* @__PURE__ */ jsx(
8580
+ "input",
8581
+ {
8582
+ ref: triggerRef,
8583
+ className: "twc-drp__input",
8584
+ type: "text",
8585
+ autoComplete: "off",
8586
+ disabled,
8587
+ placeholder,
8588
+ value: editText != null ? editText : text,
8589
+ "aria-haspopup": "dialog",
8590
+ "aria-expanded": open,
8591
+ "aria-labelledby": label ? labelId : void 0,
8592
+ "aria-invalid": invalid || void 0,
8593
+ "aria-describedby": error || hint ? descId : void 0,
8594
+ onChange: (e) => setEditText(e.target.value),
8595
+ onKeyDown: (e) => {
8596
+ if (e.key === "Enter") {
8597
+ e.preventDefault();
8598
+ commitText();
8599
+ } else if (e.key === "ArrowDown" && (e.altKey || !open)) {
8600
+ e.preventDefault();
8601
+ setOpen(true);
8602
+ } else if (e.key === "Escape" && open) setOpen(false);
8603
+ },
8604
+ onBlur: (e) => {
8605
+ if (popRef.current && popRef.current.contains(e.relatedTarget)) return;
8606
+ commitText();
8607
+ }
8608
+ }
8609
+ ),
8610
+ /* @__PURE__ */ jsx(
8611
+ "button",
8612
+ {
8613
+ type: "button",
8614
+ className: "twc-drp__toggle",
8615
+ "aria-label": "Open calendar",
8616
+ tabIndex: -1,
8617
+ disabled,
8618
+ onClick: () => !disabled && setOpen((o) => !o),
8619
+ children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
8620
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
8621
+ /* @__PURE__ */ jsx("path", { d: "M16 2v4M8 2v4M3 10h18" })
8622
+ ] })
8623
+ }
8624
+ )
8625
+ ] })
8626
+ ) : /* @__PURE__ */ jsxs(
8037
8627
  "div",
8038
8628
  {
8039
8629
  ref: triggerRef,
@@ -11507,11 +12097,13 @@ var Box = React36__default.forwardRef(function Box2({
11507
12097
  border = false,
11508
12098
  radius,
11509
12099
  shadow,
12100
+ sx,
11510
12101
  className = "",
11511
12102
  style,
11512
12103
  children,
11513
12104
  ...rest
11514
12105
  }, ref) {
12106
+ const { flatStyle, styleNode, sxAttr } = useSx(sx);
11515
12107
  const s = {
11516
12108
  paddingTop: sp(pt ?? py ?? p),
11517
12109
  paddingRight: sp(pr ?? px ?? p),
@@ -11525,10 +12117,14 @@ var Box = React36__default.forwardRef(function Box2({
11525
12117
  border: border ? "var(--border-thin, 1px) solid var(--color-border)" : void 0,
11526
12118
  borderRadius: radius ? `var(--radius-${radius})` : void 0,
11527
12119
  boxShadow: shadow ? `var(--shadow-${shadow})` : void 0,
11528
- ...style
12120
+ ...style,
12121
+ ...flatStyle
11529
12122
  };
11530
12123
  if (Tag2 === "a" && rest.href != null) rest.href = safeHref6(rest.href);
11531
- return /* @__PURE__ */ jsx(Tag2, { ref, className: `twc-box ${className}`.trim(), style: s, ...rest, children });
12124
+ return /* @__PURE__ */ jsxs(Tag2, { ref, className: `twc-box ${className}`.trim(), style: s, "data-twc-sx": sxAttr, ...rest, children: [
12125
+ styleNode,
12126
+ children
12127
+ ] });
11532
12128
  });
11533
12129
  Box.displayName = "Box";
11534
12130
  var SIZES = { sm: 640, md: 768, lg: 1024, xl: 1280, full: "100%" };
@@ -11541,27 +12137,34 @@ var Container = React36__default.forwardRef(function Container2({
11541
12137
  as: Tag2 = "div",
11542
12138
  size = "lg",
11543
12139
  padded = true,
12140
+ sx,
11544
12141
  className = "",
11545
12142
  style,
11546
12143
  children,
11547
12144
  ...rest
11548
12145
  }, ref) {
12146
+ const { flatStyle, styleNode, sxAttr } = useSx(sx);
11549
12147
  const max = SIZES[size] ?? size;
11550
12148
  if (Tag2 === "a" && rest.href != null) rest.href = safeHref7(rest.href);
11551
- return /* @__PURE__ */ jsx(
12149
+ return /* @__PURE__ */ jsxs(
11552
12150
  Tag2,
11553
12151
  {
11554
12152
  ref,
11555
12153
  className: `twc-container ${className}`.trim(),
12154
+ "data-twc-sx": sxAttr,
11556
12155
  style: {
11557
12156
  width: "100%",
11558
12157
  maxWidth: typeof max === "number" ? `${max}px` : max,
11559
12158
  marginInline: "auto",
11560
12159
  paddingInline: padded ? "var(--space-5)" : void 0,
11561
- ...style
12160
+ ...style,
12161
+ ...flatStyle
11562
12162
  },
11563
12163
  ...rest,
11564
- children
12164
+ children: [
12165
+ styleNode,
12166
+ children
12167
+ ]
11565
12168
  }
11566
12169
  );
11567
12170
  });
@@ -11591,11 +12194,13 @@ var Grid = React36__default.forwardRef(function Grid2({
11591
12194
  justify,
11592
12195
  alignContent,
11593
12196
  justifyContent,
12197
+ sx,
11594
12198
  className = "",
11595
12199
  style,
11596
12200
  children,
11597
12201
  ...rest
11598
12202
  }, ref) {
12203
+ const { flatStyle, styleNode, sxAttr } = useSx(sx);
11599
12204
  const responsive = columns != null && typeof columns === "object";
11600
12205
  const uid = React36__default.useId();
11601
12206
  const gridId = responsive ? `g${uid.replace(/[^a-zA-Z0-9]/g, "")}` : null;
@@ -11618,6 +12223,7 @@ var Grid = React36__default.forwardRef(function Grid2({
11618
12223
  ref,
11619
12224
  className: `twc-grid ${className}`.trim(),
11620
12225
  "data-twc-grid-id": gridId || void 0,
12226
+ "data-twc-sx": sxAttr,
11621
12227
  style: {
11622
12228
  display: "grid",
11623
12229
  gridTemplateColumns: templateColumns,
@@ -11627,11 +12233,13 @@ var Grid = React36__default.forwardRef(function Grid2({
11627
12233
  justifyItems: justify,
11628
12234
  alignContent,
11629
12235
  justifyContent,
11630
- ...style
12236
+ ...style,
12237
+ ...flatStyle
11631
12238
  },
11632
12239
  ...rest,
11633
12240
  children: [
11634
12241
  responsive ? __twcStyles : null,
12242
+ styleNode,
11635
12243
  children
11636
12244
  ]
11637
12245
  }
@@ -11664,18 +12272,21 @@ var Stack = React36__default.forwardRef(function Stack2({
11664
12272
  pb,
11665
12273
  pl,
11666
12274
  divider,
12275
+ sx,
11667
12276
  className = "",
11668
12277
  style,
11669
12278
  children,
11670
12279
  ...rest
11671
12280
  }, ref) {
12281
+ const { flatStyle, styleNode, sxAttr } = useSx(sx);
11672
12282
  if (Tag2 === "a" && rest.href != null) rest.href = safeHref9(rest.href);
11673
12283
  const kids = divider != null ? React36__default.Children.toArray(children).flatMap((child, i, arr) => i < arr.length - 1 ? [child, /* @__PURE__ */ jsx(React36__default.Fragment, { children: divider }, `twc-stack-div-${i}`)] : [child]) : children;
11674
- return /* @__PURE__ */ jsx(
12284
+ return /* @__PURE__ */ jsxs(
11675
12285
  Tag2,
11676
12286
  {
11677
12287
  ref,
11678
12288
  className: `twc-stack ${className}`.trim(),
12289
+ "data-twc-sx": sxAttr,
11679
12290
  style: {
11680
12291
  display: inline ? "inline-flex" : "flex",
11681
12292
  flexDirection: direction,
@@ -11688,10 +12299,14 @@ var Stack = React36__default.forwardRef(function Stack2({
11688
12299
  paddingBottom: space2(pb ?? py ?? p),
11689
12300
  paddingLeft: space2(pl ?? px ?? p),
11690
12301
  minWidth: 0,
11691
- ...style
12302
+ ...style,
12303
+ ...flatStyle
11692
12304
  },
11693
12305
  ...rest,
11694
- children: kids
12306
+ children: [
12307
+ styleNode,
12308
+ kids
12309
+ ]
11695
12310
  }
11696
12311
  );
11697
12312
  });
@@ -11774,19 +12389,22 @@ var Heading = React36__default.forwardRef(function Heading2({
11774
12389
  align,
11775
12390
  truncate,
11776
12391
  lineClamp,
12392
+ sx,
11777
12393
  className = "",
11778
12394
  style,
11779
12395
  children,
11780
12396
  ...rest
11781
12397
  }, ref) {
12398
+ const { flatStyle, styleNode, sxAttr } = useSx(sx);
11782
12399
  const Tag2 = as || `h${level}`;
11783
12400
  const sz = size || (display ? "display" : LEVEL_SIZE[level]) || "2xl";
11784
12401
  if (Tag2 === "a" && rest.href != null) rest.href = safeHref11(rest.href);
11785
- return /* @__PURE__ */ jsx(
12402
+ return /* @__PURE__ */ jsxs(
11786
12403
  Tag2,
11787
12404
  {
11788
12405
  ref,
11789
12406
  className: `twc-heading ${className}`.trim(),
12407
+ "data-twc-sx": sxAttr,
11790
12408
  style: {
11791
12409
  margin: 0,
11792
12410
  fontFamily: "var(--font-sans)",
@@ -11797,10 +12415,14 @@ var Heading = React36__default.forwardRef(function Heading2({
11797
12415
  fontSize: `var(--text-${sz})`,
11798
12416
  textAlign: align,
11799
12417
  ...lineClamp != null ? { display: "-webkit-box", WebkitBoxOrient: "vertical", WebkitLineClamp: lineClamp, overflow: "hidden" } : truncate ? { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", minWidth: 0 } : null,
11800
- ...style
12418
+ ...style,
12419
+ ...flatStyle
11801
12420
  },
11802
12421
  ...rest,
11803
- children
12422
+ children: [
12423
+ styleNode,
12424
+ children
12425
+ ]
11804
12426
  }
11805
12427
  );
11806
12428
  });
@@ -11833,18 +12455,21 @@ var Text = React36__default.forwardRef(function Text2({
11833
12455
  align,
11834
12456
  truncate,
11835
12457
  lineClamp,
12458
+ sx,
11836
12459
  className = "",
11837
12460
  style,
11838
12461
  children,
11839
12462
  ...rest
11840
12463
  }, ref) {
12464
+ const { flatStyle, styleNode, sxAttr } = useSx(sx);
11841
12465
  if (Tag2 === "a" && rest.href != null) rest.href = safeHref12(rest.href);
11842
12466
  const toneToken = TONE[tone] || TONE.default;
11843
- return /* @__PURE__ */ jsx(
12467
+ return /* @__PURE__ */ jsxs(
11844
12468
  Tag2,
11845
12469
  {
11846
12470
  ref,
11847
12471
  className: `twc-text ${className}`.trim(),
12472
+ "data-twc-sx": sxAttr,
11848
12473
  style: {
11849
12474
  margin: 0,
11850
12475
  fontFamily: "var(--font-sans)",
@@ -11854,10 +12479,14 @@ var Text = React36__default.forwardRef(function Text2({
11854
12479
  fontWeight: weight ? `var(--font-${weight})` : void 0,
11855
12480
  textAlign: align,
11856
12481
  ...lineClamp != null ? { display: "-webkit-box", WebkitBoxOrient: "vertical", WebkitLineClamp: lineClamp, overflow: "hidden" } : truncate ? { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", minWidth: 0 } : null,
11857
- ...style
12482
+ ...style,
12483
+ ...flatStyle
11858
12484
  },
11859
12485
  ...rest,
11860
- children
12486
+ children: [
12487
+ styleNode,
12488
+ children
12489
+ ]
11861
12490
  }
11862
12491
  );
11863
12492
  });