twico-ui 1.3.1 → 1.4.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.d.cts CHANGED
@@ -541,9 +541,9 @@ declare function CurrencyField(props: CurrencyFieldProps): React.JSX.Element;
541
541
  * Advanced data table (Material UI Data Grid Premium style): sortable, filterable
542
542
  * (per-column operators + quick search), hideable (with a searchable column list),
543
543
  * and pinnable (left/right, sticky) columns, drag-to-reorder and drag-to-resize
544
- * columns, a column-management + filter toolbar, density control, CSV export,
545
- * an optional aggregation/summary footer, optional checkbox selection with a
546
- * batch-action toolbar, an actions column (per-row icon buttons + overflow menu),
544
+ * columns, a column-management + filter toolbar (with opt-in density / aggregation /
545
+ * pivot / CSV-export tools), an optional aggregation/summary footer, optional
546
+ * checkbox selection with a batch-action toolbar, an actions column (per-row icon buttons + overflow menu),
547
547
  * page-number pagination with a configurable rows-per-page selector, a skeleton
548
548
  * loading state, and an optional server-side mode (sort/filter/paginate on the
549
549
  * backend — only the current page is loaded). Filter controls and the pager are
@@ -579,7 +579,7 @@ interface DatatableProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "row
579
579
  * full row objects server-side rather than relying on `selectedRows`.
580
580
  */
581
581
  batchActions?: DatatableBatchAction[];
582
- /** Row height preset. The toolbar density button cycles it locally; changing this prop re-applies it. @default "standard" */
582
+ /** Row height preset. With `showDensity`, the toolbar density button cycles it locally; changing this prop re-applies it. @default "comfortable" */
583
583
  density?: "compact" | "standard" | "comfortable";
584
584
  /** Initial rows per page. 0 disables pagination. @default 10 */
585
585
  pageSize?: number;
@@ -598,8 +598,13 @@ interface DatatableProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "row
598
598
  rowCount?: number;
599
599
  /** Server-mode callback fired (debounced) whenever the query changes. */
600
600
  onServerChange?: (state: DatatableQuery) => void;
601
- /** Show the Export toolbar button (split button: click = CSV, chevron = format menu). @default true */
601
+ /** Show the Export toolbar button (split button: click = CSV, chevron = format menu). @default false */
602
602
  showExport?: boolean;
603
+ /** Show the row-density toolbar button (cycles compact / standard / comfortable). The `density` prop
604
+ * still sets the row height even when this button is hidden. @default false */
605
+ showDensity?: boolean;
606
+ /** Show the Pivot toolbar button. Also shown automatically when `pivot` or `pivotMode` is set. @default false */
607
+ showPivot?: boolean;
603
608
  /** Filename (without extension) for exports (CSV / Excel). @default "export" */
604
609
  exportFilename?: string;
605
610
  /** Server-mode precomputed aggregation. Per field, either a scalar/node (used as-is) or a per-function map
@@ -637,7 +642,9 @@ interface DatatableProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "row
637
642
  onCellClick?: (value: any, row: any, field: string) => void;
638
643
  /** Fired when the active cell changes: ({ key, field } | null). */
639
644
  onActiveCellChange?: (cell: { key: string | number; field: string } | null) => void;
640
- /** “Show totals row” state (changing the prop re-applies it; the user can also toggle it in the toolbar **Aggregation** panel). Aggregation is configured at runtime from that panel (pick columns + function); a column's `aggregation` prop just seeds the initial choice. @default false */
645
+ /** Show the **Aggregation** toolbar button and start with the totals row on. From that panel the user
646
+ * toggles totals and picks columns + functions (a column's `aggregation` prop seeds the initial choice);
647
+ * changing the prop re-applies it. When false (the default) the Aggregation button is hidden. @default false */
641
648
  showAggregation?: boolean;
642
649
  /** Accessible label for the grid (role="grid"). A standard `aria-label` prop takes precedence. @default "Data table" */
643
650
  ariaLabel?: string;
package/dist/index.d.ts CHANGED
@@ -541,9 +541,9 @@ declare function CurrencyField(props: CurrencyFieldProps): React.JSX.Element;
541
541
  * Advanced data table (Material UI Data Grid Premium style): sortable, filterable
542
542
  * (per-column operators + quick search), hideable (with a searchable column list),
543
543
  * and pinnable (left/right, sticky) columns, drag-to-reorder and drag-to-resize
