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.cjs CHANGED
@@ -32,17 +32,21 @@ function useScopedStyles(id, css) {
32
32
  const nonce = React36__namespace.useContext(NonceContext);
33
33
  React36__namespace.useInsertionEffect(() => {
34
34
  if (SUPPORTS_STYLE_HOIST) return;
35
- if (typeof document === "undefined" || document.getElementById(id)) return;
36
- const el = document.createElement("style");
37
- el.id = id;
38
- if (nonce) {
39
- el.setAttribute("nonce", nonce);
40
- el.nonce = nonce;
41
- }
42
- el.textContent = css;
43
- document.head.appendChild(el);
35
+ if (typeof document === "undefined") return;
36
+ if (!css) return;
37
+ let el = document.getElementById(id);
38
+ if (!el) {
39
+ el = document.createElement("style");
40
+ el.id = id;
41
+ if (nonce) {
42
+ el.setAttribute("nonce", nonce);
43
+ el.nonce = nonce;
44
+ }
45
+ document.head.appendChild(el);
46
+ }
47
+ if (el.textContent !== css) el.textContent = css;
44
48
  }, [id, css, nonce]);
45
- return SUPPORTS_STYLE_HOIST ? React36__namespace.createElement("style", { href: id, precedence: "twc-ui", nonce }, css) : null;
49
+ return SUPPORTS_STYLE_HOIST && css ? React36__namespace.createElement("style", { href: id, precedence: "twc-ui", nonce }, css) : null;
46
50
  }
47
51
 
48
52
  // components/TwicoProvider.jsx
@@ -228,10 +232,13 @@ function Portal({ children }) {
228
232
  const render = usePortal();
229
233
  return render(children);
230
234
  }
235
+ function encodeScriptLiteral(value) {
236
+ return JSON.stringify(value).replace(/[<>&\u2028\u2029]/g, (c) => "\\u" + c.charCodeAt(0).toString(16).padStart(4, "0"));
237
+ }
231
238
  function getColorSchemeScript({ storageKey = "twico-theme", attribute = "class", defaultScheme } = {}) {
232
- const k = JSON.stringify(storageKey);
233
- const a = JSON.stringify(attribute);
234
- const d = defaultScheme === "dark" || defaultScheme === "light" ? JSON.stringify(defaultScheme) : "null";
239
+ const k = encodeScriptLiteral(storageKey);
240
+ const a = encodeScriptLiteral(attribute);
241
+ const d = defaultScheme === "dark" || defaultScheme === "light" ? encodeScriptLiteral(defaultScheme) : "null";
235
242
  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){}})();`;
236
243
  }
237
244
  function ColorSchemeScript({ storageKey = "twico-theme", attribute = "class", nonce, defaultScheme }) {
@@ -1214,6 +1221,122 @@ function Button({
1214
1221
  }
1215
1222
  );
1216
1223
  }
1224
+ var UNITLESS = /* @__PURE__ */ new Set([
1225
+ "animationIterationCount",
1226
+ "aspectRatio",
1227
+ "borderImageOutset",
1228
+ "borderImageSlice",
1229
+ "borderImageWidth",
1230
+ "boxFlex",
1231
+ "boxFlexGroup",
1232
+ "boxOrdinalGroup",
1233
+ "columnCount",
1234
+ "columns",
1235
+ "flex",
1236
+ "flexGrow",
1237
+ "flexShrink",
1238
+ "flexPositive",
1239
+ "flexNegative",
1240
+ "flexOrder",
1241
+ "gridArea",
1242
+ "gridRow",
1243
+ "gridRowEnd",
1244
+ "gridRowSpan",
1245
+ "gridRowStart",
1246
+ "gridColumn",
1247
+ "gridColumnEnd",
1248
+ "gridColumnSpan",
1249
+ "gridColumnStart",
1250
+ "fontWeight",
1251
+ "lineClamp",
1252
+ "lineHeight",
1253
+ "opacity",
1254
+ "order",
1255
+ "orphans",
1256
+ "tabSize",
1257
+ "widows",
1258
+ "zIndex",
1259
+ "zoom",
1260
+ "fillOpacity",
1261
+ "floodOpacity",
1262
+ "stopOpacity",
1263
+ "strokeDasharray",
1264
+ "strokeDashoffset",
1265
+ "strokeMiterlimit",
1266
+ "strokeOpacity",
1267
+ "strokeWidth",
1268
+ "scale"
1269
+ ]);
1270
+ var isObject = (v) => v != null && typeof v === "object" && !Array.isArray(v);
1271
+ var isNestedKey = (k, v) => isObject(v) || k.charCodeAt(0) === 38 || k.charCodeAt(0) === 64 || k.charCodeAt(0) === 58;
1272
+ function kebab(prop) {
1273
+ if (prop.startsWith("--")) return prop;
1274
+ return prop.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
1275
+ }
1276
+ function cssValue(key, value) {
1277
+ if (typeof value !== "number") return String(value);
1278
+ if (value === 0 || key.startsWith("--") || UNITLESS.has(key)) return String(value);
1279
+ return `${value}px`;
1280
+ }
1281
+ function resolveSelector(key, scope) {
1282
+ if (key.includes("&")) return key.replace(/&/g, scope);
1283
+ if (key.charCodeAt(0) === 58) return `${scope}${key}`;
1284
+ return `${scope} ${key}`;
1285
+ }
1286
+ function compileSx(obj, selector) {
1287
+ if (!isObject(obj)) return "";
1288
+ let decls = "";
1289
+ let rules = "";
1290
+ for (const key of Object.keys(obj)) {
1291
+ const value = obj[key];
1292
+ if (isNestedKey(key, value)) {
1293
+ if (!isObject(value)) continue;
1294
+ if (key.charCodeAt(0) === 64) {
1295
+ const inner = compileSx(value, selector);
1296
+ if (inner) rules += `${key} { ${inner.trim()} }
1297
+ `;
1298
+ } else {
1299
+ rules += compileSx(value, resolveSelector(key, selector));
1300
+ }
1301
+ } else {
1302
+ decls += `${kebab(key)}: ${cssValue(key, value)}; `;
1303
+ }
1304
+ }
1305
+ let out = "";
1306
+ if (decls) out += `${selector} { ${decls.trimEnd()} }
1307
+ `;
1308
+ out += rules;
1309
+ return out;
1310
+ }
1311
+ function buildSx(sx, uid) {
1312
+ if (!isObject(sx)) return { flatStyle: void 0, css: "" };
1313
+ const scope = `[data-twc-sx="${uid}"]`;
1314
+ let flatStyle;
1315
+ let css = "";
1316
+ for (const key of Object.keys(sx)) {
1317
+ const value = sx[key];
1318
+ if (isNestedKey(key, value)) {
1319
+ if (!isObject(value)) continue;
1320
+ if (key.charCodeAt(0) === 64) {
1321
+ const inner = compileSx(value, scope);
1322
+ if (inner) css += `${key} { ${inner.trim()} }
1323
+ `;
1324
+ } else {
1325
+ css += compileSx(value, resolveSelector(key, scope));
1326
+ }
1327
+ } else {
1328
+ (flatStyle || (flatStyle = {}))[key] = value;
1329
+ }
1330
+ }
1331
+ return { flatStyle, css };
1332
+ }
1333
+ function useSx(sx) {
1334
+ const rawId = React36__namespace.default.useId();
1335
+ const uid = `sx${rawId.replace(/[^a-zA-Z0-9]/g, "")}`;
1336
+ const { flatStyle, css } = React36__namespace.default.useMemo(() => buildSx(sx, uid), [sx, uid]);
1337
+ const styleNode = useScopedStyles(`twc-sx-${uid}`, css);
1338
+ return { flatStyle, styleNode, sxAttr: css ? uid : void 0 };
1339
+ }
1217
1340
  var CARD_CSS = `
