twico-ui 1.15.0 → 1.16.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.16.0](https://github.com/EncryptionL/twico-ui/compare/v1.15.1...v1.16.0) (2026-07-15)
6
+
7
+
8
+ ### Features
9
+
10
+ * **datatable:** batch editor picks columns (searchable) + showBatchEdit/batchEditFields ([#244](https://github.com/EncryptionL/twico-ui/issues/244)) ([82b8806](https://github.com/EncryptionL/twico-ui/commit/82b8806f32f08153f15653de68bdb82ea2dc7ff3)), closes [#236](https://github.com/EncryptionL/twico-ui/issues/236)
11
+
12
+ ## [1.15.1](https://github.com/EncryptionL/twico-ui/compare/v1.15.0...v1.15.1) (2026-07-14)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * **datatable:** keep pinned cells opaque in diff mode ([#242](https://github.com/EncryptionL/twico-ui/issues/242)) ([072b15e](https://github.com/EncryptionL/twico-ui/commit/072b15ea73443ad535b3f149d8b620618702b389)), closes [#239](https://github.com/EncryptionL/twico-ui/issues/239)
18
+
5
19
  # [1.15.0](https://github.com/EncryptionL/twico-ui/compare/v1.14.1...v1.15.0) (2026-07-14)
6
20
 
7
21
 
package/dist/index.cjs CHANGED
@@ -8328,12 +8328,17 @@ var DT_CSS = `
8328
8328
  .twc-dt__cfg-btn:hover { background: var(--color-surface-sunken); }
8329
8329
  .twc-dt__cfg-btn[data-primary="true"] { background: var(--color-primary); border-color: var(--color-primary); color: var(--color-primary-fg); }
8330
8330
  .twc-dt__cfg-btn[data-primary="true"]:hover { background: var(--color-primary-hover); border-color: var(--color-primary-hover); }
8331
- .twc-dt__be-row { display: flex; align-items: center; gap: 10px; padding: 5px 6px; opacity: 0.6; transition: opacity var(--duration-fast); }
8332
- .twc-dt__be-row[data-on] { opacity: 1; }
8333
- .twc-dt__be-check { display: inline-flex; align-items: center; gap: 7px; width: 120px; flex: none; font-size: var(--text-sm); color: var(--color-text); cursor: pointer; }
8334
- .twc-dt__be-check input { width: 16px; height: 16px; accent-color: var(--color-primary); cursor: pointer; flex: none; }
8335
- .twc-dt__be-check span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
8331
+ /* #244: batch editor = pick columns (searchable), then set a value for each. */
8332
+ .twc-dt__be-add { padding: 6px 6px 8px; border-bottom: var(--border-thin) solid var(--color-divider); }
8333
+ .twc-dt__be-row { display: flex; align-items: center; gap: 8px; padding: 5px 6px; }
8334
+ .twc-dt__be-name { width: 108px; flex: none; font-size: var(--text-sm); color: var(--color-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
8336
8335
  .twc-dt__be-ctl { flex: 1; min-width: 0; }
8336
+ .twc-dt__be-x { flex: none; display: inline-grid; place-items: center; width: 24px; height: 24px; padding: 0; border: none;
8337
+ background: transparent; color: var(--color-text-subtle); cursor: pointer; border-radius: var(--radius-sm);
8338
+ transition: background-color var(--duration-fast), color var(--duration-fast); }
8339
+ .twc-dt__be-x:hover { background: var(--color-surface-sunken); color: var(--color-text); }
8340
+ .twc-dt__be-x svg { width: 14px; height: 14px; }
8341
+ .twc-dt__be-empty { padding: 14px 8px; font-size: var(--text-xs); color: var(--color-text-muted); text-align: center; }
8337
8342
 
8338
8343
  /* Pinned rows */
8339
8344
  .twc-dt__row[data-pinned-row] > .twc-dt__td { background: var(--color-primary-subtle); }
@@ -8348,6 +8353,12 @@ var DT_CSS = `
8348
8353
  .twc-dt__row[data-op="added"] > .twc-dt__td { background: color-mix(in srgb, var(--color-success) 7%, transparent); }
8349
8354
  .twc-dt__row[data-op="removed"] > .twc-dt__td { background: color-mix(in srgb, var(--color-danger) 7%, transparent); }
8350
8355
  .twc-dt__row[data-op="modified"] > .twc-dt__td { background: color-mix(in srgb, var(--color-warning) 6%, transparent); }
8356
+ /* #242: pinned cells in a diff row must stay OPAQUE (composite the tint over the surface, not over
8357
+ transparent) or the cells scrolling under a sticky column bleed through. Specificity (0,4,0) wins
8358
+ over the (0,3,0) tint above and matches the non-pinned cells' effective color (tint over surface). */
8359
+ .twc-dt__row[data-op="added"] > .twc-dt__td[data-pin] { background: color-mix(in srgb, var(--color-success) 7%, var(--color-surface)); }
8360
+ .twc-dt__row[data-op="removed"] > .twc-dt__td[data-pin] { background: color-mix(in srgb, var(--color-danger) 7%, var(--color-surface)); }
8361
+ .twc-dt__row[data-op="modified"] > .twc-dt__td[data-pin] { background: color-mix(in srgb, var(--color-warning) 6%, var(--color-surface)); }
8351
8362
  .twc-dt__diff-change { display: inline-flex; align-items: center; gap: 4px; min-width: 0; max-width: 100%; }
8352
8363
  .twc-dt__diff-old { text-decoration: line-through; color: var(--color-text-subtle); }
8353
8364
  .twc-dt__diff-new { color: var(--color-success-subtle-fg); font-weight: var(--font-medium); }
@@ -8998,6 +9009,8 @@ function Datatable({
8998
9009
  onRowUpdate,
8999
9010
  onRowsChange,
9000
9011
  onBatchUpdate,
9012
+ showBatchEdit = true,
9013
+ batchEditFields = null,
9001
9014
  showPageJumper = true,
9002
9015
  selectionMode = "none",
9003
9016
  onRowClick,
@@ -10037,9 +10050,14 @@ function Datatable({
10037
10050
  cancelEdit();
10038
10051
  }
10039
10052
  }
10053
+ const batchEditKey = batchEditFields ? batchEditFields.join("\0") : "";
10040
10054
  const batchEditableCols = React51__namespace.default.useMemo(
10041
- () => cols.filter((c) => c.type !== "actions" && (c.editable ?? (editMode && c.editable !== false))),
10042
- [cols, editMode]
10055
+ () => {
10056
+ const base = cols.filter((c) => c.type !== "actions" && (c.editable ?? (editMode && c.editable !== false)));
10057
+ return batchEditFields ? base.filter((c) => batchEditFields.includes(c.field)) : base;
10058
+ },
10059
+ [cols, editMode, batchEditKey]
10060
+ // eslint-disable-line react-hooks/exhaustive-deps
10043
10061
  );
10044
10062
  const [batchEdit, setBatchEdit] = React51__namespace.default.useState(null);
10045
10063
  const [batchEditPos, openBatchEdit, closeBatchEdit] = useFloating();
@@ -10474,7 +10492,7 @@ function Datatable({
10474
10492
  " selected"
10475
10493
  ] }),
10476
10494
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__batch-actions", children: [
10477
- batchEditableCols.length ? /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "twc-dt__batch-btn", onClick: (e) => openBatchEditor(e.currentTarget), children: [
10495
+ showBatchEdit && batchEditableCols.length ? /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "twc-dt__batch-btn", onClick: (e) => openBatchEditor(e.currentTarget), children: [
10478
10496
  /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.pencil }),
10479
10497
  "Edit"
10480
10498
  ] }) : null,
@@ -10963,58 +10981,80 @@ function Datatable({
10963
10981
  ]
10964
10982
  }
10965
10983
  ) : null,