544
- * columns, a column-management + filter toolbar, density control, CSV export,
545
- * an optional aggregation/summary footer, optional checkbox selection with a
546
- * batch-action toolbar, an actions column (per-row icon buttons + overflow menu),
544
+ * columns, a column-management + filter toolbar (with opt-in density / aggregation /
545
+ * pivot / CSV-export tools), an optional aggregation/summary footer, optional
546
+ * checkbox selection with a batch-action toolbar, an actions column (per-row icon buttons + overflow menu),
547
547
  * page-number pagination with a configurable rows-per-page selector, a skeleton
548
548
  * loading state, and an optional server-side mode (sort/filter/paginate on the
549
549
  * backend — only the current page is loaded). Filter controls and the pager are
@@ -579,7 +579,7 @@ interface DatatableProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "row
579
579
  * full row objects server-side rather than relying on `selectedRows`.
580
580
  */
581
581
  batchActions?: DatatableBatchAction[];
582
- /** Row height preset. The toolbar density button cycles it locally; changing this prop re-applies it. @default "standard" */
582
+ /** Row height preset. With `showDensity`, the toolbar density button cycles it locally; changing this prop re-applies it. @default "comfortable" */
583
583
  density?: "compact" | "standard" | "comfortable";
584
584
  /** Initial rows per page. 0 disables pagination. @default 10 */
585
585
  pageSize?: number;
@@ -598,8 +598,13 @@ interface DatatableProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "row
598
598
  rowCount?: number;
599
599
  /** Server-mode callback fired (debounced) whenever the query changes. */
600
600
  onServerChange?: (state: DatatableQuery) => void;
601
- /** Show the Export toolbar button (split button: click = CSV, chevron = format menu). @default true */
601
+ /** Show the Export toolbar button (split button: click = CSV, chevron = format menu). @default false */
602
602
  showExport?: boolean;
603
+ /** Show the row-density toolbar button (cycles compact / standard / comfortable). The `density` prop
604
+ * still sets the row height even when this button is hidden. @default false */
605
+ showDensity?: boolean;
606
+ /** Show the Pivot toolbar button. Also shown automatically when `pivot` or `pivotMode` is set. @default false */
607
+ showPivot?: boolean;
603
608
  /** Filename (without extension) for exports (CSV / Excel). @default "export" */
604
609
  exportFilename?: string;
605
610
  /** Server-mode precomputed aggregation. Per field, either a scalar/node (used as-is) or a per-function map
@@ -637,7 +642,9 @@ interface DatatableProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "row
637
642
  onCellClick?: (value: any, row: any, field: string) => void;
638
643
  /** Fired when the active cell changes: ({ key, field } | null). */
639
644
  onActiveCellChange?: (cell: { key: string | number; field: string } | null) => void;
640
- /** “Show totals row” state (changing the prop re-applies it; the user can also toggle it in the toolbar **Aggregation** panel). Aggregation is configured at runtime from that panel (pick columns + function); a column's `aggregation` prop just seeds the initial choice. @default false */
645
+ /** Show the **Aggregation** toolbar button and start with the totals row on. From that panel the user
646
+ * toggles totals and picks columns + functions (a column's `aggregation` prop seeds the initial choice);
647
+ * changing the prop re-applies it. When false (the default) the Aggregation button is hidden. @default false */
641
648
  showAggregation?: boolean;
642
649
  /** Accessible label for the grid (role="grid"). A standard `aria-label` prop takes precedence. @default "Data table" */
643
650
  ariaLabel?: string;