1218
1341
  .twc-card {
1219
1342
  display: flex; flex-direction: column;
@@ -1253,10 +1376,13 @@ function Card({
1253
1376
  padding = "md",
1254
1377
  interactive = false,
1255
1378
  fullHeight = false,
1379
+ sx,
1256
1380
  className = "",
1381
+ style,
1257
1382
  ...rest
1258
1383
  }) {
1259
1384
  const __twcStyles = useScopedStyles("twc-card-styles", CARD_CSS);
1385
+ const { flatStyle, styleNode, sxAttr } = useSx(sx);
1260
1386
  return /* @__PURE__ */ jsxRuntime.jsxs(
1261
1387
  "div",
1262
1388
  {
@@ -1265,9 +1391,12 @@ function Card({
1265
1391
  "data-pad": padding,
1266
1392
  "data-interactive": interactive || void 0,
1267
1393
  "data-full": fullHeight || void 0,
1394
+ "data-twc-sx": sxAttr,
1395
+ style: style || flatStyle ? { ...style, ...flatStyle } : void 0,
1268
1396
  ...rest,
1269
1397
  children: [
1270
1398
  __twcStyles,
1399
+ styleNode,
1271
1400
  title || subtitle || actions ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-card__header", children: [
1272
1401
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-card__heading", children: [
1273
1402
  title ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-card__title", children: title }) : null,
@@ -1686,6 +1815,10 @@ var COLORPICKER_CSS = `
1686
1815
  .twc-cp__hue { position: relative; height: 12px; margin-top: var(--space-3); border-radius: var(--radius-full); cursor: pointer; touch-action: none;
1687
1816
  background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%); }
1688
1817
  .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; }
1818
+ .twc-cp__alpha { position: relative; height: 12px; margin-top: var(--space-2); border-radius: var(--radius-full); cursor: pointer; touch-action: none;
1819
+ background-image: linear-gradient(to right, transparent, var(--_solid, #000)), conic-gradient(#c8c8c8 0 25%, #fff 0 50%, #c8c8c8 0 75%, #fff 0);
1820
+ background-size: 100% 100%, 10px 10px; }
1821
+ .twc-cp__alpha:focus-visible { outline: none; box-shadow: var(--ring); }
1689
1822
  .twc-cp__foot { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-3); }
1690
1823
  .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;
1691
1824
  color: var(--color-text); background: var(--color-surface); border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-sm); outline: none; }
@@ -1723,11 +1856,17 @@ function rgbToHex(r, g, b) {
1723
1856
  function hsvToHex(h, s, v) {
1724
1857
  return rgbToHex(...hsvToRgb(h, s, v));
1725
1858
  }
1859
+ function hsvaToHex8(h, s, v, a) {
1860
+ return rgbToHex(...hsvToRgb(h, s, v)) + Math.round(Math.min(1, Math.max(0, a)) * 255).toString(16).padStart(2, "0");
1861
+ }
1726
1862
  function hexToHsv(hex) {
1727
- const m = /^#?([0-9a-f]{6})$/i.exec(hex || "");
1863
+ let str = String(hex || "").replace(/^#/, "");
1864
+ if (/^[0-9a-f]{3}$/i.test(str)) str = str.split("").map((c) => c + c).join("");
1865
+ const m = /^([0-9a-f]{6})([0-9a-f]{2})?$/i.exec(str);
1728
1866
  if (!m) return null;
1729
1867
  const int = parseInt(m[1], 16);
1730
1868
  const r = (int >> 16) / 255, g = (int >> 8 & 255) / 255, b = (int & 255) / 255;
1869
+ const a = m[2] != null ? parseInt(m[2], 16) / 255 : 1;
1731
1870
  const max = Math.max(r, g, b), min = Math.min(r, g, b), d = max - min;
1732
1871
  let h = 0;
1733
1872
  if (d) {
@@ -1737,7 +1876,7 @@ function hexToHsv(hex) {
1737
1876
  h *= 60;
1738
1877
  if (h < 0) h += 360;
1739
1878
  }
1740
- return { h, s: max ? d / max * 100 : 0, v: max * 100 };
1879
+ return { h, s: max ? d / max * 100 : 0, v: max * 100, a };
1741
1880
  }
1742
1881
  var DEFAULT_PRESETS = ["#6366F1", "#0EA5E9", "#14B8A6", "#22C55E", "#F59E0B", "#F43F5E", "#8B5CF6", "#0F172A"];
1743
1882
  function ColorPicker({
@@ -1749,6 +1888,7 @@ function ColorPicker({
1749
1888
  defaultValue = "#6366F1",
1750
1889
  presets = DEFAULT_PRESETS,
1751
1890
  disabled = false,
1891
+ alpha = false,
1752
1892
  tone = "primary",
1753
1893
  onChange,
1754
1894
  className = "",
@@ -1758,16 +1898,18 @@ function ColorPicker({
1758
1898
  const __twcStyles1 = useScopedStyles("twc-colorpicker-styles", COLORPICKER_CSS);
1759
1899
  const [internal, setInternal] = React36__namespace.default.useState(defaultValue);
1760
1900
  const hex = (value !== void 0 ? value : internal) || "#000000";
1901
+ 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;
1761
1902
  const lastValidRef = React36__namespace.default.useRef(hex);
1762
- if (/^#[0-9a-f]{6}$/i.test(hex)) lastValidRef.current = hex;
1903
+ if (hexRe.test(hex)) lastValidRef.current = hex;
1763
1904
  const [open, setOpen] = React36__namespace.default.useState(false);
1764
- const [hsv, setHsv] = React36__namespace.default.useState(() => hexToHsv(hex) || { h: 239, s: 60, v: 94 });
1905
+ const [hsv, setHsv] = React36__namespace.default.useState(() => hexToHsv(hex) || { h: 239, s: 60, v: 94, a: 1 });
1765
1906
  const [coords, setCoords] = React36__namespace.default.useState(null);
1766
1907
  const wrapRef = React36__namespace.default.useRef(null);
1767
1908
  const triggerRef = React36__namespace.default.useRef(null);
1768
1909
  const popRef = React36__namespace.default.useRef(null);
1769
1910
  const areaRef = React36__namespace.default.useRef(null);
1770
1911
  const hueRef = React36__namespace.default.useRef(null);
1912
+ const alphaRef = React36__namespace.default.useRef(null);
1771
1913
  const POP_W = 240;
1772
1914
  const place = React36__namespace.default.useCallback(() => {
1773
1915
  const el = triggerRef.current;
@@ -1820,10 +1962,10 @@ function ColorPicker({
1820
1962
  }, [open]);
1821
1963
  useFocusTrap(popRef, open && !!coords, { initialFocus: areaRef });
1822
1964
  const commit = (next) => {
1823
- const h = hsvToHex(next.h, next.s, next.v);
1965
+ const out = alpha ? hsvaToHex8(next.h, next.s, next.v, next.a ?? 1) : hsvToHex(next.h, next.s, next.v);
1824
1966
  setHsv(next);
1825
- if (value === void 0) setInternal(h);
1826
- onChange?.(h);
1967
+ if (value === void 0) setInternal(out);
1968
+ onChange?.(out);
1827
1969
  };
1828
1970
  const dragArea = (e) => {
1829
1971
  const r = areaRef.current.getBoundingClientRect();
@@ -1867,6 +2009,21 @@ function ColorPicker({
1867
2009
  e.preventDefault();
1868
2010
  commit({ ...hsv, h });
1869
2011
  };
2012
+ const dragAlpha = (e) => {
2013
+ const r = alphaRef.current.getBoundingClientRect();
2014
+ const a = Math.min(1, Math.max(0, (e.clientX - r.left) / r.width));
2015
+ commit({ ...hsv, a });
2016
+ };
2017
+ const onAlphaKeyDown = (e) => {
2018
+ let a = hsv.a ?? 1;
2019
+ if (e.key === "ArrowRight" || e.key === "ArrowUp") a = Math.min(1, a + 0.01);
2020
+ else if (e.key === "ArrowLeft" || e.key === "ArrowDown") a = Math.max(0, a - 0.01);
2021
+ else if (e.key === "Home") a = 0;
2022
+ else if (e.key === "End") a = 1;
2023
+ else return;
2024
+ e.preventDefault();
2025
+ commit({ ...hsv, a: Math.round(a * 100) / 100 });
2026
+ };
1870
2027
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `twc-cp twc-field ${className}`, ref: wrapRef, ...rest, children: [
1871
2028
  __twcStyles0,
1872
2029
  __twcStyles1,
@@ -1953,6 +2110,23 @@ function ColorPicker({
1953
2110
  children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-cp__hue-knob", style: { left: `${hsv.h / 360 * 100}%` } })
1954
2111
  }
1955
2112
  ),
2113
+ alpha ? /* @__PURE__ */ jsxRuntime.jsx(
2114
+ "div",
2115
+ {
2116
+ className: "twc-cp__alpha",
2117
+ ref: alphaRef,
2118
+ onPointerDown: startDrag(dragAlpha),
2119
+ role: "slider",
2120
+ tabIndex: 0,
2121
+ "aria-label": "Alpha",
2122
+ "aria-valuemin": 0,
2123
+ "aria-valuemax": 100,
2124
+ "aria-valuenow": Math.round((hsv.a ?? 1) * 100),
2125
+ onKeyDown: onAlphaKeyDown,
2126
+ style: { "--_solid": hsvToHex(hsv.h, hsv.s, hsv.v) },
2127
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-cp__hue-knob", style: { left: `${(hsv.a ?? 1) * 100}%` } })
2128
+ }
2129
+ ) : null,
1956
2130
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-cp__foot", children: [
1957
2131
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-cp__swatch", style: { background: hex } }),
1958
2132
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1961,10 +2135,10 @@ function ColorPicker({
1961
2135
  className: "twc-cp__hex",
1962
2136
  "data-tone": tone,
1963
2137
  value: hex,
1964
- maxLength: 7,
2138
+ maxLength: alpha ? 9 : 7,
1965
2139
  "aria-label": "Hex color",
1966
- "aria-invalid": !/^#[0-9a-f]{6}$/i.test(hex) || void 0,
1967
- "data-invalid": !/^#[0-9a-f]{6}$/i.test(hex) || void 0,
2140
+ "aria-invalid": !hexRe.test(hex) || void 0,
2141
+ "data-invalid": !hexRe.test(hex) || void 0,
1968
2142
  onChange: (e) => {
1969
2143
  let v = e.target.value;
1970
2144
  if (!v.startsWith("#")) v = "#" + v;
@@ -1974,7 +2148,7 @@ function ColorPicker({
1974
2148
  if (p) setHsv(p);
1975
2149
  },
1976
2150
  onBlur: () => {
1977
- if (/^#[0-9a-f]{6}$/i.test(hex)) return;
2151
+ if (hexRe.test(hex)) return;
1978
2152
  const revert = lastValidRef.current;
1979
2153
  if (revert === hex) return;
1980
2154
  if (value === void 0) setInternal(revert);
@@ -2132,6 +2306,8 @@ function Combobox({
2132
2306
  loading = false,
2133
2307
  emptyText = "No results found",
2134
2308
  name,
2309
+ virtualized = false,
2310
+ overscan = 8,
2135
2311
  id,
2136
2312
  className = "",
2137
2313
  onFocus,
@@ -2150,6 +2326,8 @@ function Combobox({
2150
2326
  const [query, setQuery] = React36__namespace.default.useState("");
2151
2327
  const [active, setActive] = React36__namespace.default.useState(0);
2152
2328
  const [coords, setCoords] = React36__namespace.default.useState(null);
2329
+ const [scrollTop, setScrollTop] = React36__namespace.default.useState(0);
2330
+ const [listH, setListH] = React36__namespace.default.useState(260);
2153
2331
  const wrapRef = React36__namespace.default.useRef(null);
2154
2332
  const controlRef = React36__namespace.default.useRef(null);
2155
2333
  const inputRef = React36__namespace.default.useRef(null);
@@ -2162,6 +2340,26 @@ function Combobox({
2162
2340
  return groups.map((g) => ({ group: g.group, options: fn ? g.options.filter((o) => fn(o, q)) : g.options })).filter((g) => g.options.length);
2163
2341
  }, [groups, query, filter]);
2164
2342
  const visible = React36__namespace.default.useMemo(() => fGroups.flatMap((g) => g.options), [fGroups]);
2343
+ const rowH = React36__namespace.default.useMemo(() => visible.some((o) => o.description) ? 48 : 36, [visible]);
2344
+ const GROUP_H = 30;
2345
+ const { rows, totalH } = React36__namespace.default.useMemo(() => {
2346
+ if (!virtualized) return { rows: [], totalH: 0 };
2347
+ const rows2 = [];
2348
+ let off = 0;
2349
+ let optIdx = -1;
2350
+ fGroups.forEach((g, gi) => {
2351
+ if (g.group) {
2352
+ rows2.push({ kind: "group", gi, label: g.group, top: off, h: GROUP_H });
2353
+ off += GROUP_H;
2354
+ }
2355
+ g.options.forEach((o) => {
2356
+ optIdx += 1;
2357
+ rows2.push({ kind: "option", o, idx: optIdx, top: off, h: rowH });
2358
+ off += rowH;
2359
+ });
2360
+ });
2361
+ return { rows: rows2, totalH: off };
2362
+ }, [virtualized, fGroups, rowH]);
2165
2363
  const nextEnabled = (from, dir) => {
2166
2364
  let i = from + dir;
2167
2365
  while (i >= 0 && i < visible.length) {
@@ -2215,8 +2413,27 @@ function Combobox({
2215
2413
  setActive(idx >= 0 ? idx : 0);
2216
2414
  }, [open]);
2217
2415
  React36__namespace.default.useEffect(() => {
2218
- if (open) ensureVisible(listRef.current, activeRef.current);
2219
- }, [active, open]);
2416
+ if (!open || !virtualized) return;
2417
+ const el = listRef.current;
2418
+ if (el && el.clientHeight) setListH(el.clientHeight);
2419
+ }, [open, virtualized]);
2420
+ React36__namespace.default.useEffect(() => {
2421
+ if (!open) return;
2422
+ if (!virtualized) {
2423
+ ensureVisible(listRef.current, activeRef.current);
2424
+ return;
2425
+ }
2426
+ const el = listRef.current;
2427
+ if (!el) return;
2428
+ const row = rows.find((r) => r.kind === "option" && r.idx === active);
2429
+ if (!row) return;
2430
+ const vh = el.clientHeight || listH;
2431
+ let next = el.scrollTop;
2432
+ if (row.top < el.scrollTop) next = row.top;
2433
+ else if (row.top + row.h > el.scrollTop + vh) next = row.top + row.h - vh;
2434
+ if (next !== el.scrollTop) el.scrollTop = next;
2435
+ setScrollTop(next);
2436
+ }, [active, open, virtualized, rows, listH]);
2220
2437
  function openMenu() {
2221
2438
  if (disabled) return;
2222
2439
  setQuery("");
@@ -2261,45 +2478,60 @@ function Combobox({
2261
2478
  const activeId = open && visible[active] ? optionId(active) : void 0;
2262
2479
  const descId = `${fieldId}-desc`;
2263
2480
  const describedBy = error || hint ? descId : void 0;
2481
+ const renderOption = (o, idx) => {
2482
+ const isSel = o.value === current;
2483
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2484
+ "button",
2485
+ {
2486
+ id: optionId(idx),
2487
+ type: "button",
2488
+ className: "twc-opt",
2489
+ role: "option",
2490
+ "aria-selected": isSel,
2491
+ ref: idx === active ? activeRef : null,
2492
+ disabled: o.disabled || void 0,
2493
+ "aria-disabled": o.disabled || void 0,
2494
+ "data-disabled": o.disabled || void 0,
2495
+ "data-selected": isSel || void 0,
2496
+ "data-active": idx === active || void 0,
2497
+ onMouseEnter: () => {
2498
+ if (!o.disabled) setActive(idx);
2499
+ },
2500
+ onMouseDown: (e) => e.preventDefault(),
2501
+ onClick: () => commit(o.value),
2502
+ children: [
2503
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-opt__main", children: [
2504
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__label", children: o.label }),
2505
+ o.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__desc", children: o.description }) : null
2506
+ ] }),
2507
+ isSel ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__check", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.6", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 6 9 17l-5-5" }) }) }) : null
2508
+ ]
2509
+ },
2510
+ o.value
2511
+ );
2512
+ };
2513
+ let vTop = 0, vBottom = 0, vShown = rows;
2514
+ if (virtualized && rows.length) {
2515
+ const winTop = scrollTop - overscan * rowH;
2516
+ const winBottom = scrollTop + listH + overscan * rowH;
2517
+ vShown = rows.filter((r) => r.top + r.h > winTop && r.top < winBottom);
2518
+ vTop = vShown.length ? vShown[0].top : 0;
2519
+ vBottom = vShown.length ? totalH - (vShown[vShown.length - 1].top + vShown[vShown.length - 1].h) : totalH;
2520
+ }
2264
2521
  let counter = -1;
2265
- const popInner = /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__list", ref: listRef, children: loading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-pop__loading", role: "status", children: [
2522
+ const popInner = /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__list", ref: listRef, onScroll: virtualized ? (e) => setScrollTop(e.currentTarget.scrollTop) : void 0, children: loading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-pop__loading", role: "status", children: [
2266
2523
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-pop__spinner", "aria-hidden": "true" }),
2267
2524
  "Loading\u2026"
2268
- ] }) : visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__empty", children: emptyText }) : fGroups.map((g, gi) => /* @__PURE__ */ jsxRuntime.jsxs(React36__namespace.default.Fragment, { children: [
2525
+ ] }) : visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__empty", children: emptyText }) : virtualized ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2526
+ vTop > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { height: vTop } }) : null,
2527
+ vShown.map((r) => r.kind === "group" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__group", children: r.label }, `g${r.gi}`) : renderOption(r.o, r.idx)),
2528
+ vBottom > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { height: vBottom } }) : null
2529
+ ] }) : fGroups.map((g, gi) => /* @__PURE__ */ jsxRuntime.jsxs(React36__namespace.default.Fragment, { children: [
2269
2530
  g.group ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__group", children: g.group }) : null,
2270
2531
  g.options.map((o) => {
2271
2532
  counter += 1;
2272
2533
  const idx = counter;
2273
- const isSel = o.value === current;
2274
- return /* @__PURE__ */ jsxRuntime.jsxs(
2275
- "button",
2276
- {
2277
- id: optionId(idx),
2278
- type: "button",
2279
- className: "twc-opt",
2280
- role: "option",
2281
- "aria-selected": isSel,
2282
- ref: idx === active ? activeRef : null,
2283
- disabled: o.disabled || void 0,
2284
- "aria-disabled": o.disabled || void 0,
2285
- "data-disabled": o.disabled || void 0,
2286
- "data-selected": isSel || void 0,
2287
- "data-active": idx === active || void 0,
2288
- onMouseEnter: () => {
2289
- if (!o.disabled) setActive(idx);
2290
- },
2291
- onMouseDown: (e) => e.preventDefault(),
2292
- onClick: () => commit(o.value),
2293
- children: [
2294
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-opt__main", children: [
2295
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__label", children: o.label }),
2296
- o.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__desc", children: o.description }) : null
2297
- ] }),
2298
- isSel ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__check", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.6", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 6 9 17l-5-5" }) }) }) : null
2299
- ]
2300
- },
2301
- o.value
2302
- );
2534
+ return renderOption(o, idx);
2303
2535
  })
2304
2536
  ] }, gi)) });
2305
2537
  const RD = { createPortal: typeof reactDom.createPortal === "function" ? reactDom.createPortal : typeof window !== "undefined" && window.ReactDOM && window.ReactDOM.createPortal };
@@ -2896,6 +3128,8 @@ function Select({
2896
3128
  loading = false,
2897
3129
  emptyText = "No results found",
2898
3130
  name,
3131
+ virtualized = false,
3132
+ overscan = 8,
2899
3133
  id,
2900
3134
  className = "",
2901
3135
  onClick,
@@ -2916,6 +3150,8 @@ function Select({
2916
3150
  const [active, setActive] = React36__namespace.default.useState(0);
2917
3151
  const [coords, setCoords] = React36__namespace.default.useState(null);
2918
3152
  const [query, setQuery] = React36__namespace.default.useState("");
3153
+ const [scrollTop, setScrollTop] = React36__namespace.default.useState(0);
3154
+ const [listH, setListH] = React36__namespace.default.useState(260);
2919
3155
  const wrapRef = React36__namespace.default.useRef(null);
2920
3156
  const triggerRef = React36__namespace.default.useRef(null);
2921
3157
  const popRef = React36__namespace.default.useRef(null);
@@ -2929,6 +3165,26 @@ function Select({
2929
3165
  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);
2930
3166
  }, [groups, query]);
2931
3167
  const visible = React36__namespace.default.useMemo(() => fGroups.flatMap((g) => g.options), [fGroups]);
3168
+ const rowH = React36__namespace.default.useMemo(() => visible.some((o) => o.description) ? 48 : 36, [visible]);
3169
+ const GROUP_H = 30;
3170
+ const { rows, totalH } = React36__namespace.default.useMemo(() => {
3171
+ if (!virtualized) return { rows: [], totalH: 0 };
3172
+ const rows2 = [];
3173
+ let off = 0;
3174
+ let optIdx = -1;
3175
+ fGroups.forEach((g, gi) => {
3176
+ if (g.group) {
3177
+ rows2.push({ kind: "group", gi, label: g.group, top: off, h: GROUP_H });
3178
+ off += GROUP_H;
3179
+ }
3180
+ g.options.forEach((o) => {
3181
+ optIdx += 1;
3182
+ rows2.push({ kind: "option", o, idx: optIdx, top: off, h: rowH });
3183
+ off += rowH;
3184
+ });
3185
+ });
3186
+ return { rows: rows2, totalH: off };
3187
+ }, [virtualized, fGroups, rowH]);
2932
3188
  const selected = flat.find((o) => o.value === current);
2933
3189
  const nextEnabled = (from, dir) => {
2934
3190
  let i = from + dir;
@@ -2985,8 +3241,27 @@ function Select({
2985
3241
  setActive(0);
2986
3242
  }, [query]);
2987
3243
  React36__namespace.default.useEffect(() => {
2988
- if (open) ensureVisible2(listRef.current, activeRef.current);
2989
- }, [active, open]);
3244
+ if (!open || !virtualized) return;
3245
+ const el = listRef.current;
3246
+ if (el && el.clientHeight) setListH(el.clientHeight);
3247
+ }, [open, virtualized, render]);
3248
+ React36__namespace.default.useEffect(() => {
3249
+ if (!open) return;
3250
+ if (!virtualized) {
3251
+ ensureVisible2(listRef.current, activeRef.current);
3252
+ return;
3253
+ }
3254
+ const el = listRef.current;
3255
+ if (!el) return;
3256
+ const row = rows.find((r) => r.kind === "option" && r.idx === active);
3257
+ if (!row) return;
3258
+ const vh = el.clientHeight || listH;
3259
+ let next = el.scrollTop;
3260
+ if (row.top < el.scrollTop) next = row.top;
3261
+ else if (row.top + row.h > el.scrollTop + vh) next = row.top + row.h - vh;
3262
+ if (next !== el.scrollTop) el.scrollTop = next;
3263
+ setScrollTop(next);
3264
+ }, [active, open, virtualized, rows, listH]);
2990
3265
  React36__namespace.default.useEffect(() => {
2991
3266
  if (open) {
2992
3267
  setRender(true);
@@ -3060,6 +3335,45 @@ function Select({
3060
3335
  const activeId = open && visible[active] ? optionId(active) : void 0;
3061
3336
  const descId = `${fieldId}-desc`;
3062
3337
  const describedBy = error || hint ? descId : void 0;
3338
+ const renderOption = (o, idx) => {
3339
+ const isSel = o.value === current;
3340
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3341
+ "button",
3342
+ {
3343
+ id: optionId(idx),
3344
+ type: "button",
3345
+ className: "twc-opt",
3346
+ role: "option",
3347
+ "aria-selected": isSel,
3348
+ ref: idx === active ? activeRef : null,
3349
+ disabled: o.disabled || void 0,
3350
+ "aria-disabled": o.disabled || void 0,
3351
+ "data-disabled": o.disabled || void 0,
3352
+ "data-selected": isSel || void 0,
3353
+ "data-active": idx === active || void 0,
3354
+ onMouseEnter: () => {
3355
+ if (!o.disabled) setActive(idx);
3356
+ },
3357
+ onClick: () => commit(o.value),
3358
+ children: [
3359
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-opt__main", children: [
3360
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__label", children: o.label }),
3361
+ o.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__desc", children: o.description }) : null
3362
+ ] }),
3363
+ isSel ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__check", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.6", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 6 9 17l-5-5" }) }) }) : null
3364
+ ]
3365
+ },
3366
+ o.value
3367
+ );
3368
+ };
3369
+ let vTop = 0, vBottom = 0, vShown = rows;
3370
+ if (virtualized && rows.length) {
3371
+ const winTop = scrollTop - overscan * rowH;
3372
+ const winBottom = scrollTop + listH + overscan * rowH;
3373
+ vShown = rows.filter((r) => r.top + r.h > winTop && r.top < winBottom);
3374
+ vTop = vShown.length ? vShown[0].top : 0;
3375
+ vBottom = vShown.length ? totalH - (vShown[vShown.length - 1].top + vShown[vShown.length - 1].h) : totalH;
3376
+ }
3063
3377
  let counter = -1;
3064
3378
  const popInner = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3065
3379
  showSearch ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-pop__search", children: [
@@ -3087,43 +3401,19 @@ function Select({
3087
3401
  searchRef.current?.focus();
3088
3402
  }, children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }) }) : null
3089
3403
  ] }) : null,
3090
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__list", ref: listRef, children: loading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-pop__loading", role: "status", children: [
3404
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__list", ref: listRef, onScroll: virtualized ? (e) => setScrollTop(e.currentTarget.scrollTop) : void 0, children: loading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-pop__loading", role: "status", children: [
3091
3405
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-pop__spinner", "aria-hidden": "true" }),
3092
3406
  "Loading\u2026"
3093
- ] }) : visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__empty", children: emptyText }) : fGroups.map((g, gi) => /* @__PURE__ */ jsxRuntime.jsxs(React36__namespace.default.Fragment, { children: [
3407
+ ] }) : visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__empty", children: emptyText }) : virtualized ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3408
+ vTop > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { height: vTop } }) : null,
3409
+ vShown.map((r) => r.kind === "group" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__group", children: r.label }, `g${r.gi}`) : renderOption(r.o, r.idx)),
3410
+ vBottom > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { height: vBottom } }) : null
3411
+ ] }) : fGroups.map((g, gi) => /* @__PURE__ */ jsxRuntime.jsxs(React36__namespace.default.Fragment, { children: [
3094
3412
  g.group ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__group", children: g.group }) : null,
3095
3413
  g.options.map((o) => {
3096
3414
  counter += 1;
3097
3415
  const idx = counter;
3098
- const isSel = o.value === current;
3099
- return /* @__PURE__ */ jsxRuntime.jsxs(
3100
- "button",
3101
- {
3102
- id: optionId(idx),
3103
- type: "button",
3104
- className: "twc-opt",
3105
- role: "option",
3106
- "aria-selected": isSel,
3107
- ref: idx === active ? activeRef : null,
3108
- disabled: o.disabled || void 0,
3109
- "aria-disabled": o.disabled || void 0,
3110
- "data-disabled": o.disabled || void 0,
3111
- "data-selected": isSel || void 0,
3112
- "data-active": idx === active || void 0,
3113
- onMouseEnter: () => {
3114
- if (!o.disabled) setActive(idx);
3115
- },
3116
- onClick: () => commit(o.value),
3117
- children: [
3118
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-opt__main", children: [
3119
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__label", children: o.label }),
3120
- o.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__desc", children: o.description }) : null
3121
- ] }),
3122
- isSel ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__check", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.6", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 6 9 17l-5-5" }) }) }) : null
3123
- ]
3124
- },
3125
- o.value
3126
- );
3416
+ return renderOption(o, idx);
3127
3417
  })
3128
3418
  ] }, gi)) }),
3129
3419
  /* @__PURE__ */ jsxRuntime.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"}` })
@@ -3669,6 +3959,8 @@ function MultiSelect({
3669
3959
  name,
3670
3960
  max,
3671
3961
  maxTagCount,
3962
+ virtualized = false,
3963
+ overscan = 8,
3672
3964
  id,
3673
3965
  className = "",
3674
3966
  onFocus,
@@ -3686,6 +3978,8 @@ function MultiSelect({
3686
3978
  const [query, setQuery] = React36__namespace.default.useState("");
3687
3979
  const [active, setActive] = React36__namespace.default.useState(0);
3688
3980
  const [coords, setCoords] = React36__namespace.default.useState(null);
3981
+ const [scrollTop, setScrollTop] = React36__namespace.default.useState(0);
3982
+ const [listH, setListH] = React36__namespace.default.useState(260);
3689
3983
  const wrapRef = React36__namespace.default.useRef(null);
3690
3984
  const controlRef = React36__namespace.default.useRef(null);
3691
3985
  const inputRef = React36__namespace.default.useRef(null);
@@ -3697,6 +3991,26 @@ function MultiSelect({
3697
3991
  [groups, query]
3698
3992
  );
3699
3993
  const visible = React36__namespace.default.useMemo(() => fGroups.flatMap((g) => g.options), [fGroups]);
3994
+ const rowH = React36__namespace.default.useMemo(() => visible.some((o) => o.description) ? 48 : 36, [visible]);
3995
+ const GROUP_H = 30;
3996
+ const { rows, totalH } = React36__namespace.default.useMemo(() => {
3997
+ if (!virtualized) return { rows: [], totalH: 0 };
3998
+ const rows2 = [];
3999
+ let off = 0;
4000
+ let optIdx = -1;
4001
+ fGroups.forEach((g, gi) => {
4002
+ if (g.group) {
4003
+ rows2.push({ kind: "group", gi, label: g.group, top: off, h: GROUP_H });
4004
+ off += GROUP_H;
4005
+ }
4006
+ g.options.forEach((o) => {
4007
+ optIdx += 1;
4008
+ rows2.push({ kind: "option", o, idx: optIdx, top: off, h: rowH });
4009
+ off += rowH;
4010
+ });
4011
+ });
4012
+ return { rows: rows2, totalH: off };
4013
+ }, [virtualized, fGroups, rowH]);
3700
4014
  const capReached = max != null && (value !== void 0 ? value : internal).length >= max;
3701
4015
  const isOptDisabled = (o) => o.disabled || capReached && !(value !== void 0 ? value : internal).includes(o.value);
3702
4016
  const nextEnabled = (from, dir) => {
@@ -3748,8 +4062,27 @@ function MultiSelect({
3748
4062
  setActive(0);
3749
4063
  }, [query]);
3750
4064
  React36__namespace.default.useEffect(() => {
3751
- if (open) ensureVisible3(listRef.current, activeRef.current);
3752
- }, [active, open]);
4065
+ if (!open || !virtualized) return;
4066
+ const el = listRef.current;
4067
+ if (el && el.clientHeight) setListH(el.clientHeight);
4068
+ }, [open, virtualized]);
4069
+ React36__namespace.default.useEffect(() => {
4070
+ if (!open) return;
4071
+ if (!virtualized) {
4072
+ ensureVisible3(listRef.current, activeRef.current);
4073
+ return;
4074
+ }
4075
+ const el = listRef.current;
4076
+ if (!el) return;
4077
+ const row = rows.find((r) => r.kind === "option" && r.idx === active);
4078
+ if (!row) return;
4079
+ const vh = el.clientHeight || listH;
4080
+ let next = el.scrollTop;
4081
+ if (row.top < el.scrollTop) next = row.top;
4082
+ else if (row.top + row.h > el.scrollTop + vh) next = row.top + row.h - vh;
4083
+ if (next !== el.scrollTop) el.scrollTop = next;
4084
+ setScrollTop(next);
4085
+ }, [active, open, virtualized, rows, listH]);
3753
4086
  function commit(next) {
3754
4087
  if (value === void 0) setInternal(next);
3755
4088
  onChange?.(next);
@@ -3786,48 +4119,74 @@ function MultiSelect({
3786
4119
  const activeId = open && visible[active] ? optionId(active) : void 0;
3787
4120
  const descId = `${fieldId}-desc`;
3788
4121
  const describedBy = error || hint ? descId : void 0;
3789
- let counter = -1;
3790
- const popInner = /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__list", id: listboxId, ref: listRef, role: "listbox", "aria-multiselectable": "true", children: loading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-pop__loading", role: "status", children: [
3791
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-pop__spinner", "aria-hidden": "true" }),
3792
- "Loading\u2026"
3793
- ] }) : visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__empty", children: emptyText }) : fGroups.map((g, gi) => /* @__PURE__ */ jsxRuntime.jsxs(React36__namespace.default.Fragment, { children: [
3794
- g.group ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__group", children: g.group }) : null,
3795
- g.options.map((o) => {
3796
- counter += 1;
3797
- const idx = counter;
3798
- const isSel = selected.includes(o.value);
3799
- const optDisabled = isOptDisabled(o);
3800
- return /* @__PURE__ */ jsxRuntime.jsxs(
3801
- "button",
3802
- {
3803
- id: optionId(idx),
3804
- type: "button",
3805
- className: "twc-opt",
3806
- role: "option",
3807
- "aria-selected": isSel,
3808
- ref: idx === active ? activeRef : null,
3809
- disabled: optDisabled || void 0,
3810
- "aria-disabled": optDisabled || void 0,
3811
- "data-disabled": optDisabled || void 0,
3812
- "data-selected": isSel || void 0,
3813
- "data-active": idx === active || void 0,
3814
- onMouseEnter: () => {
3815
- if (!optDisabled) setActive(idx);
3816
- },
3817
- onMouseDown: (e) => e.preventDefault(),
3818
- onClick: () => toggle(o.value),
3819
- children: [
3820
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__box", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3.2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 6 9 17l-5-5" }) }) }),
3821
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-opt__main", children: [
3822
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__label", children: o.label }),
3823
- o.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__desc", children: o.description }) : null
3824
- ] })
3825
- ]
4122
+ const renderOption = (o, idx) => {
4123
+ const isSel = selected.includes(o.value);
4124
+ const optDisabled = isOptDisabled(o);
4125
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4126
+ "button",
4127
+ {
4128
+ id: optionId(idx),
4129
+ type: "button",
4130
+ className: "twc-opt",
4131
+ role: "option",
4132
+ "aria-selected": isSel,
4133
+ ref: idx === active ? activeRef : null,
4134
+ disabled: optDisabled || void 0,
4135
+ "aria-disabled": optDisabled || void 0,
4136
+ "data-disabled": optDisabled || void 0,
4137
+ "data-selected": isSel || void 0,
4138
+ "data-active": idx === active || void 0,
4139
+ onMouseEnter: () => {
4140
+ if (!optDisabled) setActive(idx);
3826
4141
  },
3827
- o.value
3828
- );
3829
- })
3830
- ] }, gi)) });
4142
+ onMouseDown: (e) => e.preventDefault(),
4143
+ onClick: () => toggle(o.value),
4144
+ children: [
4145
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__box", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3.2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 6 9 17l-5-5" }) }) }),
4146
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-opt__main", children: [
4147
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__label", children: o.label }),
4148
+ o.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__desc", children: o.description }) : null
4149
+ ] })
4150
+ ]
4151
+ },
4152
+ o.value
4153
+ );
4154
+ };
4155
+ let vTop = 0, vBottom = 0, vShown = rows;
4156
+ if (virtualized && rows.length) {
4157
+ const winTop = scrollTop - overscan * rowH;
4158
+ const winBottom = scrollTop + listH + overscan * rowH;
4159
+ vShown = rows.filter((r) => r.top + r.h > winTop && r.top < winBottom);
4160
+ vTop = vShown.length ? vShown[0].top : 0;
4161
+ vBottom = vShown.length ? totalH - (vShown[vShown.length - 1].top + vShown[vShown.length - 1].h) : totalH;
4162
+ }
4163
+ let counter = -1;
4164
+ const popInner = /* @__PURE__ */ jsxRuntime.jsx(
4165
+ "div",
4166
+ {
4167
+ className: "twc-pop__list",
4168
+ id: listboxId,
4169
+ ref: listRef,
4170
+ role: "listbox",
4171
+ "aria-multiselectable": "true",
4172
+ onScroll: virtualized ? (e) => setScrollTop(e.currentTarget.scrollTop) : void 0,
4173
+ children: loading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-pop__loading", role: "status", children: [
4174
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-pop__spinner", "aria-hidden": "true" }),
4175
+ "Loading\u2026"
4176
+ ] }) : visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__empty", children: emptyText }) : virtualized ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4177
+ vTop > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { height: vTop } }) : null,
4178
+ vShown.map((r) => r.kind === "group" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__group", children: r.label }, `g${r.gi}`) : renderOption(r.o, r.idx)),
4179
+ vBottom > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { height: vBottom } }) : null
4180
+ ] }) : fGroups.map((g, gi) => /* @__PURE__ */ jsxRuntime.jsxs(React36__namespace.default.Fragment, { children: [
4181
+ g.group ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__group", children: g.group }) : null,
4182
+ g.options.map((o) => {
4183
+ counter += 1;
4184
+ const idx = counter;
4185
+ return renderOption(o, idx);
4186
+ })
4187
+ ] }, gi))
4188
+ }
4189
+ );
3831
4190
  const RD = { createPortal: typeof reactDom.createPortal === "function" ? reactDom.createPortal : typeof window !== "undefined" && window.ReactDOM && window.ReactDOM.createPortal };
