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/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to Twico UI are documented here (newest first). This file is generated automatically on each release by [semantic-release](https://semantic-release.gitbook.io/) from [Conventional Commits](https://www.conventionalcommits.org/) — do not edit it by hand. The same notes appear on each [GitHub Release](https://github.com/EncryptionL/twico-ui/releases).
4
4
 
5
+ # [1.30.0](https://github.com/EncryptionL/twico-ui/compare/v1.29.0...v1.30.0) (2026-08-04)
6
+
7
+
8
+ ### Features
9
+
10
+ * **inputs,datatable:** clearable inputs, richer select options, and datatable input clears ([e5eadbc](https://github.com/EncryptionL/twico-ui/commit/e5eadbce72db3ed96487bc7097d49c7c7caa6dfb)), closes [#328](https://github.com/EncryptionL/twico-ui/issues/328) [#326](https://github.com/EncryptionL/twico-ui/issues/326) [#327](https://github.com/EncryptionL/twico-ui/issues/327) [#326](https://github.com/EncryptionL/twico-ui/issues/326) [#327](https://github.com/EncryptionL/twico-ui/issues/327) [#328](https://github.com/EncryptionL/twico-ui/issues/328)
11
+
12
+ # [1.29.0](https://github.com/EncryptionL/twico-ui/compare/v1.28.0...v1.29.0) (2026-08-03)
13
+
14
+
15
+ ### Features
16
+
17
+ * **datatable:** controlled active row for selectionMode="row" (activeRowId) ([#324](https://github.com/EncryptionL/twico-ui/issues/324)) ([c118a06](https://github.com/EncryptionL/twico-ui/commit/c118a069d3f91ce9640b39751cab24717569be2b))
18
+
5
19
  # [1.28.0](https://github.com/EncryptionL/twico-ui/compare/v1.27.0...v1.28.0) (2026-08-03)
6
20
 
7
21
 
package/dist/index.cjs CHANGED
@@ -5671,6 +5671,10 @@ var COMBO_CSS = `
5671
5671
  .twc-opt__main { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
5672
5672
  .twc-opt__label { line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
5673
5673
  .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; }
5674
+ /* #326: optional leading icon/swatch + trailing (right-aligned) hint on an option. */
5675
+ .twc-opt__icon { flex: none; display: inline-flex; align-items: center; color: var(--color-text-muted); }
5676
+ .twc-opt__icon svg { width: 16px; height: 16px; }
5677
+ .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; }
5674
5678
  /* #300: wrapOptions \u2014 let long labels/descriptions wrap onto multiple lines instead of truncating.
5675
5679
  overflow-wrap: anywhere also breaks a single very long token so it can't overflow the popover edge. */
5676
5680
  .twc-opt[data-wrap="true"] { align-items: flex-start; }
@@ -5738,6 +5742,7 @@ function Combobox({
5738
5742
  virtualized: virtualizedProp = false,
5739
5743
  overscan = 8,
5740
5744
  wrapOptions = false,
5745
+ renderOption,
5741
5746
  id,
5742
5747
  className = "",
5743
5748
  onFocus,
@@ -5745,8 +5750,8 @@ function Combobox({
5745
5750
  ...rest
5746
5751
  }) {
5747
5752
  const __twcStyles = useScopedStyles("twc-combo-styles", COMBO_CSS);
5748
- if (virtualizedProp && wrapOptions) warnOnce("combobox-wrap-virtualized", "twico-ui Combobox: `wrapOptions` disables `virtualized` \u2014 wrapped option rows aren't a fixed height.");
5749
- const virtualized = virtualizedProp && !wrapOptions;
5753
+ if (virtualizedProp && (wrapOptions || renderOption)) warnOnce("combobox-var-height-virtualized", "twico-ui Combobox: `wrapOptions`/`renderOption` disable `virtualized` \u2014 those rows aren't a fixed height.");
5754
+ const virtualized = virtualizedProp && !wrapOptions && !renderOption;
5750
5755
  const groups = React51__namespace.default.useMemo(() => normalizeGroups(options), [options]);
5751
5756
  const flat = React51__namespace.default.useMemo(() => groups.flatMap((g) => g.options), [groups]);
5752
5757
  const autoId = React51__namespace.default.useId();
@@ -5917,7 +5922,7 @@ function Combobox({
5917
5922
  const activeId = open && visible[active] ? optionId(active) : void 0;
5918
5923
  const descId = `${fieldId}-desc`;
5919
5924
  const describedBy = error || hint ? descId : void 0;
5920
- const renderOption = (o, idx) => {
5925
+ const renderRow = (o, idx) => {
5921
5926
  const isSel = o.value === current;
5922
5927
  return /* @__PURE__ */ jsxRuntime.jsxs(
5923
5928
  "button",
@@ -5940,9 +5945,13 @@ function Combobox({
5940
5945
  onMouseDown: (e) => e.preventDefault(),
5941
5946
  onClick: () => commit(o.value),
5942
5947
  children: [
5943
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-opt__main", children: [
5944
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__label", children: o.label }),
5945
- o.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__desc", children: o.description }) : null
5948
+ renderOption ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__main", children: renderOption(o, { selected: isSel, active: idx === active }) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5949
+ o.icon != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__icon", "aria-hidden": "true", children: o.icon }) : null,
5950
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-opt__main", children: [
5951
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__label", children: o.label }),
5952
+ o.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__desc", children: o.description }) : null
5953
+ ] }),
5954
+ o.hint != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__hint", children: o.hint }) : null
5946
5955
  ] }),
5947
5956
  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
5948
5957
  ]
@@ -5964,14 +5973,14 @@ function Combobox({
5964
5973
  "Loading\u2026"
5965
5974
  ] }) : visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__empty", children: emptyText }) : virtualized ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5966
5975
  vTop > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { height: vTop } }) : null,
5967
- vShown.map((r2) => r2.kind === "group" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__group", children: r2.label }, `g${r2.gi}`) : renderOption(r2.o, r2.idx)),
5976
+ vShown.map((r2) => r2.kind === "group" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__group", children: r2.label }, `g${r2.gi}`) : renderRow(r2.o, r2.idx)),
5968
5977
  vBottom > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { height: vBottom } }) : null
5969
5978
  ] }) : fGroups.map((g, gi) => /* @__PURE__ */ jsxRuntime.jsxs(React51__namespace.default.Fragment, { children: [
5970
5979
  g.group ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__group", children: g.group }) : null,
5971
5980
  g.options.map((o) => {
5972
5981
  counter += 1;
5973
5982
  const idx = counter;
5974
- return renderOption(o, idx);
5983
+ return renderRow(o, idx);
5975
5984
  })
5976
5985
  ] }, gi)) });
5977
5986
  const RD = { createPortal: typeof reactDom.createPortal === "function" ? reactDom.createPortal : typeof window !== "undefined" && window.ReactDOM && window.ReactDOM.createPortal };
@@ -6508,6 +6517,10 @@ var SELECT_CSS = `
6508
6517
  .twc-opt__main { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
6509
6518
  .twc-opt__label { line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
6510
6519
  .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; }
6520
+ /* #326: optional leading icon/swatch + trailing (right-aligned) hint on an option. */
6521
+ .twc-opt__icon { flex: none; display: inline-flex; align-items: center; color: var(--color-text-muted); }
6522
+ .twc-opt__icon svg { width: 16px; height: 16px; }
6523
+ .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; }
6511
6524
  .twc-opt__check { flex: none; color: var(--color-primary); display: inline-flex; }
6512
6525
  .twc-opt__check svg { width: 16px; height: 16px; }
6513
6526
  .twc-pop__empty { padding: 14px 12px; text-align: center; font-size: var(--text-sm); color: var(--color-text-subtle); }
@@ -6568,8 +6581,9 @@ function Select({
6568
6581
  loading = false,
6569
6582
  emptyText = "No results found",
6570
6583
  name,
6571
- virtualized = false,
6584
+ virtualized: virtualizedProp = false,
6572
6585
  overscan = 8,
6586
+ renderOption,
6573
6587
  id,
6574
6588
  className = "",
6575
6589
  onClick,
@@ -6577,6 +6591,8 @@ function Select({
6577
6591
  ...rest
6578
6592
  }) {
6579
6593
  const __twcStyles = useScopedStyles("twc-select-styles", SELECT_CSS);
6594
+ if (virtualizedProp && renderOption) warnOnce("select-renderoption-virtualized", "twico-ui Select: `renderOption` disables `virtualized` \u2014 custom rows aren't a fixed height.");
6595
+ const virtualized = virtualizedProp && !renderOption;
6580
6596
  const bufferRef = React51__namespace.default.useRef("");
6581
6597
  const bufferTimerRef = React51__namespace.default.useRef(null);
6582
6598
  const groups = React51__namespace.default.useMemo(() => normalizeGroups2(options), [options]);
@@ -6782,7 +6798,7 @@ function Select({
6782
6798
  const activeId = open && visible[active] ? optionId(active) : void 0;
6783
6799
  const descId = `${fieldId}-desc`;
6784
6800
  const describedBy = error || hint ? descId : void 0;
6785
- const renderOption = (o, idx) => {
6801
+ const renderRow = (o, idx) => {
6786
6802
  const isSel = o.value === current;
6787
6803
  return /* @__PURE__ */ jsxRuntime.jsxs(
6788
6804
  "button",
@@ -6804,9 +6820,13 @@ function Select({
6804
6820
  },
6805
6821
  onClick: () => commit(o.value),
6806
6822
  children: [
6807
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-opt__main", children: [
6808
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__label", children: o.label }),
6809
- o.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__desc", children: o.description }) : null
6823
+ renderOption ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__main", children: renderOption(o, { selected: isSel, active: idx === active }) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6824
+ o.icon != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__icon", "aria-hidden": "true", children: o.icon }) : null,
6825
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-opt__main", children: [
6826
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__label", children: o.label }),
6827
+ o.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__desc", children: o.description }) : null
6828
+ ] }),
6829
+ o.hint != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__hint", children: o.hint }) : null
6810
6830
  ] }),
6811
6831
  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
6812
6832
  ]
@@ -6854,14 +6874,14 @@ function Select({
6854
6874
  "Loading\u2026"
6855
6875
  ] }) : visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__empty", children: emptyText }) : virtualized ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6856
6876
  vTop > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { height: vTop } }) : null,
6857
- vShown.map((r2) => r2.kind === "group" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__group", children: r2.label }, `g${r2.gi}`) : renderOption(r2.o, r2.idx)),
6877
+ vShown.map((r2) => r2.kind === "group" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__group", children: r2.label }, `g${r2.gi}`) : renderRow(r2.o, r2.idx)),
6858
6878
  vBottom > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { height: vBottom } }) : null
6859
6879
  ] }) : fGroups.map((g, gi) => /* @__PURE__ */ jsxRuntime.jsxs(React51__namespace.default.Fragment, { children: [
6860
6880
  g.group ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__group", children: g.group }) : null,
6861
6881
  g.options.map((o) => {
6862
6882
  counter += 1;
6863
6883
  const idx = counter;
6864
- return renderOption(o, idx);
6884
+ return renderRow(o, idx);
6865
6885
  })
6866
6886
  ] }, gi)) }),