10966
- batchEdit && batchEditPos ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__pop twc-dt__cfg", style: { top: batchEditPos.top, left: batchEditPos.left, width: 320 }, children: [
10967
- /* @__PURE__ */ jsxRuntime.jsx(Caret, { pos: batchEditPos }),
10968
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__cfg-head", children: [
10969
- "Edit ",
10970
- selected.size,
10971
- " selected ",
10972
- selected.size === 1 ? "row" : "rows"
10973
- ] }),
10974
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__cfg-list", children: batchEditableCols.map((c) => {
10975
- const on = !!batchEdit.fields[c.field];
10976
- const opts = c.valueOptions ? c.valueOptions.map((o) => typeof o === "string" ? { value: o, label: o } : o) : null;
10977
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__be-row", "data-on": on || void 0, children: [
10978
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "twc-dt__be-check", children: [
10979
- /* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: on, onChange: (e) => setBatchEdit((b) => ({ ...b, fields: { ...b.fields, [c.field]: e.target.checked } })) }),
10980
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: c.headerName })
10981
- ] }),
10982
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__be-ctl", children: opts ? /* @__PURE__ */ jsxRuntime.jsx(
10983
- Select,
10984
- {
10985
- size: "sm",
10986
- portal: true,
10987
- searchable: true,
10988
- placeholder: "New value\u2026",
10989
- value: batchEdit.values[c.field] ?? "",
10990
- options: opts,
10991
- disabled: !on,
10992
- onChange: (v) => setBatchEdit((b) => ({ ...b, fields: { ...b.fields, [c.field]: true }, values: { ...b.values, [c.field]: v } }))
10993
- }
10994
- ) : /* @__PURE__ */ jsxRuntime.jsx(
10995
- Input,
10996
- {
10997
- size: "sm",
10998
- type: c.type === "number" || c.type === "currency" ? "number" : "text",
10999
- placeholder: "New value\u2026",
11000
- value: batchEdit.values[c.field] ?? "",
11001
- disabled: !on,
11002
- onChange: (e) => setBatchEdit((b) => ({ ...b, values: { ...b.values, [c.field]: e.target.value } }))
11003
- }
11004
- ) })
11005
- ] }, c.field);
11006
- }) }),
11007
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__cfg-foot", children: [
11008
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__cfg-btn", onClick: () => {
11009
- setBatchEdit(null);
11010
- closeBatchEdit();
11011
- }, children: "Cancel" }),
11012
- /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "twc-dt__cfg-btn", "data-primary": "true", onClick: applyBatchEdit, children: [
11013
- "Apply to ",
11014
- selected.size
10984
+ batchEdit && batchEditPos ? (() => {
10985
+ const pickedCols = Object.keys(batchEdit.fields).filter((f) => batchEdit.fields[f]).map((f) => batchEditableCols.find((c) => c.field === f)).filter(Boolean);
10986
+ const unpicked = batchEditableCols.filter((c) => !batchEdit.fields[c.field]);
10987
+ const addField = (f) => {
10988
+ if (f) setBatchEdit((b) => ({ ...b, fields: { ...b.fields, [f]: true }, values: { ...b.values, [f]: b.values[f] ?? "" } }));
10989
+ };
10990
+ const removeField = (f) => setBatchEdit((b) => {
10991
+ const fields = { ...b.fields };
10992
+ delete fields[f];
10993
+ const values = { ...b.values };
10994
+ delete values[f];
10995
+ return { ...b, fields, values };
10996
+ });
10997
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__pop twc-dt__cfg", style: { top: batchEditPos.top, left: batchEditPos.left, width: 320 }, children: [
10998
+ /* @__PURE__ */ jsxRuntime.jsx(Caret, { pos: batchEditPos }),
10999
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__cfg-head", children: [
11000
+ "Edit ",
11001
+ selected.size,
11002
+ " selected ",
11003
+ selected.size === 1 ? "row" : "rows"
11004
+ ] }),
11005
+ unpicked.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__be-add", children: /* @__PURE__ */ jsxRuntime.jsx(
11006
+ Select,
11007
+ {
11008
+ size: "sm",
11009
+ portal: true,
11010
+ searchable: true,
11011
+ placeholder: "Add a column\u2026",
11012
+ value: "",
11013
+ "aria-label": "Add a column to edit",
11014
+ options: unpicked.map((c) => ({ value: c.field, label: String(c.headerName ?? c.field) })),
11015
+ onChange: addField
11016
+ }
11017
+ ) }) : null,
11018
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__cfg-list", children: pickedCols.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__be-empty", children: "Pick a column to set its value on all selected rows." }) : pickedCols.map((c) => {
11019
+ const opts = c.valueOptions ? c.valueOptions.map((o) => typeof o === "string" ? { value: o, label: o } : o) : null;
11020
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__be-row", children: [
11021
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-dt__be-name", title: String(c.headerName ?? c.field), children: c.headerName }),
11022
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__be-ctl", children: opts ? /* @__PURE__ */ jsxRuntime.jsx(
11023
+ Select,
11024
+ {
11025
+ size: "sm",
11026
+ portal: true,
11027
+ searchable: true,
11028
+ placeholder: "New value\u2026",
11029
+ value: batchEdit.values[c.field] ?? "",
11030
+ options: opts,
11031
+ onChange: (v) => setBatchEdit((b) => ({ ...b, values: { ...b.values, [c.field]: v } }))
11032
+ }
11033
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
11034
+ Input,
11035
+ {
11036
+ size: "sm",
11037
+ type: c.type === "number" || c.type === "currency" ? "number" : "text",
11038
+ placeholder: "New value\u2026",
11039
+ value: batchEdit.values[c.field] ?? "",
11040
+ onChange: (e) => setBatchEdit((b) => ({ ...b, values: { ...b.values, [c.field]: e.target.value } }))
11041
+ }
11042
+ ) }),
11043
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__be-x", "aria-label": `Remove ${String(c.headerName ?? c.field)}`, onClick: () => removeField(c.field), children: /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.x }) })
11044
+ ] }, c.field);
11045
+ }) }),
11046
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__cfg-foot", children: [
11047
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__cfg-btn", onClick: () => {
11048
+ setBatchEdit(null);
11049
+ closeBatchEdit();
11050
+ }, children: "Cancel" }),
11051
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "twc-dt__cfg-btn", "data-primary": "true", disabled: pickedCols.length === 0, onClick: applyBatchEdit, children: [
11052
+ "Apply to ",
11053
+ selected.size
11054
+ ] })
11015
11055
  ] })
11016
- ] })
11017
- ] }) : null,
11056
+ ] });
11057
+ })() : null,
11018
11058
  colMenu && menuPos ? /* @__PURE__ */ jsxRuntime.jsxs(
11019
11059
  "div",
11020
11060
  {