3832
4191
  const canPortal = portal && RD && RD.createPortal;
3833
4192
  let popEl = null;
@@ -5001,6 +5360,9 @@ function Datatable({
5001
5360
  density: densityProp = "comfortable",
5002
5361
  pageSize = 10,
5003
5362
  pageSizeOptions = [5, 10, 25, 50],
5363
+ page,
5364
+ onPageChange,
5365
+ onPageSizeChange,
5004
5366
  height = 440,
5005
5367
  serverMode = false,
5006
5368
  rowCount,
@@ -5115,8 +5477,21 @@ function Datatable({
5115
5477
  const [selected, setSelected] = React36__namespace.default.useState(() => /* @__PURE__ */ new Set());
5116
5478
  const [activeRow, setActiveRow] = React36__namespace.default.useState(null);
5117
5479
  const [activeCell, setActiveCell] = React36__namespace.default.useState(null);
5118
- const [page, setPage] = React36__namespace.default.useState(0);
5119
- const [rowsPerPage, setRowsPerPage] = React36__namespace.default.useState(pageSize > 0 ? pageSize : 10);
5480
+ const [internalPage, setInternalPage] = React36__namespace.default.useState(0);
5481
+ const [internalRpp, setInternalRpp] = React36__namespace.default.useState(pageSize > 0 ? pageSize : 10);
5482
+ const pageControlled = page !== void 0;
5483
+ const pageSizeControlled = onPageSizeChange !== void 0;
5484
+ const pageVal = pageControlled ? page : internalPage;
5485
+ const sizeVal = pageSizeControlled ? pageSize > 0 ? pageSize : 10 : internalRpp;
5486
+ const commitPage = (next) => {
5487
+ onPageChange?.(next);
5488
+ if (!pageControlled) setInternalPage(next);
5489
+ };
5490
+ const commitPageSize = (next) => {
5491
+ onPageSizeChange?.(next);
5492
+ if (!pageSizeControlled) setInternalRpp(next);
5493
+ commitPage(0);
5494
+ };
5120
5495
  const [colQuery, setColQuery] = React36__namespace.default.useState("");
5121
5496
  const [widths, setWidths] = React36__namespace.default.useState({});
5122
5497
  const [order, setOrder] = React36__namespace.default.useState(() => columns.map((c) => c.field));
@@ -5182,6 +5557,11 @@ function Datatable({
5182
5557
  React36__namespace.default.useEffect(() => {
5183
5558
  setAggOn(showAggregation);
5184
5559
  }, [showAggregation]);
5560
+ React36__namespace.default.useEffect(() => {
5561
+ if (pageSizeControlled) return;
5562
+ setInternalRpp(pageSize > 0 ? pageSize : 10);
5563
+ setInternalPage(0);
5564
+ }, [pageSize]);
5185
5565
  const rowGroupingKey = (rowGrouping || []).join("\0");
5186
5566
  const rowGroupingKeyRef = React36__namespace.default.useRef(rowGroupingKey);
5187
5567
  React36__namespace.default.useEffect(() => {
@@ -5365,10 +5745,10 @@ function Datatable({
5365
5745
  const paginated = pageSize > 0;
5366
5746
  const serverTotal = rowCount == null ? processed.length : rowCount;
5367
5747
  const totalRows = serverMode ? serverTotal : processed.length;
5368
- const totalPages = paginated ? Math.max(1, Math.ceil(totalRows / rowsPerPage)) : 1;
5369
- const paged = !paginated || serverMode ? processed : processed.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
5748
+ const totalPages = paginated ? Math.max(1, Math.ceil(totalRows / sizeVal)) : 1;
5749
+ const paged = !paginated || serverMode ? processed : processed.slice(pageVal * sizeVal, pageVal * sizeVal + sizeVal);
5370
5750
  React36__namespace.default.useEffect(() => {
5371
- if (page > totalPages - 1) setPage(0);
5751
+ if (pageVal > totalPages - 1) commitPage(0);
5372
5752
  }, [totalPages]);
5373
5753
  const activeGroupBy = React36__namespace.default.useMemo(() => groupBy.filter((f) => colByField[f]), [groupBy, colByField]);
5374
5754
  function subtotalText(rowsIn) {
@@ -5631,15 +6011,15 @@ function Datatable({
5631
6011
  if (!serverMode) return;
5632
6012
  const t = setTimeout(() => {
5633
6013
  onServerChangeRef.current?.({
5634
- page,
5635
- pageSize: rowsPerPage,
6014
+ page: pageVal,
6015
+ pageSize: sizeVal,
5636
6016
  sort,
5637
6017
  filters: filters.map(({ id, ...f }) => f),
5638
6018
  quickFilter: quick.trim()
5639
6019
  });
5640
6020
  }, 250);
5641
6021
  return () => clearTimeout(t);
5642
- }, [serverMode, page, rowsPerPage, sort, filters, quick]);
6022
+ }, [serverMode, pageVal, sizeVal, sort, filters, quick]);
5643
6023
  function cycleSort(field) {
5644
6024
  setSort((s) => !s || s.field !== field ? { field, dir: "asc" } : s.dir === "asc" ? { field, dir: "desc" } : null);
5645
6025
  }
@@ -5920,7 +6300,7 @@ function Datatable({
5920
6300
  }
5921
6301
  React36__namespace.default.useEffect(() => {
5922
6302
  setFocus((f) => ({ r: Math.min(f.r, Math.max(0, leafRows.length - 1)), c: Math.min(f.c, Math.max(0, ordered.length - 1)) }));
5923
- }, [leafRows.length, ordered.length, page]);
6303
+ }, [leafRows.length, ordered.length, pageVal]);
5924
6304
  const selectedRows = React36__namespace.default.useMemo(() => rows.filter((r, i) => selected.has(keyOf(r, i))), [rows, selected]);
5925
6305
  function clearSelection() {
5926
6306
  setSelected(/* @__PURE__ */ new Set());
@@ -6165,7 +6545,7 @@ function Datatable({
6165
6545
  {
6166
6546
  className: "twc-dt__row",
6167
6547
  role: "row",
6168
- "aria-rowindex": (paginated && !serverMode ? page * rowsPerPage : 0) + ri + 2,
6548
+ "aria-rowindex": (paginated && !serverMode ? pageVal * sizeVal : 0) + ri + 2,
6169
6549
  "aria-selected": (checkboxSelection ? sel : rowActive) || void 0,
6170
6550
  "data-selected": sel || void 0,
6171
6551
  "data-active": rowActive || void 0,
@@ -6223,7 +6603,7 @@ function Datatable({
6223
6603
  ] }),
6224
6604
  rowResize && !pinSide ? /* @__PURE__ */ jsxRuntime.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
6225
6605
  ] }) : null,
6226
- showRowNum ? /* @__PURE__ */ jsxRuntime.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,
6606
+ showRowNum ? /* @__PURE__ */ jsxRuntime.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,
6227
6607
  ordered.map((c, ci) => {
6228
6608
  const st = stickyOf(c.field);
6229
6609
  const val = row[c.field];
@@ -6551,7 +6931,7 @@ function Datatable({
6551
6931
  /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.search }),
6552
6932
  /* @__PURE__ */ jsxRuntime.jsx("input", { placeholder: "Search\u2026", "aria-label": "Search rows", value: quick, onChange: (e) => {
6553
6933
  setQuick(e.target.value);
6554
- setPage(0);
6934
+ commitPage(0);
6555
6935
  } })
6556
6936
  ] })
6557
6937
  ] }),
@@ -6753,7 +7133,7 @@ function Datatable({
6753
7133
  );
6754
7134
  })
6755
7135
  ] }) }),