6867
6887
  /* @__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"}` })
@@ -7165,6 +7185,11 @@ var INPUT_EXTRA_CSS = `
7165
7185
  .twc-input__reveal:hover { background: var(--color-surface-sunken); color: var(--color-text); }
7166
7186
  .twc-input__reveal:focus-visible { outline: none; box-shadow: var(--ring); }
7167
7187
  .twc-input__reveal svg { width: 17px; height: 17px; }
7188
+ /* #328: trailing clear (X) button \u2014 mirrors the reveal toggle; sits alongside rightIcon / the password eye. */
7189
+ .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); }
7190
+ .twc-input__clear:hover { background: var(--color-surface-sunken); color: var(--color-text); }
7191
+ .twc-input__clear:focus-visible { outline: none; box-shadow: var(--ring); }
7192
+ .twc-input__clear svg { width: 15px; height: 15px; }
7168
7193
  .twc-input__addon { align-self: stretch; display: inline-flex; align-items: center; padding-inline: var(--space-3);
7169
7194
  background: var(--color-surface-sunken); color: var(--color-text-muted); font-size: var(--text-sm);
7170
7195
  font-weight: var(--font-semibold); white-space: nowrap; flex: none; }
@@ -7186,6 +7211,7 @@ function Input({
7186
7211
  leftAddon,
7187
7212
  rightAddon,
7188
7213
  showCount = false,
7214
+ clearable = false,
7189
7215
  type = "text",
7190
7216
  disabled = false,
7191
7217
  id,
@@ -7207,8 +7233,25 @@ function Input({
7207
7233
  const near = showCounter && count >= Math.floor(max * 0.9);
7208
7234
  const handleChange = (e) => {
7209
7235
  rest.onChange?.(e);
7210
- if (showCount && !countControlled) setLen(e.target.value.length);
7236
+ if ((showCount || clearable) && !countControlled) setLen(e.target.value.length);
7237
+ };
7238
+ const inputRef = React51__namespace.default.useRef(null);
7239
+ const userRef = rest.ref;
7240
+ const setRef = React51__namespace.default.useCallback((node) => {
7241
+ inputRef.current = node;
7242
+ if (typeof userRef === "function") userRef(node);
7243
+ else if (userRef && typeof userRef === "object") userRef.current = node;
7244
+ }, [userRef]);
7245
+ const clearField = () => {
7246
+ const el = inputRef.current;
7247
+ if (!el) return;
7248
+ const setter = typeof HTMLInputElement !== "undefined" && Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
7249
+ if (setter) setter.call(el, "");
7250
+ else el.value = "";
7251
+ el.dispatchEvent(new Event("input", { bubbles: true }));
7252
+ el.focus();
7211
7253
  };
7254
+ const showClear = clearable && count > 0 && !disabled && !rest.readOnly;
7212
7255
  const ariaInvalid = invalid ? true : rest["aria-invalid"] ?? void 0;
7213
7256
  const describedBy = [error || hint ? descId : null, showCounter ? countId : null, rest["aria-describedby"]].filter(Boolean).join(" ") || void 0;
7214
7257
  const isPassword = type === "password";
@@ -7255,11 +7298,16 @@ function Input({
7255
7298
  required: required || void 0,
7256
7299
  "aria-required": required || void 0,
7257
7300
  ...rest,
7301
+ ref: setRef,
7258
7302
  onChange: handleChange,
7259
7303
  "aria-invalid": ariaInvalid,
7260
7304
  "aria-describedby": describedBy
7261
7305
  }
7262
7306
  ),
7307
+ showClear ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-input__clear", "aria-label": "Clear", tabIndex: 0, onMouseDown: (e) => e.preventDefault(), onClick: clearField, children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
7308
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18" }),
7309
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 6 12 12" })
7310
+ ] }) }) : null,
7263
7311
  suffix,
