twico-ui 1.15.1 → 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,13 @@
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
+
5
12
  ## [1.15.1](https://github.com/EncryptionL/twico-ui/compare/v1.15.0...v1.15.1) (2026-07-14)
6
13
 
7
14
 
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); }
@@ -9004,6 +9009,8 @@ function Datatable({
9004
9009
  onRowUpdate,
9005
9010
  onRowsChange,
9006
9011
  onBatchUpdate,
9012
+ showBatchEdit = true,
9013
+ batchEditFields = null,
9007
9014
  showPageJumper = true,
9008
9015
  selectionMode = "none",
9009
9016
  onRowClick,
@@ -10043,9 +10050,14 @@ function Datatable({
10043
10050
  cancelEdit();
10044
10051
  }
10045
10052
  }
10053
+ const batchEditKey = batchEditFields ? batchEditFields.join("\0") : "";
10046
10054
  const batchEditableCols = React51__namespace.default.useMemo(
10047
- () => cols.filter((c) => c.type !== "actions" && (c.editable ?? (editMode && c.editable !== false))),
10048
- [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
10049
10061
  );
10050
10062
  const [batchEdit, setBatchEdit] = React51__namespace.default.useState(null);
10051
10063
  const [batchEditPos, openBatchEdit, closeBatchEdit] = useFloating();
@@ -10480,7 +10492,7 @@ function Datatable({
10480
10492
  " selected"
10481
10493
  ] }),
10482
10494
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__batch-actions", children: [
10483
- 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: [
10484
10496
  /* @__PURE__ */ jsxRuntime.jsx(Svg, { d: I.pencil }),
10485
10497
  "Edit"
10486
10498
  ] }) : null,
@@ -10969,58 +10981,80 @@ function Datatable({
10969
10981
  ]
10970
10982
  }
10971
10983
  ) : null,
10972
- batchEdit && batchEditPos ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__pop twc-dt__cfg", style: { top: batchEditPos.top, left: batchEditPos.left, width: 320 }, children: [
10973
- /* @__PURE__ */ jsxRuntime.jsx(Caret, { pos: batchEditPos }),
10974
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__cfg-head", children: [
10975
- "Edit ",
10976
- selected.size,
10977
- " selected ",
10978
- selected.size === 1 ? "row" : "rows"
10979
- ] }),
10980
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__cfg-list", children: batchEditableCols.map((c) => {
10981
- const on = !!batchEdit.fields[c.field];
10982
- const opts = c.valueOptions ? c.valueOptions.map((o) => typeof o === "string" ? { value: o, label: o } : o) : null;
10983
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__be-row", "data-on": on || void 0, children: [
10984
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "twc-dt__be-check", children: [
10985
- /* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: on, onChange: (e) => setBatchEdit((b) => ({ ...b, fields: { ...b.fields, [c.field]: e.target.checked } })) }),
10986
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: c.headerName })
10987
- ] }),
10988
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dt__be-ctl", children: opts ? /* @__PURE__ */ jsxRuntime.jsx(
10989
- Select,
10990
- {
10991
- size: "sm",
10992
- portal: true,
10993
- searchable: true,
10994
- placeholder: "New value\u2026",
10995
- value: batchEdit.values[c.field] ?? "",
10996
- options: opts,
10997
- disabled: !on,
10998
- onChange: (v) => setBatchEdit((b) => ({ ...b, fields: { ...b.fields, [c.field]: true }, values: { ...b.values, [c.field]: v } }))
10999
- }
11000
- ) : /* @__PURE__ */ jsxRuntime.jsx(
11001
- Input,
11002
- {
11003
- size: "sm",
11004
- type: c.type === "number" || c.type === "currency" ? "number" : "text",
11005
- placeholder: "New value\u2026",
11006
- value: batchEdit.values[c.field] ?? "",
11007
- disabled: !on,
11008
- onChange: (e) => setBatchEdit((b) => ({ ...b, values: { ...b.values, [c.field]: e.target.value } }))
11009
- }
11010
- ) })
11011
- ] }, c.field);
11012
- }) }),
11013
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dt__cfg-foot", children: [
11014
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "twc-dt__cfg-btn", onClick: () => {
11015
- setBatchEdit(null);
11016
- closeBatchEdit();
11017
- }, children: "Cancel" }),
11018
- /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "twc-dt__cfg-btn", "data-primary": "true", onClick: applyBatchEdit, children: [
11019
- "Apply to ",
11020
- 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
+ ] })
11021
11055
  ] })
11022
- ] })
11023
- ] }) : null,
11056
+ ] });
11057
+ })() : null,
11024
11058
  colMenu && menuPos ? /* @__PURE__ */ jsxRuntime.jsxs(
11025
11059
  "div",
11026
11060
  {