6756
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: loading ? Array.from({ length: paginated ? Math.min(rowsPerPage, 8) : 8 }).map((_, ri) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "twc-dt__row", role: "row", children: [
7136
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: loading ? Array.from({ length: paginated ? Math.min(sizeVal, 8) : 8 }).map((_, ri) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "twc-dt__row", role: "row", children: [
6757
7137
  checkboxSelection ? /* @__PURE__ */ jsxRuntime.jsx("td", { className: "twc-dt__td", role: "gridcell", "data-pin": "left", style: { insetInlineStart: 0, width: 44 }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__sk", "aria-hidden": "true", style: { "--_w": "18px", height: 18, borderRadius: 4 } }) }) : null,
6758
7138
  showRowNum ? /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx("span", { className: "twc-dt__sk", "aria-hidden": "true", style: { "--_w": "16px", height: 14, borderRadius: 4 } }) }) : null,
6759
7139
  ordered.map((c, ci) => {
@@ -6797,7 +7177,7 @@ function Datatable({
6797
7177
  const nf = (n) => n.toLocaleString();
6798
7178
  if (totalRows === 0) return "No rows";
6799
7179
  const shown = paged.length;
6800
- const start = paginated ? page * rowsPerPage + 1 : 1;
7180
+ const start = paginated ? pageVal * sizeVal + 1 : 1;
6801
7181
  const end = paginated ? start + shown - 1 : shown;
6802
7182
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6803
7183
  paginated ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -6825,13 +7205,10 @@ function Datatable({
6825
7205
  })() }),
6826
7206
  paginated ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__rpp", children: [
6827
7207
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__rpp-label", children: "Rows per page" }),
6828
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 78 }, children: /* @__PURE__ */ jsxRuntime.jsx(Select, { size: "sm", portal: true, searchable: false, value: String(rowsPerPage), options: rppOptions, placement: "top", onChange: (v) => {
6829
- setRowsPerPage(Number(v));
6830
- setPage(0);
6831
- } }) })
7208
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 78 }, children: /* @__PURE__ */ jsxRuntime.jsx(Select, { size: "sm", portal: true, searchable: false, value: String(sizeVal), options: rppOptions, placement: "top", onChange: (v) => commitPageSize(Number(v)) }) })
6832
7209
  ] }) : null
