twico-ui 1.28.0 → 1.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -5650,6 +5650,10 @@ var COMBO_CSS = `
5650
5650
  .twc-opt__main { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
5651
5651
  .twc-opt__label { line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
5652
5652
  .twc-opt__desc { font-size: var(--text-xs); color: var(--color-text-muted); line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
5653
+ /* #326: optional leading icon/swatch + trailing (right-aligned) hint on an option. */
5654
+ .twc-opt__icon { flex: none; display: inline-flex; align-items: center; color: var(--color-text-muted); }
5655
+ .twc-opt__icon svg { width: 16px; height: 16px; }
5656
+ .twc-opt__hint { flex: none; margin-inline-start: auto; padding-inline-start: var(--space-2); color: var(--color-text-muted); font-size: var(--text-xs); white-space: nowrap; }
5653
5657
  /* #300: wrapOptions \u2014 let long labels/descriptions wrap onto multiple lines instead of truncating.
5654
5658
  overflow-wrap: anywhere also breaks a single very long token so it can't overflow the popover edge. */
5655
5659
  .twc-opt[data-wrap="true"] { align-items: flex-start; }
@@ -5717,6 +5721,7 @@ function Combobox({
5717
5721
  virtualized: virtualizedProp = false,
5718
5722
  overscan = 8,
5719
5723
  wrapOptions = false,
5724
+ renderOption,
5720
5725
  id,
5721
5726
  className = "",
5722
5727
  onFocus,
@@ -5724,8 +5729,8 @@ function Combobox({
5724
5729
  ...rest
5725
5730
  }) {
5726
5731
  const __twcStyles = useScopedStyles("twc-combo-styles", COMBO_CSS);
5727
- if (virtualizedProp && wrapOptions) warnOnce("combobox-wrap-virtualized", "twico-ui Combobox: `wrapOptions` disables `virtualized` \u2014 wrapped option rows aren't a fixed height.");
5728
- const virtualized = virtualizedProp && !wrapOptions;
5732
+ if (virtualizedProp && (wrapOptions || renderOption)) warnOnce("combobox-var-height-virtualized", "twico-ui Combobox: `wrapOptions`/`renderOption` disable `virtualized` \u2014 those rows aren't a fixed height.");
5733
+ const virtualized = virtualizedProp && !wrapOptions && !renderOption;
5729
5734
  const groups = React51__default.useMemo(() => normalizeGroups(options), [options]);
5730
5735
  const flat = React51__default.useMemo(() => groups.flatMap((g) => g.options), [groups]);
5731
5736
  const autoId = React51__default.useId();
@@ -5896,7 +5901,7 @@ function Combobox({
5896
5901
  const activeId = open && visible[active] ? optionId(active) : void 0;
5897
5902
  const descId = `${fieldId}-desc`;
5898
5903
  const describedBy = error || hint ? descId : void 0;
5899
- const renderOption = (o, idx) => {
5904
+ const renderRow = (o, idx) => {
5900
5905
  const isSel = o.value === current;
5901
5906
  return /* @__PURE__ */ jsxs(
5902
5907
  "button",
@@ -5919,9 +5924,13 @@ function Combobox({
5919
5924
  onMouseDown: (e) => e.preventDefault(),
5920
5925
  onClick: () => commit(o.value),
5921
5926
  children: [
5922
- /* @__PURE__ */ jsxs("span", { className: "twc-opt__main", children: [
5923
- /* @__PURE__ */ jsx("span", { className: "twc-opt__label", children: o.label }),
5924
- o.description ? /* @__PURE__ */ jsx("span", { className: "twc-opt__desc", children: o.description }) : null
5927
+ renderOption ? /* @__PURE__ */ jsx("span", { className: "twc-opt__main", children: renderOption(o, { selected: isSel, active: idx === active }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
5928
+ o.icon != null ? /* @__PURE__ */ jsx("span", { className: "twc-opt__icon", "aria-hidden": "true", children: o.icon }) : null,
5929
+ /* @__PURE__ */ jsxs("span", { className: "twc-opt__main", children: [
5930
+ /* @__PURE__ */ jsx("span", { className: "twc-opt__label", children: o.label }),
5931
+ o.description ? /* @__PURE__ */ jsx("span", { className: "twc-opt__desc", children: o.description }) : null
5932
+ ] }),
5933
+ o.hint != null ? /* @__PURE__ */ jsx("span", { className: "twc-opt__hint", children: o.hint }) : null
5925
5934
  ] }),
5926
5935
  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
5927
5936
  ]
@@ -5943,14 +5952,14 @@ function Combobox({
5943
5952
  "Loading\u2026"
5944
5953
  ] }) : visible.length === 0 ? /* @__PURE__ */ jsx("div", { className: "twc-pop__empty", children: emptyText }) : virtualized ? /* @__PURE__ */ jsxs(Fragment, { children: [
5945
5954
  vTop > 0 ? /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { height: vTop } }) : null,
5946
- vShown.map((r2) => r2.kind === "group" ? /* @__PURE__ */ jsx("div", { className: "twc-pop__group", children: r2.label }, `g${r2.gi}`) : renderOption(r2.o, r2.idx)),
5955
+ vShown.map((r2) => r2.kind === "group" ? /* @__PURE__ */ jsx("div", { className: "twc-pop__group", children: r2.label }, `g${r2.gi}`) : renderRow(r2.o, r2.idx)),
5947
5956
  vBottom > 0 ? /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { height: vBottom } }) : null
5948
5957
  ] }) : fGroups.map((g, gi) => /* @__PURE__ */ jsxs(React51__default.Fragment, { children: [
5949
5958
  g.group ? /* @__PURE__ */ jsx("div", { className: "twc-pop__group", children: g.group }) : null,
5950
5959
  g.options.map((o) => {
5951
5960
  counter += 1;
5952
5961
  const idx = counter;
5953
- return renderOption(o, idx);
5962
+ return renderRow(o, idx);
5954
5963
  })
5955
5964
  ] }, gi)) });
5956
5965
  const RD = { createPortal: typeof createPortal === "function" ? createPortal : typeof window !== "undefined" && window.ReactDOM && window.ReactDOM.createPortal };
@@ -6487,6 +6496,10 @@ var SELECT_CSS = `
6487
6496
  .twc-opt__main { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
6488
6497
  .twc-opt__label { line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
6489
6498
  .twc-opt__desc { font-size: var(--text-xs); color: var(--color-text-muted); line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
6499
+ /* #326: optional leading icon/swatch + trailing (right-aligned) hint on an option. */
6500
+ .twc-opt__icon { flex: none; display: inline-flex; align-items: center; color: var(--color-text-muted); }
6501
+ .twc-opt__icon svg { width: 16px; height: 16px; }
6502
+ .twc-opt__hint { flex: none; margin-inline-start: auto; padding-inline-start: var(--space-2); color: var(--color-text-muted); font-size: var(--text-xs); white-space: nowrap; }
6490
6503
  .twc-opt__check { flex: none; color: var(--color-primary); display: inline-flex; }
6491
6504
  .twc-opt__check svg { width: 16px; height: 16px; }
6492
6505
  .twc-pop__empty { padding: 14px 12px; text-align: center; font-size: var(--text-sm); color: var(--color-text-subtle); }
@@ -6547,8 +6560,9 @@ function Select({
6547
6560
  loading = false,
6548
6561
  emptyText = "No results found",
6549
6562
  name,
6550
- virtualized = false,
6563
+ virtualized: virtualizedProp = false,
6551
6564
  overscan = 8,
6565
+ renderOption,
6552
6566
  id,
6553
6567
  className = "",
6554
6568
  onClick,
@@ -6556,6 +6570,8 @@ function Select({
6556
6570
  ...rest
6557
6571
  }) {
6558
6572
  const __twcStyles = useScopedStyles("twc-select-styles", SELECT_CSS);
6573
+ if (virtualizedProp && renderOption) warnOnce("select-renderoption-virtualized", "twico-ui Select: `renderOption` disables `virtualized` \u2014 custom rows aren't a fixed height.");
6574
+ const virtualized = virtualizedProp && !renderOption;
6559
6575
  const bufferRef = React51__default.useRef("");
6560
6576
  const bufferTimerRef = React51__default.useRef(null);
6561
6577
  const groups = React51__default.useMemo(() => normalizeGroups2(options), [options]);
@@ -6761,7 +6777,7 @@ function Select({
6761
6777
  const activeId = open && visible[active] ? optionId(active) : void 0;
6762
6778
  const descId = `${fieldId}-desc`;
6763
6779
  const describedBy = error || hint ? descId : void 0;
6764
- const renderOption = (o, idx) => {
6780
+ const renderRow = (o, idx) => {
6765
6781
  const isSel = o.value === current;
6766
6782
  return /* @__PURE__ */ jsxs(
6767
6783
  "button",
@@ -6783,9 +6799,13 @@ function Select({
6783
6799
  },
6784
6800
  onClick: () => commit(o.value),
6785
6801
  children: [
6786
- /* @__PURE__ */ jsxs("span", { className: "twc-opt__main", children: [
6787
- /* @__PURE__ */ jsx("span", { className: "twc-opt__label", children: o.label }),
6788
- o.description ? /* @__PURE__ */ jsx("span", { className: "twc-opt__desc", children: o.description }) : null
6802
+ renderOption ? /* @__PURE__ */ jsx("span", { className: "twc-opt__main", children: renderOption(o, { selected: isSel, active: idx === active }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
6803
+ o.icon != null ? /* @__PURE__ */ jsx("span", { className: "twc-opt__icon", "aria-hidden": "true", children: o.icon }) : null,
6804
+ /* @__PURE__ */ jsxs("span", { className: "twc-opt__main", children: [
6805
+ /* @__PURE__ */ jsx("span", { className: "twc-opt__label", children: o.label }),
6806
+ o.description ? /* @__PURE__ */ jsx("span", { className: "twc-opt__desc", children: o.description }) : null
6807
+ ] }),
6808
+ o.hint != null ? /* @__PURE__ */ jsx("span", { className: "twc-opt__hint", children: o.hint }) : null
6789
6809
  ] }),
6790
6810
  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
6791
6811
  ]
@@ -6833,14 +6853,14 @@ function Select({
6833
6853
  "Loading\u2026"
6834
6854
  ] }) : visible.length === 0 ? /* @__PURE__ */ jsx("div", { className: "twc-pop__empty", children: emptyText }) : virtualized ? /* @__PURE__ */ jsxs(Fragment, { children: [
6835
6855
  vTop > 0 ? /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { height: vTop } }) : null,
6836
- vShown.map((r2) => r2.kind === "group" ? /* @__PURE__ */ jsx("div", { className: "twc-pop__group", children: r2.label }, `g${r2.gi}`) : renderOption(r2.o, r2.idx)),
6856
+ vShown.map((r2) => r2.kind === "group" ? /* @__PURE__ */ jsx("div", { className: "twc-pop__group", children: r2.label }, `g${r2.gi}`) : renderRow(r2.o, r2.idx)),
6837
6857
  vBottom > 0 ? /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { height: vBottom } }) : null
6838
6858
  ] }) : fGroups.map((g, gi) => /* @__PURE__ */ jsxs(React51__default.Fragment, { children: [
6839
6859
  g.group ? /* @__PURE__ */ jsx("div", { className: "twc-pop__group", children: g.group }) : null,
6840
6860
  g.options.map((o) => {
6841
6861
  counter += 1;
6842
6862
  const idx = counter;
6843
- return renderOption(o, idx);
6863
+ return renderRow(o, idx);
6844
6864
  })
6845
6865
  ] }, gi)) }),
6846
6866
  /* @__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"}` })
@@ -7144,6 +7164,11 @@ var INPUT_EXTRA_CSS = `
7144
7164
  .twc-input__reveal:hover { background: var(--color-surface-sunken); color: var(--color-text); }
7145
7165
  .twc-input__reveal:focus-visible { outline: none; box-shadow: var(--ring); }
7146
7166
  .twc-input__reveal svg { width: 17px; height: 17px; }
7167
+ /* #328: trailing clear (X) button \u2014 mirrors the reveal toggle; sits alongside rightIcon / the password eye. */
7168
+ .twc-input__clear { display: inline-grid; place-items: center; width: 24px; height: 24px; padding: 0; border: none; background: transparent; color: var(--color-text-subtle); cursor: pointer; border-radius: var(--radius-sm); flex: none; transition: background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard); }
7169
+ .twc-input__clear:hover { background: var(--color-surface-sunken); color: var(--color-text); }
7170
+ .twc-input__clear:focus-visible { outline: none; box-shadow: var(--ring); }
7171
+ .twc-input__clear svg { width: 15px; height: 15px; }
7147
7172
  .twc-input__addon { align-self: stretch; display: inline-flex; align-items: center; padding-inline: var(--space-3);
7148
7173
  background: var(--color-surface-sunken); color: var(--color-text-muted); font-size: var(--text-sm);
7149
7174
  font-weight: var(--font-semibold); white-space: nowrap; flex: none; }
@@ -7165,6 +7190,7 @@ function Input({
7165
7190
  leftAddon,
7166
7191
  rightAddon,
7167
7192
  showCount = false,
7193
+ clearable = false,
7168
7194
  type = "text",
7169
7195
  disabled = false,
7170
7196
  id,
@@ -7186,8 +7212,25 @@ function Input({
7186
7212
  const near = showCounter && count >= Math.floor(max * 0.9);
7187
7213
  const handleChange = (e) => {
7188
7214
  rest.onChange?.(e);
7189
- if (showCount && !countControlled) setLen(e.target.value.length);
7215
+ if ((showCount || clearable) && !countControlled) setLen(e.target.value.length);
7216
+ };
7217
+ const inputRef = React51__default.useRef(null);
7218
+ const userRef = rest.ref;
7219
+ const setRef = React51__default.useCallback((node) => {
7220
+ inputRef.current = node;
7221
+ if (typeof userRef === "function") userRef(node);
7222
+ else if (userRef && typeof userRef === "object") userRef.current = node;
7223
+ }, [userRef]);
7224
+ const clearField = () => {
7225
+ const el = inputRef.current;
7226
+ if (!el) return;
7227
+ const setter = typeof HTMLInputElement !== "undefined" && Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
7228
+ if (setter) setter.call(el, "");
7229
+ else el.value = "";
7230
+ el.dispatchEvent(new Event("input", { bubbles: true }));
7231
+ el.focus();
7190
7232
  };
7233
+ const showClear = clearable && count > 0 && !disabled && !rest.readOnly;
7191
7234
  const ariaInvalid = invalid ? true : rest["aria-invalid"] ?? void 0;
7192
7235
  const describedBy = [error || hint ? descId : null, showCounter ? countId : null, rest["aria-describedby"]].filter(Boolean).join(" ") || void 0;
7193
7236
  const isPassword = type === "password";
@@ -7234,11 +7277,16 @@ function Input({
7234
7277
  required: required || void 0,
7235
7278
  "aria-required": required || void 0,
7236
7279
  ...rest,
7280
+ ref: setRef,
7237
7281
  onChange: handleChange,
7238
7282
  "aria-invalid": ariaInvalid,
7239
7283
  "aria-describedby": describedBy
7240
7284
  }
7241
7285
  ),
7286
+ showClear ? /* @__PURE__ */ jsx("button", { type: "button", className: "twc-input__clear", "aria-label": "Clear", tabIndex: 0, onMouseDown: (e) => e.preventDefault(), onClick: clearField, children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
7287
+ /* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
7288
+ /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
7289
+ ] }) }) : null,
7242
7290
  suffix,
7243
7291
  rightAddon != null ? /* @__PURE__ */ jsx("span", { className: "twc-input__addon twc-input__addon--end", children: rightAddon }) : null
7244
7292
  ] }),
@@ -7326,6 +7374,10 @@ var MULTI_CSS = `
7326
7374
  .twc-opt__main { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
7327
7375
  .twc-opt__label { line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
7328
7376
  .twc-opt__desc { font-size: var(--text-xs); color: var(--color-text-muted); line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
7377
+ /* #326: optional leading icon/swatch + trailing (right-aligned) hint on an option. */
7378
+ .twc-opt__icon { flex: none; display: inline-flex; align-items: center; color: var(--color-text-muted); }
7379
+ .twc-opt__icon svg { width: 16px; height: 16px; }
7380
+ .twc-opt__hint { flex: none; margin-inline-start: auto; padding-inline-start: var(--space-2); color: var(--color-text-muted); font-size: var(--text-xs); white-space: nowrap; }
7329
7381
  .twc-pop__empty { padding: 14px 12px; text-align: center; font-size: var(--text-sm); color: var(--color-text-subtle); }
7330
7382
  .twc-opt[data-disabled="true"] { opacity: 0.45; cursor: not-allowed; pointer-events: none; }
7331
7383
  .twc-pop__loading { display: flex; align-items: center; justify-content: center; gap: var(--space-2); padding: 14px 12px; font-size: var(--text-sm); color: var(--color-text-muted); }
@@ -7387,8 +7439,9 @@ function MultiSelect({
7387
7439
  maxTagCount,
7388
7440
  filter,
7389
7441
  onInputChange,
7390
- virtualized = false,
7442
+ virtualized: virtualizedProp = false,
7391
7443
  overscan = 8,
7444
+ renderOption,
7392
7445
  id,
7393
7446
  className = "",
7394
7447
  onFocus,
@@ -7396,6 +7449,8 @@ function MultiSelect({
7396
7449
  ...rest
7397
7450
  }) {
7398
7451
  const __twcStyles = useScopedStyles("twc-ms-styles", MULTI_CSS);
7452
+ if (virtualizedProp && renderOption) warnOnce("multiselect-renderoption-virtualized", "twico-ui MultiSelect: `renderOption` disables `virtualized` \u2014 custom rows aren't a fixed height.");
7453
+ const virtualized = virtualizedProp && !renderOption;
7399
7454
  const groups = React51__default.useMemo(() => normalizeGroups3(options), [options]);
7400
7455
  const flat = React51__default.useMemo(() => groups.flatMap((g) => g.options), [groups]);
7401
7456
  const autoId = React51__default.useId();
@@ -7555,7 +7610,7 @@ function MultiSelect({
7555
7610
  const activeId = open && visible[active] ? optionId(active) : void 0;
7556
7611
  const descId = `${fieldId}-desc`;
7557
7612
  const describedBy = error || hint ? descId : void 0;
7558
- const renderOption = (o, idx) => {
7613
+ const renderRow = (o, idx) => {
7559
7614
  const isSel = selected.includes(o.value);
7560
7615
  const optDisabled = isOptDisabled(o);
7561
7616
  return /* @__PURE__ */ jsxs(
@@ -7579,9 +7634,13 @@ function MultiSelect({
7579
7634
  onClick: () => toggle(o.value),
7580
7635
  children: [
7581
7636
  /* @__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" }) }) }),
7582
- /* @__PURE__ */ jsxs("span", { className: "twc-opt__main", children: [
7583
- /* @__PURE__ */ jsx("span", { className: "twc-opt__label", children: o.label }),
7584
- o.description ? /* @__PURE__ */ jsx("span", { className: "twc-opt__desc", children: o.description }) : null
7637
+ renderOption ? /* @__PURE__ */ jsx("span", { className: "twc-opt__main", children: renderOption(o, { selected: isSel, active: idx === active }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
7638
+ o.icon != null ? /* @__PURE__ */ jsx("span", { className: "twc-opt__icon", "aria-hidden": "true", children: o.icon }) : null,
7639
+ /* @__PURE__ */ jsxs("span", { className: "twc-opt__main", children: [
7640
+ /* @__PURE__ */ jsx("span", { className: "twc-opt__label", children: o.label }),
7641
+ o.description ? /* @__PURE__ */ jsx("span", { className: "twc-opt__desc", children: o.description }) : null
7642
+ ] }),
7643
+ o.hint != null ? /* @__PURE__ */ jsx("span", { className: "twc-opt__hint", children: o.hint }) : null
7585
7644
  ] })
7586
7645
  ]
7587
7646
  },
@@ -7611,14 +7670,14 @@ function MultiSelect({
7611
7670
  "Loading\u2026"
7612
7671
  ] }) : visible.length === 0 ? /* @__PURE__ */ jsx("div", { className: "twc-pop__empty", children: emptyText }) : virtualized ? /* @__PURE__ */ jsxs(Fragment, { children: [
7613
7672
  vTop > 0 ? /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { height: vTop } }) : null,
7614
- vShown.map((r2) => r2.kind === "group" ? /* @__PURE__ */ jsx("div", { className: "twc-pop__group", children: r2.label }, `g${r2.gi}`) : renderOption(r2.o, r2.idx)),
7673
+ vShown.map((r2) => r2.kind === "group" ? /* @__PURE__ */ jsx("div", { className: "twc-pop__group", children: r2.label }, `g${r2.gi}`) : renderRow(r2.o, r2.idx)),
7615
7674
  vBottom > 0 ? /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { height: vBottom } }) : null
7616
7675
  ] }) : fGroups.map((g, gi) => /* @__PURE__ */ jsxs(React51__default.Fragment, { children: [
7617
7676
  g.group ? /* @__PURE__ */ jsx("div", { className: "twc-pop__group", children: g.group }) : null,
7618
7677
  g.options.map((o) => {
7619
7678
  counter += 1;
7620
7679
  const idx = counter;
7621
- return renderOption(o, idx);
7680
+ return renderRow(o, idx);
7622
7681
  })
7623
7682
  ] }, gi))
7624
7683
  }
@@ -8202,6 +8261,11 @@ var DT_CSS = `
8202
8261
  font-size: var(--text-sm); color: var(--color-text); width: 150px; }
8203
8262
  .twc-dt__search input:focus, .twc-dt__search input:focus-visible { outline: none; box-shadow: none; }
8204
8263
  .twc-dt__search input::placeholder { color: var(--color-text-subtle); }
8264
+ /* #327: trailing clear (X) for the built-in search inputs (quick-search + column-menu search). */
8265
+ .twc-dt__search-clear { border: none; background: transparent; color: var(--color-text-subtle); cursor: pointer; display: inline-grid; place-items: center; width: 20px; height: 20px; padding: 0; border-radius: var(--radius-sm); flex: none; }
8266
+ .twc-dt__search-clear:hover { color: var(--color-text); background: var(--color-surface-sunken); }
8267
+ .twc-dt__search-clear:focus-visible { outline: none; box-shadow: var(--ring); }
8268
+ .twc-dt__search-clear svg { width: 14px; height: 14px; }
8205
8269
  .twc-dt__tlabel { display: inline; }
8206
8270
  /* Narrow grid: collapse the toolbar to icon-only buttons (labels survive as
8207
8271
  hover tooltips via data-tip) and let the search flex so nothing wraps. */
@@ -9162,6 +9226,8 @@ function Datatable({
9162
9226
  onCellClick,
9163
9227
  onActiveCellChange,
9164
9228
  onCellSelectionChange,
9229
+ activeRowId,
9230
+ scrollActiveRowIntoView = true,
9165
9231
  enableClipboard = false,
9166
9232
  onCellsCopy,
9167
9233
  onCellsPaste,
@@ -9294,6 +9360,8 @@ function Datatable({
9294
9360
  };
9295
9361
  const [selected, setSelected] = React51__default.useState(() => /* @__PURE__ */ new Set());
9296
9362
  const [activeRow, setActiveRow] = React51__default.useState(null);
9363
+ const activeRowControlled = activeRowId !== void 0;
9364
+ const activeRowVal = activeRowControlled ? activeRowId : activeRow;
9297
9365
  const [activeCell, setActiveCell] = React51__default.useState(null);
9298
9366
  const [anchorCell, setAnchorCell] = React51__default.useState(null);
9299
9367
  const gridId = React51__default.useId();
@@ -9566,6 +9634,8 @@ function Datatable({
9566
9634
  const colMenuRef = React51__default.useRef(null);
9567
9635
  const rowMenuRef = React51__default.useRef(null);
9568
9636
  const exportMenuRef = React51__default.useRef(null);
9637
+ const quickSearchRef = React51__default.useRef(null);
9638
+ const colSearchRef = React51__default.useRef(null);
9569
9639
  function focusFirstItem(container) {
9570
9640
  if (!container) return;
9571
9641
  const first = container.querySelector('[role="menuitem"]:not([disabled])');
@@ -10281,7 +10351,7 @@ function Datatable({
10281
10351
  if (selectionMode === "none") return;
10282
10352
  if (e.target.closest("button, a, input, select, .twc-dt__check, .twc-dt__editor-wrap")) return;
10283
10353
  if (selectionMode === "row") {
10284
- setActiveRow(k);
10354
+ if (!activeRowControlled) setActiveRow(k);
10285
10355
  onRowClick?.(row, k);
10286
10356
  onActiveCellChange?.(null);
10287
10357
  }
@@ -10360,8 +10430,9 @@ function Datatable({
10360
10430
  }
10361
10431
  if (selectionMode === "row") {
10362
10432
  e.preventDefault();
10363
- setActiveRow(keyOf(leafRows[r2], r2));
10364
- onRowClick?.(leafRows[r2], keyOf(leafRows[r2], r2));
10433
+ const rk = keyOf(leafRows[r2], r2);
10434
+ if (!activeRowControlled) setActiveRow(rk);
10435
+ onRowClick?.(leafRows[r2], rk);
10365
10436
  return;
10366
10437
  }
10367
10438
  handled = false;
@@ -10402,6 +10473,11 @@ function Datatable({
10402
10473
  prevRowSelSigRef.current = sig;
10403
10474
  cb(selKeys, selectedRows);
10404
10475
  }, [selKeys, selectedRows]);
10476
+ React51__default.useEffect(() => {
10477
+ if (!activeRowControlled || !scrollActiveRowIntoView || activeRowId == null) return;
10478
+ const el = gridRef.current && gridRef.current.querySelector('.twc-dt__row[data-active="true"]');
10479
+ if (el && el.scrollIntoView) el.scrollIntoView({ block: "nearest" });
10480
+ }, [activeRowId, activeRowControlled, scrollActiveRowIntoView]);
10405
10481
  function clearSelection() {
10406
10482
  setSelected(/* @__PURE__ */ new Set());
10407
10483
  }
@@ -11119,7 +11195,7 @@ function Datatable({
11119
11195
  function renderLeaf(row, ri, pinSide, midIdx) {
11120
11196
  const k = keyOf(row, ri);
11121
11197
  const sel = selected.has(k);
11122
- const rowActive = selectionMode === "row" && activeRow === k;
11198
+ const rowActive = selectionMode === "row" && activeRowVal === k;
11123
11199
  const stickyStyle = pinSide === "top" ? { position: "sticky", top: headH, zIndex: 5 } : pinSide === "bottom" ? { position: "sticky", bottom: 0, zIndex: 5 } : void 0;
11124
11200
  const h = rowHeights[k];
11125
11201
  const rowStyle = { ...stickyStyle || {}, ...h ? { height: h } : {} };
@@ -11574,7 +11650,11 @@ function Datatable({
11574
11650
  ] }) : null,
11575
11651
  searchable ? /* @__PURE__ */ jsxs("div", { className: "twc-dt__search", children: [
11576
11652
  /* @__PURE__ */ jsx(Svg, { d: I.search }),
11577
- /* @__PURE__ */ jsx("input", { placeholder: "Search\u2026", "aria-label": "Search rows", value: quick, onChange: (e) => commitQuick(e.target.value) })
11653
+ /* @__PURE__ */ jsx("input", { ref: quickSearchRef, placeholder: "Search\u2026", "aria-label": "Search rows", value: quick, onChange: (e) => commitQuick(e.target.value) }),
11654
+ quick ? /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__search-clear", "aria-label": "Clear search", onMouseDown: (e) => e.preventDefault(), onClick: () => {
11655
+ commitQuick("");
11656
+ quickSearchRef.current?.focus();
11657
+ }, children: /* @__PURE__ */ jsx(Svg, { d: I.x }) }) : null
11578
11658
  ] }) : null
11579
11659
  ] }),
11580
11660
  !pivotActive && activeGroupBy.length ? /* @__PURE__ */ jsxs("div", { className: "twc-dt__groupbar", children: [
@@ -12167,7 +12247,11 @@ function Datatable({
12167
12247
  ] }),
12168
12248
  /* @__PURE__ */ jsxs("div", { className: "twc-dt__col-search", children: [
12169
12249
  /* @__PURE__ */ jsx(Svg, { d: I.search }),
12170
- /* @__PURE__ */ jsx("input", { autoFocus: true, placeholder: "Find column\u2026", "aria-label": "Find column", value: colQuery, onChange: (e) => setColQuery(e.target.value) })
12250
+ /* @__PURE__ */ jsx("input", { ref: colSearchRef, autoFocus: true, placeholder: "Find column\u2026", "aria-label": "Find column", value: colQuery, onChange: (e) => setColQuery(e.target.value) }),
12251
+ colQuery ? /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__search-clear", "aria-label": "Clear column search", onMouseDown: (e) => e.preventDefault(), onClick: () => {
12252
+ setColQuery("");
12253
+ colSearchRef.current?.focus();
12254
+ }, children: /* @__PURE__ */ jsx(Svg, { d: I.x }) }) : null
12171
12255
  ] }),
12172
12256
  /* @__PURE__ */ jsxs("div", { className: "twc-dt__col-list", children: [
12173
12257
  rowNumMatch ? /* @__PURE__ */ jsxs("div", { className: "twc-dt__col-row", onClick: () => setShowRowNum((v) => !v), children: [
@@ -12537,6 +12621,7 @@ function Datatable({
12537
12621
  Input,
12538
12622
  {
12539
12623
  size: "sm",
12624
+ clearable: true,
12540
12625
  type: filterTypeOf(col) === "number" ? "number" : "text",
12541
12626
  placeholder: "Value",
12542
12627
  value: f.value,
@@ -17625,6 +17710,12 @@ var TEXTAREA_CSS = `
17625
17710
  .twc-textarea__el:disabled { background: var(--color-surface-sunken); opacity: 0.7; cursor: not-allowed; }
17626
17711
  .twc-textarea__el[data-readonly] { background: var(--color-surface-sunken); }
17627
17712
  .twc-textarea__el[data-autosize="true"] { resize: none; overflow: hidden; }
17713
+ /* #328: clearable (X). Wrap positions the button top-inline-end so it dodges the bottom-right resize handle. */
17714
+ .twc-textarea__wrap { position: relative; display: block; }
17715
+ .twc-textarea__clear { position: absolute; top: 6px; inset-inline-end: 8px; display: inline-grid; place-items: center; width: 22px; height: 22px; padding: 0; border: none; background: var(--color-surface); color: var(--color-text-subtle); cursor: pointer; border-radius: var(--radius-sm); transition: background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard); }
17716
+ .twc-textarea__clear:hover { background: var(--color-surface-sunken); color: var(--color-text); }
17717
+ .twc-textarea__clear:focus-visible { outline: none; box-shadow: var(--ring); }
17718
+ .twc-textarea__clear svg { width: 15px; height: 15px; }
17628
17719
  .twc-field__footer { display: flex; align-items: center; gap: var(--space-2); }
17629
17720
  .twc-field__count { margin-inline-start: auto; font-size: var(--text-xs); color: var(--color-text-muted); font-variant-numeric: tabular-nums; flex: none; }
17630
17721
  .twc-field__count[data-danger="true"] { color: var(--color-danger-subtle-fg); }
@@ -17641,6 +17732,7 @@ var Textarea = React51__default.forwardRef(function Textarea2({
17641
17732
  minRows,
17642
17733
  maxRows,
17643
17734
  showCount = false,
17735
+ clearable = false,
17644
17736
  disabled = false,
17645
17737
  id,
17646
17738
  className = "",
@@ -17687,8 +17779,18 @@ var Textarea = React51__default.forwardRef(function Textarea2({
17687
17779
  };
17688
17780
  const handleChange = (e) => {
17689
17781
  rest.onChange?.(e);
17690
- if (showCount && !countControlled) setLen(e.target.value.length);
17782
+ if ((showCount || clearable) && !countControlled) setLen(e.target.value.length);
17783
+ };
17784
+ const clearField = () => {
17785
+ const el = innerRef.current;
17786
+ if (!el) return;
17787
+ const setter = typeof HTMLTextAreaElement !== "undefined" && Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
17788
+ if (setter) setter.call(el, "");
17789
+ else el.value = "";
17790
+ el.dispatchEvent(new Event("input", { bubbles: true }));
17791
+ el.focus();
17691
17792
  };
17793
+ const showClear = clearable && count > 0 && !disabled && !rest.readOnly;
17692
17794
  const ariaInvalid = invalid ? true : rest["aria-invalid"] ?? void 0;
17693
17795
  const describedBy = [error || hint ? descId : null, showCounter ? countId : null, rest["aria-describedby"]].filter(Boolean).join(" ") || void 0;
17694
17796
  return /* @__PURE__ */ jsxs("div", { className: `twc-field ${className}`, children: [
@@ -17697,27 +17799,33 @@ var Textarea = React51__default.forwardRef(function Textarea2({
17697
17799
  label,
17698
17800
  required ? /* @__PURE__ */ jsx("span", { className: "twc-field__req", children: "*" }) : null
17699
17801
  ] }) : null,
17700
- /* @__PURE__ */ jsx(
17701
- "textarea",
17702
- {
17703
- ref: setRef,
17704
- id: fieldId,
17705
- className: "twc-textarea__el",
17706
- "data-size": size,
17707
- "data-tone": tone,
17708
- "data-autosize": autosize || void 0,
17709
- "data-readonly": rest.readOnly || void 0,
17710
- rows: autosize ? minRows ?? rows : rows,
17711
- disabled,
17712
- required: required || void 0,
17713
- "aria-required": required || void 0,
17714
- ...rest,
17715
- onInput: handleInput,
17716
- onChange: handleChange,
17717
- "aria-invalid": ariaInvalid,
17718
- "aria-describedby": describedBy
17719
- }
17720
- ),
17802
+ /* @__PURE__ */ jsxs("div", { className: "twc-textarea__wrap", children: [
17803
+ /* @__PURE__ */ jsx(
17804
+ "textarea",
17805
+ {
17806
+ ref: setRef,
17807
+ id: fieldId,
17808
+ className: "twc-textarea__el",
17809
+ "data-size": size,
17810
+ "data-tone": tone,
17811
+ "data-autosize": autosize || void 0,
17812
+ "data-readonly": rest.readOnly || void 0,
17813
+ rows: autosize ? minRows ?? rows : rows,
17814
+ disabled,
17815
+ required: required || void 0,
17816
+ "aria-required": required || void 0,
17817
+ ...rest,
17818
+ onInput: handleInput,
17819
+ onChange: handleChange,
17820
+ "aria-invalid": ariaInvalid,
17821
+ "aria-describedby": describedBy
17822
+ }
17823
+ ),
17824
+ showClear ? /* @__PURE__ */ jsx("button", { type: "button", className: "twc-textarea__clear", "aria-label": "Clear", tabIndex: 0, onMouseDown: (e) => e.preventDefault(), onClick: clearField, children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
17825
+ /* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
17826
+ /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
17827
+ ] }) }) : null
17828
+ ] }),
17721
17829
  error || hint || showCounter ? /* @__PURE__ */ jsxs("div", { className: "twc-field__footer", children: [
17722
17830
  error ? /* @__PURE__ */ jsx("span", { id: descId, className: "twc-field__error", children: error }) : hint ? /* @__PURE__ */ jsx("span", { id: descId, className: "twc-field__hint", children: hint }) : null,
17723
17831
  showCounter ? /* @__PURE__ */ jsxs("span", { id: countId, className: "twc-field__count", "data-danger": near || void 0, "aria-live": "polite", children: [