7264
7312
  rightAddon != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-input__addon twc-input__addon--end", children: rightAddon }) : null
7265
7313
  ] }),
@@ -7347,6 +7395,10 @@ var MULTI_CSS = `
7347
7395
  .twc-opt__main { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
7348
7396
  .twc-opt__label { line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
7349
7397
  .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; }
7398
+ /* #326: optional leading icon/swatch + trailing (right-aligned) hint on an option. */
7399
+ .twc-opt__icon { flex: none; display: inline-flex; align-items: center; color: var(--color-text-muted); }
7400
+ .twc-opt__icon svg { width: 16px; height: 16px; }
7401
+ .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; }
7350
7402
  .twc-pop__empty { padding: 14px 12px; text-align: center; font-size: var(--text-sm); color: var(--color-text-subtle); }
7351
7403
  .twc-opt[data-disabled="true"] { opacity: 0.45; cursor: not-allowed; pointer-events: none; }
7352
7404
  .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); }
@@ -7408,8 +7460,9 @@ function MultiSelect({
7408
7460
  maxTagCount,
7409
7461
  filter,
7410
7462
  onInputChange,
7411
- virtualized = false,
7463
+ virtualized: virtualizedProp = false,
7412
7464
  overscan = 8,
7465
+ renderOption,
7413
7466
  id,
7414
7467
  className = "",
7415
7468
  onFocus,
@@ -7417,6 +7470,8 @@ function MultiSelect({
7417
7470
  ...rest
7418
7471
  }) {
7419
7472
  const __twcStyles = useScopedStyles("twc-ms-styles", MULTI_CSS);
7473
+ if (virtualizedProp && renderOption) warnOnce("multiselect-renderoption-virtualized", "twico-ui MultiSelect: `renderOption` disables `virtualized` \u2014 custom rows aren't a fixed height.");
7474
+ const virtualized = virtualizedProp && !renderOption;
7420
7475
  const groups = React51__namespace.default.useMemo(() => normalizeGroups3(options), [options]);
7421
7476
  const flat = React51__namespace.default.useMemo(() => groups.flatMap((g) => g.options), [groups]);
7422
7477
  const autoId = React51__namespace.default.useId();
@@ -7576,7 +7631,7 @@ function MultiSelect({
7576
7631
  const activeId = open && visible[active] ? optionId(active) : void 0;
7577
7632
  const descId = `${fieldId}-desc`;
7578
7633
  const describedBy = error || hint ? descId : void 0;
7579
- const renderOption = (o, idx) => {
7634
+ const renderRow = (o, idx) => {
7580
7635
  const isSel = selected.includes(o.value);
7581
7636
  const optDisabled = isOptDisabled(o);
7582
7637
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -7600,9 +7655,13 @@ function MultiSelect({
7600
7655
  onClick: () => toggle(o.value),
7601
7656
  children: [
7602
7657
  /* @__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" }) }) }),
7603
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-opt__main", children: [
7604
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__label", children: o.label }),
7605
- o.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__desc", children: o.description }) : null
7658
+ renderOption ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__main", children: renderOption(o, { selected: isSel, active: idx === active }) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7659
+ o.icon != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__icon", "aria-hidden": "true", children: o.icon }) : null,
7660
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "twc-opt__main", children: [
7661
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__label", children: o.label }),
7662
+ o.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__desc", children: o.description }) : null
7663
+ ] }),
7664
+ o.hint != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-opt__hint", children: o.hint }) : null
7606
7665
  ] })
7607
7666
  ]
7608
7667
  },
@@ -7632,14 +7691,14 @@ function MultiSelect({
7632
7691
  "Loading\u2026"
7633
7692
  ] }) : visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__empty", children: emptyText }) : virtualized ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7634
7693
  vTop > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { height: vTop } }) : null,
7635
- vShown.map((r2) => r2.kind === "group" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__group", children: r2.label }, `g${r2.gi}`) : renderOption(r2.o, r2.idx)),
7694
+ vShown.map((r2) => r2.kind === "group" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__group", children: r2.label }, `g${r2.gi}`) : renderRow(r2.o, r2.idx)),
7636
7695
  vBottom > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { height: vBottom } }) : null
7637
7696
  ] }) : fGroups.map((g, gi) => /* @__PURE__ */ jsxRuntime.jsxs(React51__namespace.default.Fragment, { children: [
7638
7697
  g.group ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-pop__group", children: g.group }) : null,
7639
7698
  g.options.map((o) => {
7640
7699
  counter += 1;
7641
7700
  const idx = counter;
7642
- return renderOption(o, idx);
7701
+ return renderRow(o, idx);
7643
7702
  })
7644
7703
  ] }, gi))
7645
7704
  }
@@ -8223,6 +8282,11 @@ var DT_CSS = `
8223
8282
  font-size: var(--text-sm); color: var(--color-text); width: 150px; }