package/dist/index.mjs CHANGED
@@ -187,7 +187,9 @@ function initials(name) {
187
187
  function safeSrc(url) {
188
188
  if (url == null) return void 0;
189
189
  const s = String(url).replace(/[\x00-\x20]+/g, "").toLowerCase();
190
- return s.startsWith("javascript:") || s.startsWith("vbscript:") ? void 0 : url;
190
+ if (s.startsWith("javascript:") || s.startsWith("vbscript:")) return void 0;
191
+ if (s.startsWith("data:") && !s.startsWith("data:image/")) return void 0;
192
+ return url;
191
193
  }
192
194
  function Avatar({
193
195
  src,
@@ -3780,6 +3782,7 @@ th.twc-dt__rownum .twc-dt__th-inner { padding-inline: 8px; gap: 2px; justify-con
3780
3782
  .twc-dt__sw[data-on="true"]::after { transform: translateX(14px); }
3781
3783
  .twc-dt__panel-head { display: flex; align-items: center; justify-content: space-between; padding: 6px 8px 8px; }
3782
3784
  .twc-dt__panel-title { font-size: var(--text-xs); font-weight: 700; letter-spacing: var(--tracking-wide); text-transform: uppercase; color: var(--color-text-subtle); }
3785
+ .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; }
3783
3786
  .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); }
3784
3787
  .twc-dt__link:hover { background: var(--color-primary-subtle); }
3785
3788
 
@@ -4177,7 +4180,7 @@ function Datatable({
4177
4180
  loading = false,
4178
4181
  rowKey,
4179
4182
  checkboxSelection = false,
4180
- density: densityProp = "standard",
4183
+ density: densityProp = "comfortable",
4181
4184
  pageSize = 10,
4182
4185
  pageSizeOptions = [5, 10, 25, 50],
4183
4186
  height = 440,
@@ -4185,7 +4188,9 @@ function Datatable({
4185
4188
  rowCount,
4186
4189
  onServerChange,
4187
4190
  batchActions = [],
4188
- showExport = true,
4191
+ showExport = false,
4192
+ showDensity = false,
4193
+ showPivot = false,
4189
4194
  exportFilename = "export",
4190
4195
  aggregationValues = null,
4191
4196
  disableColumnReorder = false,
@@ -4724,7 +4729,7 @@ function Datatable({
4724
4729
  rowFocusRef.current = null;
4725
4730
  const root = gridRef.current;
4726
4731
  if (!root) return;
4727
- const sel = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(String(key)) : String(key).replace(/"/g, '\\"');
4732
+ const sel = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(String(key)) : String(key).replace(/[\\"]/g, "\\$&");
4728
4733
  const el = root.querySelector(`.twc-dt__row-handle[data-row-key="${sel}"]`);
4729
4734
  if (el) el.focus();
4730
4735
  });
@@ -5274,6 +5279,8 @@ function Datatable({
5274
5279
  };
5275
5280
  const shownColRows = cols.filter((c) => c.field !== "__pinactions__" && c.headerName.toLowerCase().includes(colQuery.trim().toLowerCase())).sort((a, b) => orderIdxOf(a.field) - orderIdxOf(b.field));
5276
5281
  const rowNumMatch = rowNumbers && "row number".includes(colQuery.trim().toLowerCase());
5282
+ const manageableCols = cols.filter((c) => c.field !== "__pinactions__");
5283
+ const visibleColCount = manageableCols.filter((c) => !hidden.has(c.field)).length;
5277
5284
  const rppOptions = Array.from(new Set([...pageSizeOptions || [], pageSize].filter((n) => n > 0))).sort((a, b) => a - b).map((n) => ({ value: String(n), label: String(n) }));
5278
5285
  const totalCols = ordered.length + (checkboxSelection ? 1 : 0) + (showRowNum ? 1 : 0);
5279
5286
  function renderGroupRow(item) {
@@ -5636,11 +5643,11 @@ function Datatable({
5636
5643
  ]
5637
5644
  }
5638
5645
  ),
5639
- /* @__PURE__ */ jsxs("button", { type: "button", className: "twc-dt__tbtn", "data-tip": "Change row density", onClick: () => setDensity((d) => d === "compact" ? "standard" : d === "standard" ? "comfortable" : "compact"), children: [
5646
+ showDensity ? /* @__PURE__ */ jsxs("button", { type: "button", className: "twc-dt__tbtn", "data-tip": "Change row density", onClick: () => setDensity((d) => d === "compact" ? "standard" : d === "standard" ? "comfortable" : "compact"), children: [
5640
5647
  /* @__PURE__ */ jsx(Svg, { d: I.density }),
5641
5648
  /* @__PURE__ */ jsx("span", { className: "twc-dt__tlabel", children: density[0].toUpperCase() + density.slice(1) })
5642
- ] }),
5643
- /* @__PURE__ */ jsxs(
5649
+ ] }) : null,
5650
+ showAggregation ? /* @__PURE__ */ jsxs(
5644
5651
  "button",
5645
5652
  {
5646
5653
  type: "button",
@@ -5665,8 +5672,8 @@ function Datatable({
5665
5672
  aggOn && hasAggregation ? /* @__PURE__ */ jsx("span", { className: "twc-dt__tbadge", children: ordered.filter((c) => aggOf(c)).length }) : null
5666
5673
  ]
5667
5674
  }
5668
- ),
5669
- /* @__PURE__ */ jsxs(
5675
+ ) : null,
5676
+ showPivot || pivotMode || pivot != null || pivotActive ? /* @__PURE__ */ jsxs(
5670
5677
  "button",
5671
5678
  {
5672
5679
  type: "button",
@@ -5691,7 +5698,7 @@ function Datatable({
5691
5698
  pivotActive ? /* @__PURE__ */ jsx("span", { className: "twc-dt__tdot", "aria-label": "on", role: "img" }) : null
5692
5699
  ]
5693
5700
  }
5694
- ),
5701
+ ) : null,
5695
5702
  showExport ? /* @__PURE__ */ jsxs("span", { className: "twc-dt__export", children: [
5696
5703
  /* @__PURE__ */ jsxs("button", { type: "button", className: "twc-dt__export-main", "data-tip": "Export to CSV", onClick: () => exportData("csv"), "aria-label": "Export to CSV", children: [
5697
5704
  /* @__PURE__ */ jsx(Svg, { d: I.download }),
@@ -6260,7 +6267,14 @@ function Datatable({
6260
6267
  ) : null,
6261
6268
  panel === "columns" && panelPos ? /* @__PURE__ */ jsxs("div", { className: "twc-dt__pop twc-dt__cols", style: { top: panelPos.top, left: panelPos.left }, role: "dialog", "aria-label": "Column settings", children: [
6262
6269
  /* @__PURE__ */ jsxs("div", { className: "twc-dt__panel-head", children: [
6263
- /* @__PURE__ */ jsx("span", { className: "twc-dt__panel-title", children: "Columns" }),
6270
+ /* @__PURE__ */ jsxs("span", { className: "twc-dt__panel-title", children: [
6271
+ "Columns ",
6272
+ /* @__PURE__ */ jsxs("span", { className: "twc-dt__panel-count", title: `${visibleColCount} of ${manageableCols.length} columns shown`, children: [
6273
+ visibleColCount,
6274
+ "/",
6275
+ manageableCols.length
6276
+ ] })
6277
+ ] }),
6264
6278
  /* @__PURE__ */ jsxs("div", { children: [
6265
6279
  /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__link", onClick: () => setHidden(/* @__PURE__ */ new Set()), children: "Show all" }),
6266
6280
  /* @__PURE__ */ jsx("button", { type: "button", className: "twc-dt__link", onClick: () => setHidden(new Set(cols.filter((c) => c.hideable).map((c) => c.field))), children: "Hide all" })
@@ -8836,8 +8850,10 @@ var SIDEBAR_CSS = `
8836
8850
  .twc-sidebar__badge { flex: none; font-size: 11px; font-weight: var(--font-bold); padding: 1px 7px; border-radius: var(--radius-full); background: var(--color-primary); color: var(--color-primary-fg); opacity: 1; visibility: visible;
8837
8851
  transition: opacity var(--duration-fast) var(--ease-standard) var(--duration-fast), visibility 0s; }
8838
8852
  .twc-sidebar[data-collapsed="true"] .twc-sidebar__badge { opacity: 0; visibility: hidden; transition: opacity var(--duration-instant) var(--ease-standard), visibility 0s var(--duration-instant); }
8839
- /* Collapsed: pad the head so the brand mark lines up with the nav icon column (23px). */
8840
- .twc-sidebar[data-collapsed="true"] .twc-sidebar__head { padding: 0 calc(var(--space-3) + 11px); }
8853
+ /* Collapsed: centre the brand mark in the 68px rail (\u2248 the nav icon column) with no
8854
+ right padding, so a mark wider than the icons isn't clipped. flex-start (not
8855
+ justify-content) keeps it from jolting to centre while the rail is still wide. */
8856
+ .twc-sidebar[data-collapsed="true"] .twc-sidebar__head { padding-inline: 18px 0; }
8841
8857
  .twc-sidebar[data-collapsed="true"] .twc-sidebar__brand, .twc-sidebar[data-collapsed="true"] .twc-sidebar__foot-user { font-size: 0; gap: 0; }
8842
8858
  .twc-sidebar[data-collapsed="true"] .twc-sidebar__foot-user { justify-content: center; }
8843
8859
  .twc-sidebar__brand > :not(:first-child) { visibility: visible; transition: opacity var(--duration-fast) var(--ease-standard) var(--duration-fast), visibility 0s; }
@@ -9066,7 +9082,7 @@ function Slider({
9066
9082
  }
9067
9083
  function onKeyDown(e) {
9068
9084
  if (disabled) return;
9069
- let next = val;
9085
+ let next;
9070
9086
  if (e.key === "ArrowRight" || e.key === "ArrowUp") next = clampSnap(val + step);
9071
9087
  else if (e.key === "ArrowLeft" || e.key === "ArrowDown") next = clampSnap(val - step);
9072
9088
  else if (e.key === "Home") next = min;