twico-ui 1.29.0 → 1.31.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);
7190
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();
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
  }
@@ -8131,6 +8190,8 @@ function classifyDiff(from = [], to = [], columns = [], rowKey, opts = {}) {
8131
8190
  });
8132
8191
  return { rows, counts };
8133
8192
  }
8193
+ var DT_FILTER_PANEL_W = 620;
8194
+ var DT_FILTER_LOGIC_W = 68;
8134
8195
  var DT_CSS = `
8135
8196
  .twc-dt { display: flex; flex-direction: column; font-family: var(--font-sans); color: var(--color-text);
8136
8197
  border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-lg);
@@ -8202,6 +8263,11 @@ var DT_CSS = `
8202
8263
  font-size: var(--text-sm); color: var(--color-text); width: 150px; }
8203
8264
  .twc-dt__search input:focus, .twc-dt__search input:focus-visible { outline: none; box-shadow: none; }
8204
8265
  .twc-dt__search input::placeholder { color: var(--color-text-subtle); }
8266
+ /* #327: trailing clear (X) for the built-in search inputs (quick-search + column-menu search). */
8267
+ .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; }
8268
+ .twc-dt__search-clear:hover { color: var(--color-text); background: var(--color-surface-sunken); }
8269
+ .twc-dt__search-clear:focus-visible { outline: none; box-shadow: var(--ring); }
8270
+ .twc-dt__search-clear svg { width: 14px; height: 14px; }
8205
8271
  .twc-dt__tlabel { display: inline; }
8206
8272
  /* Narrow grid: collapse the toolbar to icon-only buttons (labels survive as
8207
8273
  hover tooltips via data-tip) and let the search flex so nothing wraps. */
@@ -8548,13 +8614,11 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
8548
8614
  .twc-dt__panel-count { font-weight: 600; color: var(--color-text-muted); text-transform: none; letter-spacing: 0; margin-inline-start: 6px; font-variant-numeric: tabular-nums; }
8549
8615
  .twc-dt__link { border: none; background: transparent; color: var(--color-primary); font-family: inherit; font-size: var(--text-xs); font-weight: 600; cursor: pointer; padding: 2px 4px; border-radius: var(--radius-sm); }
8550
8616
  .twc-dt__link:hover { background: var(--color-primary-subtle); }
8551
- /* #303: AND/OR connective toggle (segmented pill) in the Filters panel head. */
8552
- .twc-dt__flogic { display: inline-flex; border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; }
8553
- .twc-dt__flogic-btn { border: none; background: transparent; color: var(--color-text-muted); font-family: inherit; font-size: 10px; font-weight: 700; letter-spacing: var(--tracking-wide); cursor: pointer; padding: 2px 8px; }
8554
- .twc-dt__flogic-btn + .twc-dt__flogic-btn { border-inline-start: var(--border-thin) solid var(--color-border); }
8555
- .twc-dt__flogic-btn:hover { background: var(--color-surface-sunken); }
8556
- .twc-dt__flogic-btn[data-active] { background: var(--color-primary); color: var(--color-primary-fg); }
8557
- .twc-dt__flogic-btn[data-active]:hover { background: var(--color-primary); }
8617
+ /* #330: MUI DataGrid-style per-row And/Or connector. Row 1 shows a static "Where"; the first connector
8618
+ row (row 2) hosts an editable And/Or Select bound to the single filterLogic; rows 3+ echo it as static
8619
+ text. Fixed-width leading cell so the Column/Operator/Value cells stay aligned across rows. */
8620
+ .twc-dt__f-logic { position: relative; flex: none; width: 68px; }
8621
+ .twc-dt__f-where { display: inline-flex; align-items: center; height: 38px; padding-inline: 6px; color: var(--color-text-muted); font-size: var(--text-sm); font-weight: var(--font-medium); }
8558
8622
 
8559
8623
  /* Filter panel \u2014 user-resizable (#292): panel size + per-field widths resolve through a two-tier CSS var
8560
8624
  cascade \u2014 user drag (-usr) > #289 measured auto-fit (-fit) > 118px fallback \u2014 entirely in clamp(), so
@@ -8564,7 +8628,7 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
8564
8628
  equal-specificity 'position: relative' defined later would clobber it, dropping the popover into normal
8565
8629
  flow below the table. The panel stays position: fixed, which already anchors the absolutely-positioned
8566
8630
  .twc-dt__pop-grip; per-field handles anchor to their own position: relative .twc-dt__f-* fields. */
