@morze/ui 0.2.6 → 0.2.8

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
@@ -538,6 +538,12 @@ type DataTableProps<T> = {
538
538
  /** localStorage key for widths, order, visibility and pinning. */
539
539
  persistKey?: string | null;
540
540
  toolbar?: React.ReactNode;
541
+ /**
542
+ * `false` drops the Columns button — for a table whose layout the host
543
+ * fixes, or drives from its own UI. The layout itself still works: `layout`,
544
+ * `onLayoutChange` and `persistKey` are unaffected.
545
+ */
546
+ columnManager?: boolean;
541
547
  /**
542
548
  * Stretches the columns to fill the container on mount and on container
543
549
  * resize, so there is no dead space at the right edge. Columns the user
@@ -568,7 +574,7 @@ type DataTableProps<T> = {
568
574
  caption?: string;
569
575
  className?: string;
570
576
  };
571
- declare function DataTable<T>({ columns, data, rowKey, total, loading, error, onRetry, query, onQueryChange, selection, onSelectionChange, bulkActions, renderExpanded, onRowClick, rowClassName, rowProps, layout: controlledLayout, onLayoutChange, persistKey, toolbar, autoFit, density, stickyHeader, emptyState, pageSizeOptions, pagination, onLoadMore, hasMore, autoLoadMore, labels: labelsProp, locale, caption, className, }: DataTableProps<T>): React.JSX.Element;
577
+ declare function DataTable<T>({ columns, data, rowKey, total, loading, error, onRetry, query, onQueryChange, selection, onSelectionChange, bulkActions, renderExpanded, onRowClick, rowClassName, rowProps, layout: controlledLayout, onLayoutChange, persistKey, toolbar, columnManager, autoFit, density, stickyHeader, emptyState, pageSizeOptions, pagination, onLoadMore, hasMore, autoLoadMore, labels: labelsProp, locale, caption, className, }: DataTableProps<T>): React.JSX.Element;
572
578
 
573
579
  declare function DataTablePagination({ query, total, rowsOnPage, pageSizeOptions, labels: labelsProp, locale, onQueryChange, }: {
574
580
  query: DataTableQuery;
@@ -590,7 +596,12 @@ type Props$1<T> = {
590
596
  layout: ColumnLayout;
591
597
  onToggleHidden: (id: string) => void;
592
598
  onSetPinned: (id: string, side: 'left' | 'right' | undefined) => void;
593
- onMove: (id: string, delta: number) => void;
599
+ /**
600
+ * Unused since the arrows started moving a column past its neighbour in the
601
+ * same pin group, which `onMoveTo` already expresses. Kept so a host that
602
+ * mounts this component directly still type-checks.
603
+ */
604
+ onMove?: (id: string, delta: number) => void;
594
605
  onMoveTo: (id: string, targetId: string) => void;
595
606
  onReset: () => void;
596
607
  labels?: Partial<DataTableLabels>;
@@ -599,8 +610,16 @@ type Props$1<T> = {
599
610
  * Reordering lives here rather than on the header itself: dragging a header
600
611
  * would fight the resize handle, and a list gives keyboard users real controls
601
612
  * instead of a drag-only affordance.
613
+ *
614
+ * The list is grouped the way the table paints — pinned left, then loose, then
615
+ * pinned right — because it used to show the raw `layout.order` instead. A
616
+ * column dragged to the top of that list landed second in the table, behind
617
+ * whatever was pinned, and moving a column past a pinned neighbour changed the
618
+ * order without changing anything on screen. Both read as a broken reorder.
619
+ * Moves therefore stay inside a column's own group; crossing between them is
620
+ * what the pin button is for.
602
621
  */
603
- declare function ColumnManager<T>({ columns, layout, onToggleHidden, onSetPinned, onMove, onMoveTo, onReset, labels: labelsProp, }: Props$1<T>): React.JSX.Element;
622
+ declare function ColumnManager<T>({ columns, layout, onToggleHidden, onSetPinned, onMoveTo, onReset, labels: labelsProp, }: Props$1<T>): React.JSX.Element;
604
623
 
605
624
  type Props = {
606
625
  columnLabel: string;
package/dist/index.d.ts CHANGED
@@ -538,6 +538,12 @@ type DataTableProps<T> = {
538
538
  /** localStorage key for widths, order, visibility and pinning. */
539
539
  persistKey?: string | null;
540
540
  toolbar?: React.ReactNode;
541
+ /**
542
+ * `false` drops the Columns button — for a table whose layout the host
543
+ * fixes, or drives from its own UI. The layout itself still works: `layout`,
544
+ * `onLayoutChange` and `persistKey` are unaffected.
545
+ */
546
+ columnManager?: boolean;
541
547
  /**
542
548
  * Stretches the columns to fill the container on mount and on container
543
549
  * resize, so there is no dead space at the right edge. Columns the user
@@ -568,7 +574,7 @@ type DataTableProps<T> = {
568
574
  caption?: string;
569
575
  className?: string;
570
576
  };
571
- declare function DataTable<T>({ columns, data, rowKey, total, loading, error, onRetry, query, onQueryChange, selection, onSelectionChange, bulkActions, renderExpanded, onRowClick, rowClassName, rowProps, layout: controlledLayout, onLayoutChange, persistKey, toolbar, autoFit, density, stickyHeader, emptyState, pageSizeOptions, pagination, onLoadMore, hasMore, autoLoadMore, labels: labelsProp, locale, caption, className, }: DataTableProps<T>): React.JSX.Element;
577
+ declare function DataTable<T>({ columns, data, rowKey, total, loading, error, onRetry, query, onQueryChange, selection, onSelectionChange, bulkActions, renderExpanded, onRowClick, rowClassName, rowProps, layout: controlledLayout, onLayoutChange, persistKey, toolbar, columnManager, autoFit, density, stickyHeader, emptyState, pageSizeOptions, pagination, onLoadMore, hasMore, autoLoadMore, labels: labelsProp, locale, caption, className, }: DataTableProps<T>): React.JSX.Element;
572
578
 
573
579
  declare function DataTablePagination({ query, total, rowsOnPage, pageSizeOptions, labels: labelsProp, locale, onQueryChange, }: {
574
580
  query: DataTableQuery;
@@ -590,7 +596,12 @@ type Props$1<T> = {
590
596
  layout: ColumnLayout;
591
597
  onToggleHidden: (id: string) => void;
592
598
  onSetPinned: (id: string, side: 'left' | 'right' | undefined) => void;
593
- onMove: (id: string, delta: number) => void;
599
+ /**
600
+ * Unused since the arrows started moving a column past its neighbour in the
601
+ * same pin group, which `onMoveTo` already expresses. Kept so a host that
602
+ * mounts this component directly still type-checks.
603
+ */
604
+ onMove?: (id: string, delta: number) => void;
594
605
  onMoveTo: (id: string, targetId: string) => void;
595
606
  onReset: () => void;
596
607
  labels?: Partial<DataTableLabels>;
@@ -599,8 +610,16 @@ type Props$1<T> = {
599
610
  * Reordering lives here rather than on the header itself: dragging a header
600
611
  * would fight the resize handle, and a list gives keyboard users real controls
601
612
  * instead of a drag-only affordance.
613
+ *
614
+ * The list is grouped the way the table paints — pinned left, then loose, then
615
+ * pinned right — because it used to show the raw `layout.order` instead. A
616
+ * column dragged to the top of that list landed second in the table, behind
617
+ * whatever was pinned, and moving a column past a pinned neighbour changed the
618
+ * order without changing anything on screen. Both read as a broken reorder.
619
+ * Moves therefore stay inside a column's own group; crossing between them is
620
+ * what the pin button is for.
602
621
  */
603
- declare function ColumnManager<T>({ columns, layout, onToggleHidden, onSetPinned, onMove, onMoveTo, onReset, labels: labelsProp, }: Props$1<T>): React.JSX.Element;
622
+ declare function ColumnManager<T>({ columns, layout, onToggleHidden, onSetPinned, onMoveTo, onReset, labels: labelsProp, }: Props$1<T>): React.JSX.Element;
604
623
 
605
624
  type Props = {
606
625
  columnLabel: string;
package/dist/index.js CHANGED
@@ -138,8 +138,8 @@ var ArrowUpIcon = icon(/* @__PURE__ */ jsx("path", { d: "M12 19V5m-7 7 7-7 7 7"
138
138
  var ArrowDownIcon = icon(/* @__PURE__ */ jsx("path", { d: "M12 5v14m7-7-7 7-7-7" }));
139
139
  var SortIcon = icon(
140
140
  /* @__PURE__ */ jsxs(Fragment, { children: [
141
- /* @__PURE__ */ jsx("path", { d: "m7 15 3 3 3-3", opacity: "0.9" }),
142
- /* @__PURE__ */ jsx("path", { d: "m7 9 3-3 3 3", opacity: "0.9" })
141
+ /* @__PURE__ */ jsx("path", { d: "m8 10 4-4 4 4" }),
142
+ /* @__PURE__ */ jsx("path", { d: "m8 14 4 4 4-4" })
143
143
  ] })
144
144
  );
145
145
  var FilterIcon = icon(/* @__PURE__ */ jsx("path", { d: "M4 5h16l-6.5 7.5V19l-3 2v-8.5L4 5Z" }));
@@ -159,7 +159,13 @@ var GripIcon = icon(
159
159
  /* @__PURE__ */ jsx("circle", { cx: "15", cy: "17", r: "1.4", fill: "currentColor" })
160
160
  ] })
161
161
  );
162
- var PinIcon = icon(/* @__PURE__ */ jsx("path", { d: "M15 3 21 9l-4 1-4 4-1 5-6-6 5-1 4-4 1-4Z" }));
162
+ var PinIcon = icon(
163
+ /* @__PURE__ */ jsxs(Fragment, { children: [
164
+ /* @__PURE__ */ jsx("path", { d: "M9 4h6v5l3 4v2H6v-2l3-4V4Z" }),
165
+ /* @__PURE__ */ jsx("path", { d: "M8 4h8" }),
166
+ /* @__PURE__ */ jsx("path", { d: "M12 15v5" })
167
+ ] })
168
+ );
163
169
  var EyeIcon = icon(
164
170
  /* @__PURE__ */ jsxs(Fragment, { children: [
165
171
  /* @__PURE__ */ jsx("path", { d: "M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z" }),
@@ -2082,26 +2088,53 @@ function FilterChip({
2082
2088
  /* @__PURE__ */ jsx("button", { type: "button", onClick: onClear, "aria-label": clearLabel, className: "mz-focusable", children: "\xD7" })
2083
2089
  ] });
2084
2090
  }
2091
+ function nameOf(column, declaredIndex) {
2092
+ if (column.label?.trim()) return column.label;
2093
+ if (typeof column.header === "string" && column.header.trim()) return column.header;
2094
+ return `#${declaredIndex + 1}`;
2095
+ }
2085
2096
  function ColumnManager({
2086
2097
  columns,
2087
2098
  layout,
2088
2099
  onToggleHidden,
2089
2100
  onSetPinned,
2090
- onMove,
2091
2101
  onMoveTo,
2092
2102
  onReset,
2093
2103
  labels: labelsProp
2094
2104
  }) {
2095
2105
  const labels = resolveLabels(labelsProp);
2096
2106
  const [dragging, setDragging] = React39.useState(null);
2097
- const byId = React39.useMemo(() => new Map(columns.map((c) => [c.id, c])), [columns]);
2098
- const ordered = layout.order.map((id) => byId.get(id)).filter((c) => Boolean(c));
2107
+ const [dropTarget, setDropTarget] = React39.useState(null);
2108
+ const entries = React39.useMemo(() => {
2109
+ const byId = new Map(columns.map((c, index) => [c.id, { column: c, declaredIndex: index }]));
2110
+ const ordered = layout.order.map((id) => byId.get(id)).filter((e) => Boolean(e));
2111
+ const side = (id) => layout.pinned[id];
2112
+ const groups = ["left", void 0, "right"];
2113
+ return groups.flatMap((group) => {
2114
+ const inGroup = ordered.filter((e) => side(e.column.id) === group);
2115
+ return inGroup.map((entry, indexInGroup) => ({
2116
+ ...entry,
2117
+ side: group,
2118
+ indexInGroup,
2119
+ groupSize: inGroup.length,
2120
+ previous: inGroup[indexInGroup - 1]?.column.id,
2121
+ next: inGroup[indexInGroup + 1]?.column.id,
2122
+ name: nameOf(entry.column, entry.declaredIndex)
2123
+ }));
2124
+ });
2125
+ }, [columns, layout]);
2126
+ const sideOf = (id) => layout.pinned[id];
2127
+ const canDrop = (targetId) => Boolean(dragging) && dragging !== targetId && sideOf(dragging) === sideOf(targetId);
2128
+ const endDrag = () => {
2129
+ setDragging(null);
2130
+ setDropTarget(null);
2131
+ };
2099
2132
  const hiddenCount = layout.hidden.length;
2100
2133
  return /* @__PURE__ */ jsxs(Popover, { children: [
2101
2134
  /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "secondary", size: "sm", children: [
2102
2135
  /* @__PURE__ */ jsx(ColumnsIcon, {}),
2103
2136
  labels.columns,
2104
- hiddenCount > 0 ? /* @__PURE__ */ jsx("span", { className: "mz-dt__count", children: ordered.length - hiddenCount }) : null
2137
+ hiddenCount > 0 ? /* @__PURE__ */ jsx("span", { className: "mz-dt__count", children: entries.length - hiddenCount }) : null
2105
2138
  ] }) }),
2106
2139
  /* @__PURE__ */ jsxs(PopoverContent, { align: "end", className: "mz-dt__columns", children: [
2107
2140
  /* @__PURE__ */ jsxs("div", { className: "mz-dt__columns-head", children: [
@@ -2109,35 +2142,60 @@ function ColumnManager({
2109
2142
  /* @__PURE__ */ jsx(Button, { variant: "link", size: "xs", onClick: onReset, children: labels.reset })
2110
2143
  ] }),
2111
2144
  /* @__PURE__ */ jsx(Separator, {}),
2112
- /* @__PURE__ */ jsx("ul", { className: "mz-dt__columns-list", children: ordered.map((column, index) => {
2145
+ /* @__PURE__ */ jsx("ul", { className: "mz-dt__columns-list", children: entries.map((entry) => {
2146
+ const column = entry.column;
2113
2147
  const hidden = layout.hidden.includes(column.id);
2114
- const pinned = layout.pinned[column.id];
2115
- const label = column.label ?? (typeof column.header === "string" ? column.header : column.id);
2148
+ const pinned = entry.side;
2149
+ const name = entry.name;
2150
+ const pinTitle = pinned === "left" ? labels.pinnedLeft : pinned === "right" ? labels.pinnedRight : labels.notPinned;
2116
2151
  return /* @__PURE__ */ jsxs(
2117
2152
  "li",
2118
2153
  {
2119
2154
  className: "mz-dt__columns-item",
2120
2155
  "data-dragging": dragging === column.id || void 0,
2156
+ "data-drop": dropTarget === column.id || void 0,
2157
+ "data-pinned": pinned,
2121
2158
  draggable: true,
2122
- onDragStart: () => setDragging(column.id),
2123
- onDragEnd: () => setDragging(null),
2124
- onDragOver: (event) => event.preventDefault(),
2125
- onDrop: () => {
2126
- if (dragging && dragging !== column.id) onMoveTo(dragging, column.id);
2127
- setDragging(null);
2159
+ onDragStart: (event) => {
2160
+ if (event.target.closest(".mz-dt__columns-controls")) {
2161
+ event.preventDefault();
2162
+ return;
2163
+ }
2164
+ setDragging(column.id);
2165
+ event.dataTransfer.effectAllowed = "move";
2166
+ try {
2167
+ event.dataTransfer.setData("text/plain", column.id);
2168
+ } catch {
2169
+ }
2170
+ },
2171
+ onDragEnd: endDrag,
2172
+ onDragOver: (event) => {
2173
+ if (!canDrop(column.id)) {
2174
+ event.dataTransfer.dropEffect = "none";
2175
+ return;
2176
+ }
2177
+ event.preventDefault();
2178
+ event.dataTransfer.dropEffect = "move";
2179
+ setDropTarget(column.id);
2180
+ },
2181
+ onDragLeave: () => setDropTarget((c) => c === column.id ? null : c),
2182
+ onDrop: (event) => {
2183
+ event.preventDefault();
2184
+ if (canDrop(column.id)) onMoveTo(dragging, column.id);
2185
+ endDrag();
2128
2186
  },
2129
2187
  children: [
2130
2188
  /* @__PURE__ */ jsx("span", { className: "mz-dt__columns-grip", "aria-hidden": "true", children: /* @__PURE__ */ jsx(GripIcon, {}) }),
2131
- /* @__PURE__ */ jsx("span", { className: "mz-dt__columns-label", children: label }),
2132
- /* @__PURE__ */ jsxs("div", { className: "mz-dt__columns-controls", children: [
2189
+ /* @__PURE__ */ jsx("span", { className: "mz-dt__columns-label", children: name }),
2190
+ /* @__PURE__ */ jsxs("div", { className: "mz-dt__columns-controls", draggable: false, children: [
2133
2191
  /* @__PURE__ */ jsx(
2134
2192
  "button",
2135
2193
  {
2136
2194
  type: "button",
2137
2195
  className: "mz-dt__icon-btn mz-focusable",
2138
- disabled: index === 0,
2139
- onClick: () => onMove(column.id, -1),
2140
- "aria-label": labels.moveUp(label),
2196
+ disabled: !entry.previous,
2197
+ onClick: () => entry.previous && onMoveTo(column.id, entry.previous),
2198
+ "aria-label": labels.moveUp(name),
2141
2199
  children: "\u2191"
2142
2200
  }
2143
2201
  ),
@@ -2146,9 +2204,9 @@ function ColumnManager({
2146
2204
  {
2147
2205
  type: "button",
2148
2206
  className: "mz-dt__icon-btn mz-focusable",
2149
- disabled: index === ordered.length - 1,
2150
- onClick: () => onMove(column.id, 1),
2151
- "aria-label": labels.moveDown(label),
2207
+ disabled: !entry.next,
2208
+ onClick: () => entry.next && onMoveTo(column.id, entry.next),
2209
+ "aria-label": labels.moveDown(name),
2152
2210
  children: "\u2193"
2153
2211
  }
2154
2212
  ),
@@ -2156,14 +2214,12 @@ function ColumnManager({
2156
2214
  "button",
2157
2215
  {
2158
2216
  type: "button",
2159
- className: "mz-dt__icon-btn mz-focusable",
2217
+ className: "mz-dt__icon-btn mz-dt__pin mz-focusable",
2160
2218
  "data-active": pinned ? true : void 0,
2219
+ "data-pinned": pinned,
2161
2220
  onClick: () => onSetPinned(column.id, pinned === "left" ? "right" : pinned === "right" ? void 0 : "left"),
2162
- "aria-label": labels.pinning(
2163
- label,
2164
- pinned === "left" ? labels.pinnedLeft : pinned === "right" ? labels.pinnedRight : labels.notPinned
2165
- ),
2166
- title: pinned === "left" ? labels.pinnedLeft : pinned === "right" ? labels.pinnedRight : labels.notPinned,
2221
+ "aria-label": labels.pinning(name, pinTitle),
2222
+ title: pinTitle,
2167
2223
  children: /* @__PURE__ */ jsx(PinIcon, {})
2168
2224
  }
2169
2225
  ),
@@ -2174,7 +2230,7 @@ function ColumnManager({
2174
2230
  className: "mz-dt__icon-btn mz-focusable",
2175
2231
  disabled: column.hideable === false,
2176
2232
  onClick: () => onToggleHidden(column.id),
2177
- "aria-label": hidden ? labels.show(label) : labels.hide(label),
2233
+ "aria-label": hidden ? labels.show(name) : labels.hide(name),
2178
2234
  children: hidden ? /* @__PURE__ */ jsx(EyeOffIcon, {}) : /* @__PURE__ */ jsx(EyeIcon, {})
2179
2235
  }
2180
2236
  )
@@ -2627,6 +2683,7 @@ function DataTable({
2627
2683
  onLayoutChange,
2628
2684
  persistKey = null,
2629
2685
  toolbar,
2686
+ columnManager = true,
2630
2687
  autoFit = true,
2631
2688
  density = "normal",
2632
2689
  stickyHeader = true,
@@ -2655,7 +2712,6 @@ function DataTable({
2655
2712
  setWidth,
2656
2713
  toggleHidden,
2657
2714
  setPinned,
2658
- move,
2659
2715
  moveTo,
2660
2716
  reset,
2661
2717
  fitTo
@@ -2887,22 +2943,25 @@ function DataTable({
2887
2943
  className: cn("mz-dt", className),
2888
2944
  style: widthVars,
2889
2945
  children: [
2890
- (toolbar || chips.length > 0 || columns.some((c) => c.hideable !== false)) && /* @__PURE__ */ jsxs("div", { className: "mz-dt__toolbar", children: [
2891
- /* @__PURE__ */ jsx("div", { className: "mz-dt__toolbar-main", children: toolbar }),
2892
- /* @__PURE__ */ jsx("div", { className: "mz-dt__toolbar-side", children: /* @__PURE__ */ jsx(
2893
- ColumnManager,
2894
- {
2895
- columns,
2896
- layout,
2897
- labels: labelsProp,
2898
- onToggleHidden: toggleHidden,
2899
- onSetPinned: setPinned,
2900
- onMove: move,
2901
- onMoveTo: moveTo,
2902
- onReset: reset
2903
- }
2904
- ) })
2905
- ] }),
2946
+ (() => {
2947
+ const showManager = columnManager && columns.some((c) => c.hideable !== false);
2948
+ if (!toolbar && chips.length === 0 && !showManager) return null;
2949
+ return /* @__PURE__ */ jsxs("div", { className: "mz-dt__toolbar", children: [
2950
+ /* @__PURE__ */ jsx("div", { className: "mz-dt__toolbar-main", children: toolbar }),
2951
+ showManager && /* @__PURE__ */ jsx("div", { className: "mz-dt__toolbar-side", children: /* @__PURE__ */ jsx(
2952
+ ColumnManager,
2953
+ {
2954
+ columns,
2955
+ layout,
2956
+ labels: labelsProp,
2957
+ onToggleHidden: toggleHidden,
2958
+ onSetPinned: setPinned,
2959
+ onMoveTo: moveTo,
2960
+ onReset: reset
2961
+ }
2962
+ ) })
2963
+ ] });
2964
+ })(),
2906
2965
  chips.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mz-dt__chips", children: [
2907
2966
  chips.map(([id, value]) => /* @__PURE__ */ jsx(
2908
2967
  FilterChip,