6833
7210
  ] }),
6834
- paginated ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__footer-row", "data-pager": "true", children: /* @__PURE__ */ jsxRuntime.jsx(Pagination, { size: "sm", page: page + 1, total: totalPages, boundaries: 3, showPageJumper: showPageJumper && totalPages > 5, onChange: (p) => setPage(p - 1) }) }) : null
7211
+ paginated ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__footer-row", "data-pager": "true", children: /* @__PURE__ */ jsxRuntime.jsx(Pagination, { size: "sm", page: pageVal + 1, total: totalPages, boundaries: 3, showPageJumper: showPageJumper && totalPages > 5, onChange: (p) => commitPage(p - 1) }) }) : null
6835
7212
  ] }) : null,
6836
7213
  exportOpen && exportPos ? /* @__PURE__ */ jsxRuntime.jsxs(
6837
7214
  "div",
@@ -7480,6 +7857,18 @@ var DATEPICKER_CSS = `
7480
7857
  .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); }
7481
7858
  .twc-dp__clear:hover { background: var(--color-surface-sunken); color: var(--color-text); }
7482
7859
  .twc-dp__clear svg { width: 14px; height: 14px; }
7860
+ /* #105: editable (typed entry) variant \u2014 the control wraps a real text input + a trailing toggle. */
7861
+ .twc-dp__control--editable { cursor: text; gap: 0; padding-inline-end: var(--space-1); }
7862
+ .twc-dp__input { flex: 1; min-width: 0; height: 100%; border: none; background: transparent; outline: none; padding: 0;
7863
+ font-family: inherit; font-size: var(--text-sm); color: var(--color-text); }
7864
+ .twc-dp__input::placeholder { color: var(--color-text-subtle); }
7865
+ .twc-dp__input:disabled { cursor: not-allowed; }
7866
+ .twc-dp__clear--inline { position: static; transform: none; flex: none; }
7867
+ .twc-dp__toggle { flex: none; display: inline-grid; place-items: center; width: 26px; height: 26px; border: none; background: transparent;
7868
+ color: var(--color-text-subtle); cursor: pointer; border-radius: var(--radius-md); transition: background-color var(--duration-fast), color var(--duration-fast); }
7869
+ .twc-dp__toggle:hover:not(:disabled) { background: var(--color-surface-sunken); color: var(--color-text); }
7870
+ .twc-dp__toggle:disabled { cursor: not-allowed; }
7871
+ .twc-dp__toggle svg { width: 17px; height: 17px; }
7483
7872
 
7484
7873
  .twc-dp__pop {
7485
7874
  position: fixed; z-index: var(--z-popover); width: 280px;
@@ -7539,6 +7928,8 @@ function DatePicker({
7539
7928
  disabled = false,
7540
7929
  tone = "primary",
7541
7930
  clearable = true,
7931
+ editable = false,
7932
+ parse,
7542
7933
  format,
7543
7934
  locale,
7544
7935
  weekStartsOn = 0,
@@ -7560,6 +7951,7 @@ function DatePicker({
7560
7951
  const [mode, setMode] = React36__namespace.default.useState("days");
7561
7952
  const [focusDate, setFocusDate] = React36__namespace.default.useState(null);
7562
7953
  const [focusMonth, setFocusMonth] = React36__namespace.default.useState(null);
7954
+ const [editText, setEditText] = React36__namespace.default.useState(null);
7563
7955
  const [coords, setCoords] = React36__namespace.default.useState(null);
7564
7956
  const wrapRef = React36__namespace.default.useRef(null);
7565
7957
  const triggerRef = React36__namespace.default.useRef(null);
@@ -7623,8 +8015,33 @@ function DatePicker({
7623
8015
  const pick = (d) => {
7624
8016
  if (value === void 0) setInternal(d);
7625
8017
  onChange?.(d);
8018
+ setEditText(null);
7626
8019
  setOpen(false);
7627
8020
  };
8021
+ const parseTyped = (s) => {
8022
+ const str = s.trim();
8023
+ if (str === "") return null;
8024
+ if (parse) {
8025
+ const d = parse(str);
8026
+ return d instanceof Date && !Number.isNaN(d.getTime()) ? d : void 0;
8027
+ }
8028
+ const t = Date.parse(str);
8029
+ return Number.isNaN(t) ? void 0 : new Date(t);
8030
+ };
8031
+ const commitText = () => {
8032
+ if (editText == null) return;
8033
+ const parsed = parseTyped(editText);
8034
+ if (parsed === null) {
8035
+ setEditText(null);
8036
+ if (selected) pick(null);
8037
+ return;
8038
+ }
8039
+ if (parsed === void 0 || outOfRange(parsed)) {
8040
+ setEditText(null);
8041
+ return;
8042
+ }
8043
+ pick(parsed);
8044
+ };
7628
8045
  const y = view.getFullYear(), m = view.getMonth();
7629
8046
  const first = new Date(y, m, 1);
7630
8047
  const startOffset = (first.getDay() - weekStartsOn + 7) % 7;
@@ -7700,7 +8117,82 @@ function DatePicker({
7700
8117
  label,
7701
8118
  required ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-field__req", children: "*" }) : null
7702
8119
  ] }) : null,
7703
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dp__field", children: [
8120
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dp__field", children: editable ? (
8121
+ // #105: typed entry — the trigger is a real text input with a trailing
8122
+ // calendar toggle button; typing commits via `parse` on Enter/blur.
8123
+ /* @__PURE__ */ jsxRuntime.jsxs(
8124
+ "div",
8125
+ {
8126
+ className: "twc-dp__control twc-dp__control--editable",
8127
+ "data-open": open || void 0,
8128
+ "data-disabled": disabled || void 0,
8129
+ "data-tone": tone,
8130
+ "data-invalid": invalid || void 0,
8131
+ children: [
8132
+ /* @__PURE__ */ jsxRuntime.jsx(
8133
+ "input",
8134
+ {
8135
+ ref: triggerRef,
8136
+ className: "twc-dp__input",
8137
+ id: fieldId,
8138
+ type: "text",
8139
+ inputMode: "numeric",
8140
+ autoComplete: "off",
8141
+ disabled,
8142
+ placeholder,
8143
+ value: editText != null ? editText : selected ? fmt(selected) : "",
8144
+ "aria-haspopup": "dialog",
8145
+ "aria-expanded": open,
8146
+ "aria-labelledby": label ? `${fieldId}-label` : void 0,
8147
+ "aria-invalid": invalid || void 0,
8148
+ "aria-describedby": error || hint ? descId : void 0,
8149
+ onChange: (e) => setEditText(e.target.value),
8150
+ onKeyDown: (e) => {
8151
+ if (e.key === "Enter") {
8152
+ e.preventDefault();
8153
+ commitText();
8154
+ } else if (e.key === "ArrowDown" && (e.altKey || !open)) {
8155
+ e.preventDefault();
8156
+ setOpen(true);
8157
+ } else if (e.key === "Escape" && open) setOpen(false);
8158
+ },
8159
+ onBlur: (e) => {
8160
+ if (popRef.current && popRef.current.contains(e.relatedTarget)) return;
8161
+ commitText();
8162
+ }
8163
+ }
8164
+ ),
8165
+ clearable && selected && !disabled ? /* @__PURE__ */ jsxRuntime.jsx(
8166
+ "button",
8167
+ {
8168
+ type: "button",
8169
+ className: "twc-dp__clear twc-dp__clear--inline",
8170
+ "aria-label": "Clear",
8171
+ tabIndex: -1,
8172
+ onMouseDown: (e) => e.preventDefault(),
8173
+ onClick: () => pick(null),
8174
+ children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18M6 6l12 12" }) })
8175
+ }
8176
+ ) : null,
8177
+ /* @__PURE__ */ jsxRuntime.jsx(
8178
+ "button",
8179
+ {
8180
+ type: "button",
8181
+ className: "twc-dp__toggle",
8182
+ "aria-label": "Open calendar",
8183
+ tabIndex: -1,
8184
+ disabled,
8185
+ onClick: () => !disabled && setOpen((o) => !o),
8186
+ children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
8187
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
8188
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 2v4M8 2v4M3 10h18" })
8189
+ ] })
8190
+ }
8191
+ )
8192
+ ]
8193
+ }
8194
+ )
8195
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7704
8196
  /* @__PURE__ */ jsxRuntime.jsxs(
7705
8197
  "div",
7706
8198
  {
@@ -7739,7 +8231,7 @@ function DatePicker({
7739
8231
  e.stopPropagation();
7740
8232
  pick(null);
7741
8233
  }, children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }) }) : null
7742
- ] }),
8234
+ ] }) }),
7743
8235
  open && coords ? reactDom.createPortal(
7744
8236
  /* @__PURE__ */ jsxRuntime.jsxs(
7745
8237
  "div",
@@ -7838,6 +8330,17 @@ var RANGE_CSS = `
7838
8330
  .twc-drp__ic svg { width: 17px; height: 17px; }
7839
8331
  .twc-drp__text { flex: 1; font-size: var(--text-sm); color: var(--color-text); white-space: nowrap; }
7840
8332
  .twc-drp__text[data-placeholder="true"] { color: var(--color-text-subtle); }
8333
+ /* #105: editable (typed entry) variant. */
8334
+ .twc-drp__control--editable { cursor: text; gap: 0; padding-inline-end: var(--space-1); }
8335
+ .twc-drp__input { flex: 1; min-width: 0; height: 100%; border: none; background: transparent; outline: none; padding: 0;
8336
+ font-family: inherit; font-size: var(--text-sm); color: var(--color-text); }
8337
+ .twc-drp__input::placeholder { color: var(--color-text-subtle); }
8338
+ .twc-drp__input:disabled { cursor: not-allowed; }
8339
+ .twc-drp__toggle { flex: none; display: inline-grid; place-items: center; width: 26px; height: 26px; border: none; background: transparent;
8340
+ color: var(--color-text-subtle); cursor: pointer; border-radius: var(--radius-md); transition: background-color var(--duration-fast), color var(--duration-fast); }
8341
+ .twc-drp__toggle:hover:not(:disabled) { background: var(--color-surface-sunken); color: var(--color-text); }
8342
+ .twc-drp__toggle:disabled { cursor: not-allowed; }
8343
+ .twc-drp__toggle svg { width: 17px; height: 17px; }
7841
8344
  .twc-drp__pop { position: fixed; z-index: var(--z-popover); display: flex;
7842
8345
  background: var(--color-surface-raised); border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-lg);
7843
8346
  box-shadow: var(--shadow-lg); animation: twico-scale-in var(--duration-fast) var(--ease-spring); transform-origin: top; }