8224
8283
  .twc-dt__search input:focus, .twc-dt__search input:focus-visible { outline: none; box-shadow: none; }
8225
8284
  .twc-dt__search input::placeholder { color: var(--color-text-subtle); }
8285
+ /* #327: trailing clear (X) for the built-in search inputs (quick-search + column-menu search). */
8286
+ .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; }
8287
+ .twc-dt__search-clear:hover { color: var(--color-text); background: var(--color-surface-sunken); }
8288
+ .twc-dt__search-clear:focus-visible { outline: none; box-shadow: var(--ring); }
8289
+ .twc-dt__search-clear svg { width: 14px; height: 14px; }
8226
8290
  .twc-dt__tlabel { display: inline; }
8227
8291
  /* Narrow grid: collapse the toolbar to icon-only buttons (labels survive as
8228
8292
  hover tooltips via data-tip) and let the search flex so nothing wraps. */
@@ -9183,6 +9247,8 @@ function Datatable({
9183
9247
  onCellClick,
9184
9248
  onActiveCellChange,
9185
9249
  onCellSelectionChange,
9250
+ activeRowId,
9251
+ scrollActiveRowIntoView = true,
9186
9252
  enableClipboard = false,
9187
9253
  onCellsCopy,
9188
9254
  onCellsPaste,
@@ -9315,6 +9381,8 @@ function Datatable({
9315
9381
  };
9316
9382
  const [selected, setSelected] = React51__namespace.default.useState(() => /* @__PURE__ */ new Set());
9317
9383
  const [activeRow, setActiveRow] = React51__namespace.default.useState(null);
9384
+ const activeRowControlled = activeRowId !== void 0;
9385
+ const activeRowVal = activeRowControlled ? activeRowId : activeRow;
9318
9386
  const [activeCell, setActiveCell] = React51__namespace.default.useState(null);
9319
9387
  const [anchorCell, setAnchorCell] = React51__namespace.default.useState(null);
9320
9388
  const gridId = React51__namespace.default.useId();
@@ -9587,6 +9655,8 @@ function Datatable({
9587
9655
  const colMenuRef = React51__namespace.default.useRef(null);
9588
9656
  const rowMenuRef = React51__namespace.default.useRef(null);
9589
9657
  const exportMenuRef = React51__namespace.default.useRef(null);
9658
+ const quickSearchRef = React51__namespace.default.useRef(null);
9659
+ const colSearchRef = React51__namespace.default.useRef(null);
9590
9660
  function focusFirstItem(container) {
9591
9661
  if (!container) return;
9592
9662
  const first = container.querySelector('[role="menuitem"]:not([disabled])');
@@ -10302,7 +10372,7 @@ function Datatable({
10302
10372
  if (selectionMode === "none") return;
10303
10373
  if (e.target.closest("button, a, input, select, .twc-dt__check, .twc-dt__editor-wrap")) return;
10304
10374
  if (selectionMode === "row") {
10305
- setActiveRow(k);
10375
+ if (!activeRowControlled) setActiveRow(k);
10306
10376
  onRowClick?.(row, k);
10307
10377
  onActiveCellChange?.(null);
10308
10378
  }
@@ -10381,8 +10451,9 @@ function Datatable({
10381
10451
  }
10382
10452
  if (selectionMode === "row") {
10383
10453
  e.preventDefault();
10384
- setActiveRow(keyOf(leafRows[r2], r2));
10385
- onRowClick?.(leafRows[r2], keyOf(leafRows[r2], r2));
10454
+ const rk = keyOf(leafRows[r2], r2);
10455
+ if (!activeRowControlled) setActiveRow(rk);
10456
+ onRowClick?.(leafRows[r2], rk);
10386
10457
  return;
10387
10458
  }
10388
10459
  handled = false;
@@ -10423,6 +10494,11 @@ function Datatable({
10423
10494
  prevRowSelSigRef.current = sig;
10424
10495
  cb(selKeys, selectedRows);
10425
10496
  }, [selKeys, selectedRows]);
10497
+ React51__namespace.default.useEffect(() => {
10498
+ if (!activeRowControlled || !scrollActiveRowIntoView || activeRowId == null) return;
10499
+ const el = gridRef.current && gridRef.current.querySelector('.twc-dt__row[data-active="true"]');
10500
+ if (el && el.scrollIntoView) el.scrollIntoView({ block: "nearest" });
10501
+ }, [activeRowId, activeRowControlled, scrollActiveRowIntoView]);
10426
10502
  function clearSelection() {
10427
10503
  setSelected(/* @__PURE__ */ new Set());
10428
10504
  }
@@ -11140,7 +11216,7 @@ function Datatable({
11140
11216
  function renderLeaf(row, ri, pinSide, midIdx) {
11141
11217
  const k = keyOf(row, ri);
11142
11218
  const sel = selected.has(k);
11143
- const rowActive = selectionMode === "row" && activeRow === k;
11219
+ const rowActive = selectionMode === "row" && activeRowVal === k;
11144
11220
  const stickyStyle = pinSide === "top" ? { position: "sticky", top: headH, zIndex: 5 } : pinSide === "bottom" ? { position: "sticky", bottom: 0, zIndex: 5 } : void 0;
11145
11221
  const h = rowHeights[k];
11146
11222
  const rowStyle = { ...stickyStyle || {}, ...h ? { height: h } : {} };
@@ -11595,7 +11671,11 @@ function Datatable({
11595
11671
  ] }) : null,
11596
11672
  searchable ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__search", children: [
11597
11673
  /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.search }),
11598
- /* @__PURE__ */ jsxRuntime.jsx("input", { placeholder: "Search\u2026", "aria-label": "Search rows", value: quick, onChange: (e) => commitQuick(e.target.value) })
11674
+ /* @__PURE__ */ jsxRuntime.jsx("input", { ref: quickSearchRef, placeholder: "Search\u2026", "aria-label": "Search rows", value: quick, onChange: (e) => commitQuick(e.target.value) }),
11675
+ quick ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__search-clear", "aria-label": "Clear search", onMouseDown: (e) => e.preventDefault(), onClick: () => {
11676
+ commitQuick("");
11677
+ quickSearchRef.current?.focus();
11678
+ }, children: /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.x }) }) : null
11599
11679
  ] }) : null
11600
11680
  ] }),