8567
- width: var(--twc-dt-panel-w, 580px); max-width: calc(100vw - 32px);
8631
+ width: var(--twc-dt-panel-w, 620px); max-width: calc(100vw - 32px);
8568
8632
  height: var(--twc-dt-panel-h, auto); max-height: calc(100vh - 32px);
8569
8633
  display: flex; flex-direction: column;
8570
8634
  }
@@ -9176,6 +9240,9 @@ function Datatable({
9176
9240
  searchable = true,
9177
9241
  quickFilter,
9178
9242
  onQuickFilterChange,
9243
+ filterLogic: filterLogicProp,
9244
+ defaultFilterLogic = "and",
9245
+ onFilterLogicChange,
9179
9246
  toolbarActions,
9180
9247
  resizableFilters = true,
9181
9248
  resizablePopovers = true,
@@ -9269,7 +9336,14 @@ function Datatable({
9269
9336
  });
9270
9337
  const [sort, setSort] = React51__default.useState(null);
9271
9338
  const [filters, setFilters] = React51__default.useState([]);
9272
- const [filterLogic, setFilterLogic] = React51__default.useState("and");
9339
+ const filterLogicControlled = filterLogicProp !== void 0;
9340
+ const [filterLogicInternal, setFilterLogicInternal] = React51__default.useState(defaultFilterLogic === "or" ? "or" : "and");
9341
+ const filterLogic = filterLogicControlled ? filterLogicProp === "or" ? "or" : "and" : filterLogicInternal;
9342
+ const setFilterLogic = (next) => {
9343
+ const v = next === "or" ? "or" : "and";
9344
+ if (!filterLogicControlled) setFilterLogicInternal(v);
9345
+ onFilterLogicChange?.(v);
9346
+ };
9273
9347
  const [hidden, setHidden] = React51__default.useState(() => /* @__PURE__ */ new Set());
9274
9348
  const [showRowNum, setShowRowNum] = React51__default.useState(rowNumbers);
9275
9349
  const [wrapped, setWrapped] = React51__default.useState(() => new Set((columns || []).filter((c) => c.wrapText).map((c) => c.field)));
@@ -9365,7 +9439,7 @@ function Datatable({
9365
9439
  if (!s || typeof s !== "object") return;
9366
9440
  const known = new Set(cols.map((c) => c.field).filter(Boolean));
9367
9441
  if (Array.isArray(s.filters)) setFilters(s.filters.filter((f) => f && known.has(f.field)).map((f, i) => ({ id: Date.now() + Math.random() + i, field: f.field, op: f.op, value: f.value })));
9368
- if (s.filterLogic === "or" || s.filterLogic === "and") setFilterLogic(s.filterLogic);
9442
+ if ((s.filterLogic === "or" || s.filterLogic === "and") && !filterLogicControlled) setFilterLogicInternal(s.filterLogic);
9369
9443
  if ("sort" in s) setSort(s.sort && known.has(s.sort.field) ? { field: s.sort.field, dir: s.sort.dir === "desc" ? "desc" : "asc" } : null);
9370
9444
  if (typeof s.quickFilter === "string" && quickFilter === void 0) setInternalQuick(s.quickFilter);
9371
9445
  if (typeof s.page === "number" && !pageControlled) setInternalPage(Math.max(0, Math.floor(s.page)));
@@ -9570,6 +9644,8 @@ function Datatable({
9570
9644
  const colMenuRef = React51__default.useRef(null);
9571
9645
  const rowMenuRef = React51__default.useRef(null);
9572
9646
  const exportMenuRef = React51__default.useRef(null);
9647
+ const quickSearchRef = React51__default.useRef(null);
9648
+ const colSearchRef = React51__default.useRef(null);
9573
9649
  function focusFirstItem(container) {
9574
9650
  if (!container) return;
9575
9651
  const first = container.querySelector('[role="menuitem"]:not([disabled])');
@@ -10756,7 +10832,7 @@ function Datatable({
10756
10832
  const pad2 = trigger ? (parseFloat(cs.paddingLeft) || 0) + (parseFloat(cs.paddingRight) || 0) : 24;
10757
10833
  const chrome = pad2 + 8 + 16 + 4;
10758
10834
  const widest = (labels) => labels.reduce((m, l) => Math.max(m, ctx.measureText(String(l)).width), 0);
10759
- const colCap = Math.max(118, Math.min(210, el.clientWidth - 118 - 30 - 140 - 3 * 6 - 2 * 4));
10835
+ const colCap = Math.max(118, Math.min(210, el.clientWidth - DT_FILTER_LOGIC_W - 118 - 30 - 30 - 140 - 5 * 6 - 2 * 4));
10760
10836
  const colW = Math.min(colCap, Math.max(118, Math.ceil(widest(filterableCols.map((c) => c.headerName ?? c.field)) + chrome)));
10761
10837
  const opW = Math.min(170, Math.max(118, Math.ceil(widest(ALL_OP_LABELS) + chrome)));
10762
10838
  if (Number.isFinite(colW)) el.style.setProperty("--twc-dt-fcol-fit", `${colW}px`);
@@ -10788,7 +10864,7 @@ function Datatable({
10788
10864
  if (field === "op") return filterFieldMaxWidth || 260;
10789
10865
  const p = filterPanelRef.current;
10790
10866
  if (!p) return 640;
10791
- return clampNum(p.clientWidth - fLiveW("col") - fLiveW("op") - 30 - 24, F_MIN.val, 640);
10867
+ return clampNum(p.clientWidth - DT_FILTER_LOGIC_W - fLiveW("col") - fLiveW("op") - 30 - 30 - 5 * 6 - 2 * 4, F_MIN.val, 640);
10792
10868
  };
10793
10869
  const isRtl = () => {
10794
10870
  const p = filterPanelRef.current;
@@ -11485,7 +11561,7 @@ function Datatable({
11485
11561
  } else {
11486
11562
  setPanel("filters");
11487
11563
  setColMenu(null);
11488
- openPanel(e.currentTarget, "left", 580);
11564
+ openPanel(e.currentTarget, "left", DT_FILTER_PANEL_W);
11489
11565
  }
11490
11566
  },
11491
11567
  children: [
@@ -11584,7 +11660,11 @@ function Datatable({
11584
11660
  ] }) : null,
11585
11661
  searchable ? /* @__PURE__ */ jsxs("div", { className: "twc-dt__search", children: [
11586
11662
  /* @__PURE__ */ jsx(Svg, { d: I.search }),
11587
- /* @__PURE__ */ jsx("input", { placeholder: "Search\u2026", "aria-label": "Search rows", value: quick, onChange: (e) => commitQuick(e.target.value) })
11663
+ /* @__PURE__ */ jsx("input", { ref: quickSearchRef, placeholder: "Search\u2026", "aria-label": "Search rows", value: quick, onChange: (e) => commitQuick(e.target.value) }),
11664
+ quick ? /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__search-clear", "aria-label": "Clear search", onMouseDown: (e) => e.preventDefault(), onClick: () => {
11665
+ commitQuick("");
11666
+ quickSearchRef.current?.focus();
11667
+ }, children: /* @__PURE__ */ jsx(Svg, { d: I.x }) }) : null
11588
11668
  ] }) : null
11589
11669
  ] }),
11590
11670
  !pivotActive && activeGroupBy.length ? /* @__PURE__ */ jsxs("div", { className: "twc-dt__groupbar", children: [
@@ -12049,7 +12129,7 @@ function Datatable({
12049
12129
  closeMenu();
12050
12130
  restoreTriggerFocus();
12051
12131
  setPanel("filters");
12052
- openPanel(document.querySelector(".twc-dt__toolbar .twc-dt__tbtn:nth-child(2)"), "left", 580);
12132
+ openPanel(document.querySelector(".twc-dt__toolbar .twc-dt__tbtn:nth-child(2)"), "left", DT_FILTER_PANEL_W);
12053
12133
  }, children: [
12054
12134
  /* @__PURE__ */ jsx(Svg, { d: I.filter }),
12055
12135
  " Filter"
@@ -12177,7 +12257,11 @@ function Datatable({
12177
12257
  ] }),
12178
12258
  /* @__PURE__ */ jsxs("div", { className: "twc-dt__col-search", children: [
12179
12259
  /* @__PURE__ */ jsx(Svg, { d: I.search }),
12180
- /* @__PURE__ */ jsx("input", { autoFocus: true, placeholder: "Find column\u2026", "aria-label": "Find column", value: colQuery, onChange: (e) => setColQuery(e.target.value) })
12260
+ /* @__PURE__ */ jsx("input", { ref: colSearchRef, autoFocus: true, placeholder: "Find column\u2026", "aria-label": "Find column", value: colQuery, onChange: (e) => setColQuery(e.target.value) }),
12261
+ colQuery ? /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__search-clear", "aria-label": "Clear column search", onMouseDown: (e) => e.preventDefault(), onClick: () => {
12262
+ setColQuery("");
12263
+ colSearchRef.current?.focus();
12264
+ }, children: /* @__PURE__ */ jsx(Svg, { d: I.x }) }) : null
12181
12265
  ] }),
12182
12266
  /* @__PURE__ */ jsxs("div", { className: "twc-dt__col-list", children: [
12183
12267
  rowNumMatch ? /* @__PURE__ */ jsxs("div", { className: "twc-dt__col-row", onClick: () => setShowRowNum((v) => !v), children: [
@@ -12479,10 +12563,6 @@ function Datatable({
12479
12563
  /* @__PURE__ */ jsxs("div", { className: "twc-dt__panel-head", children: [
12480
12564
  /* @__PURE__ */ jsx("span", { className: "twc-dt__panel-title", children: "Filters" }),
12481
12565
  /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 6 }, children: [
12482
- filters.length > 1 ? /* @__PURE__ */ jsxs("span", { className: "twc-dt__flogic", role: "group", "aria-label": "Combine filters with", children: [
12483
- /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__flogic-btn", "data-active": filterLogic === "and" || void 0, "aria-pressed": filterLogic === "and", onClick: () => setFilterLogic("and"), children: "AND" }),
12484
- /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__flogic-btn", "data-active": filterLogic === "or" || void 0, "aria-pressed": filterLogic === "or", onClick: () => setFilterLogic("or"), children: "OR" })
12485
- ] }) : null,
12486
12566
  resizableFilters && hasFilterSizeOverride ? /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__link", onClick: resetFilterSizes, children: "Reset sizes" }) : null,
12487
12567
  filters.length ? /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__link", onClick: () => setFilters([]), children: "Clear all" }) : null
12488
12568
  ] })
@@ -12492,6 +12572,17 @@ function Datatable({
12492
12572
  const ops = opsFor(filterTypeOf(col));
12493
12573
  const op = ops.find((o) => o.value === f.op) || ops[0];
12494
12574
  return /* @__PURE__ */ jsxs("div", { className: "twc-dt__frow", children: [
12575
+ /* @__PURE__ */ jsx("div", { className: "twc-dt__f-logic", children: i === 0 ? /* @__PURE__ */ jsx("span", { className: "twc-dt__f-where", children: "Where" }) : i === 1 ? /* @__PURE__ */ jsx(
12576
+ Select,
12577
+ {
12578
+ size: "sm",
12579
+ portal: true,
12580
+ "aria-label": "Combine with the other filters",
12581
+ value: filterLogic,
12582
+ options: [{ value: "and", label: "And" }, { value: "or", label: "Or" }],
12583
+ onChange: (v) => setFilterLogic(v)
12584
+ }
12585
+ ) : /* @__PURE__ */ jsx("span", { className: "twc-dt__f-where", children: filterLogic === "or" ? "Or" : "And" }) }),
12495
12586
  /* @__PURE__ */ jsxs("div", { className: "twc-dt__f-col", children: [
12496
12587
  /* @__PURE__ */ jsx(
12497
12588
  Select,
@@ -12547,6 +12638,7 @@ function Datatable({
12547
12638
  Input,
12548
12639
  {
12549
12640
  size: "sm",
12641
+ clearable: true,
12550
12642
  type: filterTypeOf(col) === "number" ? "number" : "text",
12551
12643
  placeholder: "Value",
12552
12644
  value: f.value,
@@ -12570,7 +12662,7 @@ function Datatable({
12570
12662
  role: "slider",
12571
12663
  tabIndex: 0,
12572
12664
  "aria-label": "Resize filters panel (Enter to reset)",
12573
- "aria-valuetext": `Width ${Math.round(fPanelSize?.w ?? 580)} pixels, height ${fPanelSize?.h != null ? Math.round(fPanelSize.h) + " pixels" : "auto"}`,
12665
+ "aria-valuetext": `Width ${Math.round(fPanelSize?.w ?? DT_FILTER_PANEL_W)} pixels, height ${fPanelSize?.h != null ? Math.round(fPanelSize.h) + " pixels" : "auto"}`,
12574
12666
  onPointerDown: startPanelResize,
12575
12667
  onKeyDown: onPanelGripKey,
12576
12668
  onDoubleClick: (e) => {
@@ -17635,6 +17727,12 @@ var TEXTAREA_CSS = `
17635
17727
  .twc-textarea__el:disabled { background: var(--color-surface-sunken); opacity: 0.7; cursor: not-allowed; }
17636
17728
  .twc-textarea__el[data-readonly] { background: var(--color-surface-sunken); }
17637
17729
  .twc-textarea__el[data-autosize="true"] { resize: none; overflow: hidden; }
17730
+ /* #328: clearable (X). Wrap positions the button top-inline-end so it dodges the bottom-right resize handle. */
17731
+ .twc-textarea__wrap { position: relative; display: block; }
17732
+ .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); }
17733
+ .twc-textarea__clear:hover { background: var(--color-surface-sunken); color: var(--color-text); }
17734
+ .twc-textarea__clear:focus-visible { outline: none; box-shadow: var(--ring); }
17735
+ .twc-textarea__clear svg { width: 15px; height: 15px; }
17638
17736
  .twc-field__footer { display: flex; align-items: center; gap: var(--space-2); }
17639
17737
  .twc-field__count { margin-inline-start: auto; font-size: var(--text-xs); color: var(--color-text-muted); font-variant-numeric: tabular-nums; flex: none; }
17640
17738
  .twc-field__count[data-danger="true"] { color: var(--color-danger-subtle-fg); }
@@ -17651,6 +17749,7 @@ var Textarea = React51__default.forwardRef(function Textarea2({
17651
17749
  minRows,
17652
17750
  maxRows,
17653
17751
  showCount = false,
17752
+ clearable = false,
17654
17753
  disabled = false,
17655
17754
  id,
17656
17755
  className = "",
@@ -17697,8 +17796,18 @@ var Textarea = React51__default.forwardRef(function Textarea2({
17697
17796
  };
17698
17797
  const handleChange = (e) => {
17699
17798
  rest.onChange?.(e);
17700
- if (showCount && !countControlled) setLen(e.target.value.length);
17799
+ if ((showCount || clearable) && !countControlled) setLen(e.target.value.length);
17701
17800
  };
17801
+ const clearField = () => {
17802
+ const el = innerRef.current;
17803
+ if (!el) return;
17804
+ const setter = typeof HTMLTextAreaElement !== "undefined" && Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
17805
+ if (setter) setter.call(el, "");
17806
+ else el.value = "";
17807
+ el.dispatchEvent(new Event("input", { bubbles: true }));
17808
+ el.focus();
17809
+ };
17810
+ const showClear = clearable && count > 0 && !disabled && !rest.readOnly;
17702
17811
  const ariaInvalid = invalid ? true : rest["aria-invalid"] ?? void 0;
17703
17812
  const describedBy = [error || hint ? descId : null, showCounter ? countId : null, rest["aria-describedby"]].filter(Boolean).join(" ") || void 0;
17704
17813
  return /* @__PURE__ */ jsxs("div", { className: `twc-field ${className}`, children: [
@@ -17707,27 +17816,33 @@ var Textarea = React51__default.forwardRef(function Textarea2({
17707
17816
  label,
17708
17817
  required ? /* @__PURE__ */ jsx("span", { className: "twc-field__req", children: "*" }) : null
17709
17818
  ] }) : null,
17710
- /* @__PURE__ */ jsx(
17711
- "textarea",
17712
- {
17713
- ref: setRef,
17714
- id: fieldId,
17715
- className: "twc-textarea__el",
17716
- "data-size": size,
17717
- "data-tone": tone,
17718
- "data-autosize": autosize || void 0,
17719
- "data-readonly": rest.readOnly || void 0,
17720
- rows: autosize ? minRows ?? rows : rows,
17721
- disabled,
17722
- required: required || void 0,
17723
- "aria-required": required || void 0,
17724
- ...rest,
17725
- onInput: handleInput,
17726
- onChange: handleChange,
17727
- "aria-invalid": ariaInvalid,
17728
- "aria-describedby": describedBy
17729
- }
17730
- ),
17819
+ /* @__PURE__ */ jsxs("div", { className: "twc-textarea__wrap", children: [
17820
+ /* @__PURE__ */ jsx(
17821
+ "textarea",
17822
+ {
17823
+ ref: setRef,
17824
+ id: fieldId,
17825
+ className: "twc-textarea__el",
17826
+ "data-size": size,
17827
+ "data-tone": tone,
17828
+ "data-autosize": autosize || void 0,
17829
+ "data-readonly": rest.readOnly || void 0,
17830
+ rows: autosize ? minRows ?? rows : rows,
17831
+ disabled,
17832
+ required: required || void 0,
17833
+ "aria-required": required || void 0,
17834
+ ...rest,
17835
+ onInput: handleInput,
17836
+ onChange: handleChange,
17837
+ "aria-invalid": ariaInvalid,
17838
+ "aria-describedby": describedBy
17839
+ }
17840
+ ),
17841
+ 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: [
17842
+ /* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
17843
+ /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
17844
+ ] }) }) : null
17845
+ ] }),
17731
17846
  error || hint || showCounter ? /* @__PURE__ */ jsxs("div", { className: "twc-field__footer", children: [
17732
17847
  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,
17733
17848
  showCounter ? /* @__PURE__ */ jsxs("span", { id: countId, className: "twc-field__count", "data-danger": near || void 0, "aria-live": "polite", children: [