@@ -7892,6 +8395,8 @@ function DateRangePicker({
7892
8395
  disabledDate,
7893
8396
  disabled = false,
7894
8397
  tone = "primary",
8398
+ editable = false,
8399
+ parse,
7895
8400
  onChange,
7896
8401
  className = "",
7897
8402
  ...rest
@@ -7904,6 +8409,7 @@ function DateRangePicker({
7904
8409
  const [view, setView] = React36__namespace.default.useState(range.start || /* @__PURE__ */ new Date());
7905
8410
  const [hover, setHover] = React36__namespace.default.useState(null);
7906
8411
  const [focusDate, setFocusDate] = React36__namespace.default.useState(null);
8412
+ const [editText, setEditText] = React36__namespace.default.useState(null);
7907
8413
  const [coords, setCoords] = React36__namespace.default.useState(null);
7908
8414
  const wrapRef = React36__namespace.default.useRef(null);
7909
8415
  const triggerRef = React36__namespace.default.useRef(null);
@@ -7959,6 +8465,40 @@ function DateRangePicker({
7959
8465
  if (value === void 0) setInternal(r);
7960
8466
  onChange?.(r);
7961
8467
  };
8468
+ const parseOne = (s) => {
8469
+ const str = s.trim();
8470
+ if (str === "") return null;
8471
+ if (parse) {
8472
+ const d = parse(str);
8473
+ return d instanceof Date && !Number.isNaN(d.getTime()) ? d : void 0;
8474
+ }
8475
+ const t = Date.parse(str);
8476
+ return Number.isNaN(t) ? void 0 : new Date(t);
8477
+ };
8478
+ const commitText = () => {
8479
+ if (editText == null) return;
8480
+ const raw = editText.trim();
8481
+ if (raw === "") {
8482
+ setEditText(null);
8483
+ if (range.start || range.end) set({ start: null, end: null });
8484
+ return;
8485
+ }
8486
+ const parts = raw.split(/\s*(?:–|—|\bto\b|\.\.+)\s*/i).map((p) => p.trim());
8487
+ let a = parseOne(parts[0] || "");
8488
+ let b = parts.length > 1 ? parseOne(parts[1] || "") : null;
8489
+ if (a === void 0 || b === void 0 || a && outOfRange(a) || b && outOfRange(b)) {
8490
+ setEditText(null);
8491
+ return;
8492
+ }
8493
+ if (a && b && ymd(b) < ymd(a)) {
8494
+ const t = a;
8495
+ a = b;
8496
+ b = t;
8497
+ }
8498
+ set({ start: a || null, end: b || null });
8499
+ setEditText(null);
8500
+ setOpen(false);
8501
+ };
7962
8502
  useFocusTrap(popRef, open && !!coords, { initialFocus: () => gridRef.current?.querySelector('[tabindex="0"]') });
7963
8503
  const outOfRange = (d) => min && ymd(d) < ymd(min) || max && ymd(d) > ymd(max) || Boolean(disabledDate && disabledDate(d));
7964
8504
  const sameDay2 = (a, b) => a && b && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
@@ -8054,7 +8594,57 @@ function DateRangePicker({
8054
8594
  label,
8055
8595
  required ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-field__req", children: "*" }) : null
8056
8596
  ] }) : null,
8057
- /* @__PURE__ */ jsxRuntime.jsxs(
8597
+ editable ? (
8598
+ // #105: typed entry — a text input accepting "start – end"; commits via `parse` on Enter/blur.
8599
+ /* @__PURE__ */ jsxRuntime.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: [
8600
+ /* @__PURE__ */ jsxRuntime.jsx(
8601
+ "input",
8602
+ {
8603
+ ref: triggerRef,
8604
+ className: "twc-drp__input",
8605
+ type: "text",
8606
+ autoComplete: "off",
8607
+ disabled,
8608
+ placeholder,
8609
+ value: editText != null ? editText : text,
8610
+ "aria-haspopup": "dialog",
8611
+ "aria-expanded": open,
8612
+ "aria-labelledby": label ? labelId : void 0,
8613
+ "aria-invalid": invalid || void 0,
8614
+ "aria-describedby": error || hint ? descId : void 0,
8615
+ onChange: (e) => setEditText(e.target.value),
8616
+ onKeyDown: (e) => {
8617
+ if (e.key === "Enter") {
8618
+ e.preventDefault();
8619
+ commitText();
8620
+ } else if (e.key === "ArrowDown" && (e.altKey || !open)) {
8621
+ e.preventDefault();
8622
+ setOpen(true);
8623
+ } else if (e.key === "Escape" && open) setOpen(false);
8624
+ },
8625
+ onBlur: (e) => {
8626
+ if (popRef.current && popRef.current.contains(e.relatedTarget)) return;
8627
+ commitText();
8628
+ }
8629
+ }
8630
+ ),
8631
+ /* @__PURE__ */ jsxRuntime.jsx(
8632
+ "button",
8633
+ {
8634
+ type: "button",
8635
+ className: "twc-drp__toggle",
8636
+ "aria-label": "Open calendar",
8637
+ tabIndex: -1,
8638
+ disabled,
8639
+ onClick: () => !disabled && setOpen((o) => !o),
8640
+ children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
8641
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
8642
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 2v4M8 2v4M3 10h18" })
8643
+ ] })
8644
+ }
8645
+ )
8646
+ ] })
8647
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(
8058
8648
  "div",
8059
8649
  {
8060
8650
  ref: triggerRef,
@@ -11528,11 +12118,13 @@ var Box = React36__namespace.default.forwardRef(function Box2({
11528
12118
  border = false,
11529
12119
  radius,
11530
12120
  shadow,
12121
+ sx,
11531
12122
  className = "",
11532
12123
  style,
11533
12124
  children,
11534
12125
  ...rest
11535
12126
  }, ref) {
12127
+ const { flatStyle, styleNode, sxAttr } = useSx(sx);
11536
12128
  const s = {
11537
12129
  paddingTop: sp(pt ?? py ?? p),
11538
12130
  paddingRight: sp(pr ?? px ?? p),
@@ -11546,10 +12138,14 @@ var Box = React36__namespace.default.forwardRef(function Box2({
11546
12138
  border: border ? "var(--border-thin, 1px) solid var(--color-border)" : void 0,
11547
12139
  borderRadius: radius ? `var(--radius-${radius})` : void 0,
11548
12140
  boxShadow: shadow ? `var(--shadow-${shadow})` : void 0,
11549
- ...style
12141
+ ...style,
12142
+ ...flatStyle
11550
12143
  };
11551
12144
  if (Tag2 === "a" && rest.href != null) rest.href = safeHref6(rest.href);
11552
- return /* @__PURE__ */ jsxRuntime.jsx(Tag2, { ref, className: `twc-box ${className}`.trim(), style: s, ...rest, children });
12145
+ return /* @__PURE__ */ jsxRuntime.jsxs(Tag2, { ref, className: `twc-box ${className}`.trim(), style: s, "data-twc-sx": sxAttr, ...rest, children: [
12146
+ styleNode,
12147
+ children
12148
+ ] });
11553
12149
  });
11554
12150
  Box.displayName = "Box";
11555
12151
  var SIZES = { sm: 640, md: 768, lg: 1024, xl: 1280, full: "100%" };
@@ -11562,27 +12158,34 @@ var Container = React36__namespace.default.forwardRef(function Container2({
11562
12158
  as: Tag2 = "div",
11563
12159
  size = "lg",
11564
12160
  padded = true,
12161
+ sx,
11565
12162
  className = "",
11566
12163
  style,
11567
12164
  children,
11568
12165
  ...rest
11569
12166
  }, ref) {
12167
+ const { flatStyle, styleNode, sxAttr } = useSx(sx);
11570
12168
  const max = SIZES[size] ?? size;
11571
12169
  if (Tag2 === "a" && rest.href != null) rest.href = safeHref7(rest.href);
11572
- return /* @__PURE__ */ jsxRuntime.jsx(
12170
+ return /* @__PURE__ */ jsxRuntime.jsxs(
11573
12171
  Tag2,
11574
12172
  {
11575
12173
  ref,
11576
12174
  className: `twc-container ${className}`.trim(),
12175
+ "data-twc-sx": sxAttr,
11577
12176
  style: {
11578
12177
  width: "100%",
11579
12178
  maxWidth: typeof max === "number" ? `${max}px` : max,
11580
12179
  marginInline: "auto",
11581
12180
  paddingInline: padded ? "var(--space-5)" : void 0,
11582
- ...style
12181
+ ...style,
12182
+ ...flatStyle
11583
12183
  },
11584
12184
  ...rest,
11585
- children
12185
+ children: [
12186
+ styleNode,
12187
+ children
12188
+ ]
11586
12189
  }
11587
12190
  );
11588
12191
  });
@@ -11612,11 +12215,13 @@ var Grid = React36__namespace.default.forwardRef(function Grid2({
11612
12215
  justify,
11613
12216
  alignContent,
11614
12217
  justifyContent,
12218
+ sx,
11615
12219
  className = "",
11616
12220
  style,
11617
12221
  children,
11618
12222
  ...rest
11619
12223
  }, ref) {
12224
+ const { flatStyle, styleNode, sxAttr } = useSx(sx);
11620
12225
  const responsive = columns != null && typeof columns === "object";
11621
12226
  const uid = React36__namespace.default.useId();
11622
12227
  const gridId = responsive ? `g${uid.replace(/[^a-zA-Z0-9]/g, "")}` : null;
@@ -11639,6 +12244,7 @@ var Grid = React36__namespace.default.forwardRef(function Grid2({
11639
12244
  ref,
11640
12245
  className: `twc-grid ${className}`.trim(),
11641
12246
  "data-twc-grid-id": gridId || void 0,
12247
+ "data-twc-sx": sxAttr,
11642
12248
  style: {
11643
12249
  display: "grid",
11644
12250
  gridTemplateColumns: templateColumns,
@@ -11648,11 +12254,13 @@ var Grid = React36__namespace.default.forwardRef(function Grid2({
11648
12254
  justifyItems: justify,
11649
12255
  alignContent,
11650
12256
  justifyContent,
11651
- ...style
12257
+ ...style,
12258
+ ...flatStyle
11652
12259
  },
11653
12260
  ...rest,
11654
12261
  children: [
11655
12262
  responsive ? __twcStyles : null,
12263
+ styleNode,
11656
12264
  children
11657
12265
  ]
11658
12266
  }
@@ -11685,18 +12293,21 @@ var Stack = React36__namespace.default.forwardRef(function Stack2({
11685
12293
  pb,
11686
12294
  pl,
11687
12295
  divider,
12296
+ sx,
11688
12297
  className = "",
11689
12298
  style,
11690
12299
  children,
11691
12300
  ...rest
11692
12301
  }, ref) {
12302
+ const { flatStyle, styleNode, sxAttr } = useSx(sx);
11693
12303
  if (Tag2 === "a" && rest.href != null) rest.href = safeHref9(rest.href);
11694
12304
  const kids = divider != null ? React36__namespace.default.Children.toArray(children).flatMap((child, i, arr) => i < arr.length - 1 ? [child, /* @__PURE__ */ jsxRuntime.jsx(React36__namespace.default.Fragment, { children: divider }, `twc-stack-div-${i}`)] : [child]) : children;
11695
- return /* @__PURE__ */ jsxRuntime.jsx(
12305
+ return /* @__PURE__ */ jsxRuntime.jsxs(
11696
12306
  Tag2,
11697
12307
  {
11698
12308
  ref,
11699
12309
  className: `twc-stack ${className}`.trim(),
12310
+ "data-twc-sx": sxAttr,
11700
12311
  style: {
11701
12312
  display: inline ? "inline-flex" : "flex",
11702
12313
  flexDirection: direction,
@@ -11709,10 +12320,14 @@ var Stack = React36__namespace.default.forwardRef(function Stack2({
11709
12320
  paddingBottom: space2(pb ?? py ?? p),
11710
12321
  paddingLeft: space2(pl ?? px ?? p),
11711
12322
  minWidth: 0,
11712
- ...style
12323
+ ...style,
12324
+ ...flatStyle
11713
12325
  },
11714
12326
  ...rest,
11715
- children: kids
12327
+ children: [
12328
+ styleNode,
12329
+ kids
12330
+ ]
11716
12331
  }
11717
12332
  );
11718
12333
  });
@@ -11795,19 +12410,22 @@ var Heading = React36__namespace.default.forwardRef(function Heading2({
11795
12410
  align,
11796
12411
  truncate,
11797
12412
  lineClamp,
12413
+ sx,
11798
12414
  className = "",
11799
12415
  style,
11800
12416
  children,
11801
12417
  ...rest
11802
12418
  }, ref) {
12419
+ const { flatStyle, styleNode, sxAttr } = useSx(sx);
11803
12420
  const Tag2 = as || `h${level}`;
11804
12421
  const sz = size || (display ? "display" : LEVEL_SIZE[level]) || "2xl";
11805
12422
  if (Tag2 === "a" && rest.href != null) rest.href = safeHref11(rest.href);
11806
- return /* @__PURE__ */ jsxRuntime.jsx(
12423
+ return /* @__PURE__ */ jsxRuntime.jsxs(
11807
12424
  Tag2,
11808
12425
  {
11809
12426
  ref,
11810
12427
  className: `twc-heading ${className}`.trim(),
12428
+ "data-twc-sx": sxAttr,
11811
12429
  style: {
11812
12430
  margin: 0,
11813
12431
  fontFamily: "var(--font-sans)",
@@ -11818,10 +12436,14 @@ var Heading = React36__namespace.default.forwardRef(function Heading2({
11818
12436
  fontSize: `var(--text-${sz})`,
11819
12437
  textAlign: align,
11820
12438
  ...lineClamp != null ? { display: "-webkit-box", WebkitBoxOrient: "vertical", WebkitLineClamp: lineClamp, overflow: "hidden" } : truncate ? { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", minWidth: 0 } : null,
11821
- ...style
12439
+ ...style,
12440
+ ...flatStyle
11822
12441
  },
11823
12442
  ...rest,
11824
- children
12443
+ children: [
12444
+ styleNode,
12445
+ children
12446
+ ]
11825
12447
  }
11826
12448
  );
11827
12449
  });
@@ -11854,18 +12476,21 @@ var Text = React36__namespace.default.forwardRef(function Text2({
11854
12476
  align,
11855
12477
  truncate,
11856
12478
  lineClamp,
12479
+ sx,
11857
12480
  className = "",
11858
12481
  style,
11859
12482
  children,
11860
12483
  ...rest
11861
12484
  }, ref) {
12485
+ const { flatStyle, styleNode, sxAttr } = useSx(sx);
11862
12486
  if (Tag2 === "a" && rest.href != null) rest.href = safeHref12(rest.href);
11863
12487
  const toneToken = TONE[tone] || TONE.default;
11864
- return /* @__PURE__ */ jsxRuntime.jsx(
12488
+ return /* @__PURE__ */ jsxRuntime.jsxs(
11865
12489
  Tag2,
11866
12490
  {
11867
12491
  ref,
11868
12492
  className: `twc-text ${className}`.trim(),
12493
+ "data-twc-sx": sxAttr,
11869
12494
  style: {
11870
12495
  margin: 0,
11871
12496
  fontFamily: "var(--font-sans)",
@@ -11875,10 +12500,14 @@ var Text = React36__namespace.default.forwardRef(function Text2({
11875
12500
  fontWeight: weight ? `var(--font-${weight})` : void 0,
11876
12501
  textAlign: align,
11877
12502
  ...lineClamp != null ? { display: "-webkit-box", WebkitBoxOrient: "vertical", WebkitLineClamp: lineClamp, overflow: "hidden" } : truncate ? { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", minWidth: 0 } : null,
11878
- ...style
12503
+ ...style,
12504
+ ...flatStyle
11879
12505
  },
11880
12506
  ...rest,
11881
- children
12507
+ children: [
12508
+ styleNode,
12509
+ children
12510
+ ]
11882
12511
  }
11883
12512
  );
11884
12513
  });