11601
11681
  !pivotActive && activeGroupBy.length ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__groupbar", children: [
@@ -12188,7 +12268,11 @@ function Datatable({
12188
12268
  ] }),
12189
12269
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__col-search", children: [
12190
12270
  /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.search }),
12191
- /* @__PURE__ */ jsxRuntime.jsx("input", { autoFocus: true, placeholder: "Find column\u2026", "aria-label": "Find column", value: colQuery, onChange: (e) => setColQuery(e.target.value) })
12271
+ /* @__PURE__ */ jsxRuntime.jsx("input", { ref: colSearchRef, autoFocus: true, placeholder: "Find column\u2026", "aria-label": "Find column", value: colQuery, onChange: (e) => setColQuery(e.target.value) }),
12272
+ colQuery ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__search-clear", "aria-label": "Clear column search", onMouseDown: (e) => e.preventDefault(), onClick: () => {
12273
+ setColQuery("");
12274
+ colSearchRef.current?.focus();
12275
+ }, children: /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.x }) }) : null
12192
12276
  ] }),
12193
12277
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__col-list", children: [
12194
12278
  rowNumMatch ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__col-row", onClick: () => setShowRowNum((v) => !v), children: [
@@ -12558,6 +12642,7 @@ function Datatable({
12558
12642
  Input,
12559
12643
  {
12560
12644
  size: "sm",
12645
+ clearable: true,
12561
12646
  type: filterTypeOf(col) === "number" ? "number" : "text",
12562
12647
  placeholder: "Value",
12563
12648
  value: f.value,
@@ -17646,6 +17731,12 @@ var TEXTAREA_CSS = `
17646
17731
  .twc-textarea__el:disabled { background: var(--color-surface-sunken); opacity: 0.7; cursor: not-allowed; }
17647
17732
  .twc-textarea__el[data-readonly] { background: var(--color-surface-sunken); }
17648
17733
  .twc-textarea__el[data-autosize="true"] { resize: none; overflow: hidden; }
17734
+ /* #328: clearable (X). Wrap positions the button top-inline-end so it dodges the bottom-right resize handle. */
17735
+ .twc-textarea__wrap { position: relative; display: block; }
17736
+ .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); }
17737
+ .twc-textarea__clear:hover { background: var(--color-surface-sunken); color: var(--color-text); }
17738
+ .twc-textarea__clear:focus-visible { outline: none; box-shadow: var(--ring); }
17739
+ .twc-textarea__clear svg { width: 15px; height: 15px; }
17649
17740
  .twc-field__footer { display: flex; align-items: center; gap: var(--space-2); }
17650
17741
  .twc-field__count { margin-inline-start: auto; font-size: var(--text-xs); color: var(--color-text-muted); font-variant-numeric: tabular-nums; flex: none; }
17651
17742
  .twc-field__count[data-danger="true"] { color: var(--color-danger-subtle-fg); }
@@ -17662,6 +17753,7 @@ var Textarea = React51__namespace.default.forwardRef(function Textarea2({
17662
17753
  minRows,
17663
17754
  maxRows,
17664
17755
  showCount = false,
17756
+ clearable = false,
17665
17757
  disabled = false,
17666
17758
  id,
17667
17759
  className = "",
@@ -17708,8 +17800,18 @@ var Textarea = React51__namespace.default.forwardRef(function Textarea2({
17708
17800
  };
17709
17801
  const handleChange = (e) => {
17710
17802
  rest.onChange?.(e);
17711
- if (showCount && !countControlled) setLen(e.target.value.length);
17803
+ if ((showCount || clearable) && !countControlled) setLen(e.target.value.length);
17804
+ };
17805
+ const clearField = () => {
17806
+ const el = innerRef.current;
17807
+ if (!el) return;
17808
+ const setter = typeof HTMLTextAreaElement !== "undefined" && Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
17809
+ if (setter) setter.call(el, "");
17810
+ else el.value = "";
17811
+ el.dispatchEvent(new Event("input", { bubbles: true }));
17812
+ el.focus();
17712
17813
  };
17814
+ const showClear = clearable && count > 0 && !disabled && !rest.readOnly;
17713
17815
  const ariaInvalid = invalid ? true : rest["aria-invalid"] ?? void 0;
17714
17816
  const describedBy = [error || hint ? descId : null, showCounter ? countId : null, rest["aria-describedby"]].filter(Boolean).join(" ") || void 0;
17715
17817
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `twc-field ${className}`, children: [
@@ -17718,27 +17820,33 @@ var Textarea = React51__namespace.default.forwardRef(function Textarea2({
17718
17820
  label,
17719
17821
  required ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-field__req", children: "*" }) : null
17720
17822
  ] }) : null,
17721
- /* @__PURE__ */ jsxRuntime.jsx(
17722
- "textarea",
17723
- {
17724
- ref: setRef,
17725
- id: fieldId,
17726
- className: "twc-textarea__el",
17727
- "data-size": size,
17728
- "data-tone": tone,
17729
- "data-autosize": autosize || void 0,
17730
- "data-readonly": rest.readOnly || void 0,
17731
- rows: autosize ? minRows ?? rows : rows,
17732
- disabled,
17733
- required: required || void 0,
17734
- "aria-required": required || void 0,
17735
- ...rest,
17736
- onInput: handleInput,
17737
- onChange: handleChange,
17738
- "aria-invalid": ariaInvalid,
17739
- "aria-describedby": describedBy
17740
- }
17741
- ),
17823
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-textarea__wrap", children: [
17824
+ /* @__PURE__ */ jsxRuntime.jsx(
17825
+ "textarea",
17826
+ {
17827
+ ref: setRef,
17828
+ id: fieldId,
17829
+ className: "twc-textarea__el",
17830
+ "data-size": size,
17831
+ "data-tone": tone,
17832
+ "data-autosize": autosize || void 0,
17833
+ "data-readonly": rest.readOnly || void 0,
17834
+ rows: autosize ? minRows ?? rows : rows,
17835
+ disabled,
17836
+ required: required || void 0,
17837
+ "aria-required": required || void 0,
17838
+ ...rest,
17839
+ onInput: handleInput,
17840
+ onChange: handleChange,
17841
+ "aria-invalid": ariaInvalid,
17842
+ "aria-describedby": describedBy
17843
+ }
17844
+ ),
17845
+ showClear ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-textarea__clear", "aria-label": "Clear", tabIndex: 0, onMouseDown: (e) => e.preventDefault(), onClick: clearField, children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
17846
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18" }),
17847
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 6 12 12" })
17848
+ ] }) }) : null
17849
+ ] }),
17742
17850
  error || hint || showCounter ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-field__footer", children: [
17743
17851
  error ? /* @__PURE__ */ jsxRuntime.jsx("span", { id: descId, className: "twc-field__error", children: error }) : hint ? /* @__PURE__ */ jsxRuntime.jsx("span", { id: descId, className: "twc-field__hint", children: hint }) : null,
17744
17852
  showCounter ? /* @__PURE__ */ jsxRuntime.jsxs("span", { id: countId, className: "twc-field__count", "data-danger": near || void 0, "aria-live": "polite", children: [