@dbcdk/react-components 0.0.197 → 0.0.198

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.
Files changed (55) hide show
  1. package/dist/components/json-viewer/JsonViewer.module.css +52 -0
  2. package/dist/components/json-viewer/JsonViewerShell.cjs +147 -143
  3. package/dist/components/json-viewer/JsonViewerShell.js +149 -145
  4. package/dist/components/line-viewer/LineViewer.cjs +15 -10
  5. package/dist/components/line-viewer/LineViewer.d.ts +1 -1
  6. package/dist/components/line-viewer/LineViewer.js +15 -10
  7. package/dist/components/line-viewer/LineViewer.module.css +58 -3
  8. package/dist/components/page-layout/PageLayout.module.css +1 -1
  9. package/dist/components/scroll-fade/ScrollFade.cjs +42 -0
  10. package/dist/components/scroll-fade/ScrollFade.d.ts +18 -0
  11. package/dist/components/scroll-fade/ScrollFade.js +36 -0
  12. package/dist/components/scroll-fade/ScrollFade.module.css +71 -0
  13. package/dist/components/search-box/SearchBox.cjs +39 -35
  14. package/dist/components/search-box/SearchBox.d.ts +2 -0
  15. package/dist/components/search-box/SearchBox.js +39 -35
  16. package/dist/components/table/Table.cjs +52 -23
  17. package/dist/components/table/Table.d.ts +1 -1
  18. package/dist/components/table/Table.js +53 -24
  19. package/dist/components/table/Table.module.css +4 -0
  20. package/dist/components/table/Table.types.d.ts +27 -0
  21. package/dist/components/table/TanstackTable.cjs +64 -64
  22. package/dist/components/table/TanstackTable.js +67 -67
  23. package/dist/components/table/columnLayout.utils.cjs +146 -0
  24. package/dist/components/table/columnLayout.utils.d.ts +44 -0
  25. package/dist/components/table/columnLayout.utils.js +141 -0
  26. package/dist/components/table/components/TableHeader.cjs +3 -1
  27. package/dist/components/table/components/TableHeader.d.ts +2 -1
  28. package/dist/components/table/components/TableHeader.js +3 -1
  29. package/dist/components/table/components/TableHeaderCell.cjs +29 -19
  30. package/dist/components/table/components/TableHeaderCell.d.ts +2 -1
  31. package/dist/components/table/components/TableHeaderCell.js +29 -19
  32. package/dist/components/table/hooks/useElementWidth.cjs +40 -0
  33. package/dist/components/table/hooks/useElementWidth.d.ts +7 -0
  34. package/dist/components/table/hooks/useElementWidth.js +38 -0
  35. package/dist/components/table/table.utils.cjs +24 -2
  36. package/dist/components/table/table.utils.d.ts +40 -2
  37. package/dist/components/table/table.utils.js +20 -2
  38. package/dist/components/table/tanstackTable.utils.cjs +33 -97
  39. package/dist/components/table/tanstackTable.utils.d.ts +24 -8
  40. package/dist/components/table/tanstackTable.utils.js +34 -99
  41. package/dist/components/xml-viewer/XmlViewer.cjs +15 -10
  42. package/dist/components/xml-viewer/XmlViewer.d.ts +1 -1
  43. package/dist/components/xml-viewer/XmlViewer.js +15 -10
  44. package/dist/components/xml-viewer/XmlViewer.module.css +58 -3
  45. package/dist/hooks/useScrollOverflow.cjs +71 -0
  46. package/dist/hooks/useScrollOverflow.d.ts +23 -0
  47. package/dist/hooks/useScrollOverflow.js +69 -0
  48. package/dist/hooks/useTableSettings.cjs +2 -1
  49. package/dist/hooks/useTableSettings.d.ts +1 -0
  50. package/dist/hooks/useTableSettings.js +2 -1
  51. package/dist/index.css +1 -0
  52. package/dist/utils/refs.utils.cjs +13 -0
  53. package/dist/utils/refs.utils.d.ts +7 -0
  54. package/dist/utils/refs.utils.js +11 -0
  55. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ var columnLayout_utils = require('./columnLayout.utils');
3
4
  var table_utils = require('./table.utils');
4
5
 
5
6
  function getColumnId(def, index) {
@@ -74,124 +75,59 @@ function getSortPropsFromSorting(sorting) {
74
75
  };
75
76
  }
76
77
  function clamp(value, min, max) {
77
- const lowerBounded = Math.max(min, value);
78
- if (max == null || !Number.isFinite(max)) return lowerBounded;
79
- return Math.min(lowerBounded, max);
78
+ return Math.min(Math.max(value, min), max);
80
79
  }
81
- function toIntegerTrackWidths(tracks, targetWidth) {
82
- const floored = tracks.map((track) => ({
83
- id: track.id,
84
- width: Math.floor(track.width),
85
- fraction: track.width - Math.floor(track.width)
86
- }));
87
- const baseTotal = floored.reduce((sum, track) => sum + track.width, 0);
88
- let remainder = Math.max(0, targetWidth - baseTotal);
89
- floored.slice().sort((a, b) => b.fraction - a.fraction).forEach((track) => {
90
- if (remainder <= 0) return;
91
- track.width += 1;
92
- remainder -= 1;
93
- });
94
- return Object.fromEntries(floored.map((track) => [track.id, track.width]));
80
+ function applyNeighborResize(next, resize) {
81
+ const proposed = next[resize.columnId];
82
+ if (proposed == null) return next;
83
+ const total = resize.startWidth + resize.neighborStartWidth;
84
+ const min = Math.min(resize.min, resize.startWidth);
85
+ const max = Math.max(resize.max, resize.startWidth);
86
+ const neighborMin = Math.min(resize.neighborMin, resize.neighborStartWidth);
87
+ const neighborMax = Math.max(resize.neighborMax, resize.neighborStartWidth);
88
+ const width = Math.round(
89
+ clamp(
90
+ resize.startWidth + proposed - resize.tanstackStartWidth,
91
+ Math.max(min, total - neighborMax),
92
+ Math.min(max, total - neighborMin)
93
+ )
94
+ );
95
+ return { ...next, [resize.columnId]: width, [resize.neighborId]: total - width };
95
96
  }
96
97
  function buildDistributedColumnWidths(args) {
97
98
  const {
98
99
  table,
99
100
  hasSelection,
100
- hasContextMenu = false,
101
+ hasContextMenu,
102
+ contextMenuReservePx = table_utils.ROW_ACTIONS_RESERVE_PX,
101
103
  defaultMinPx,
102
104
  columnSizing,
103
105
  availableWidth
104
106
  } = args;
105
- const leaf = table.getVisibleLeafColumns();
106
- const selectionWidth = hasSelection ? table_utils.SELECTION_COLUMN_PX : 0;
107
- const contextMenuWidth = hasContextMenu ? 0 : 0;
108
- const tracks = leaf.map((c) => {
107
+ const visibleColumns = table.getVisibleLeafColumns();
108
+ const tracks = visibleColumns.map((c, index) => {
109
109
  var _a, _b, _c, _d;
110
110
  const def = c.columnDef;
111
111
  const meta = (_a = def.meta) != null ? _a : {};
112
112
  const min = Math.max(1, Number((_b = def.minSize) != null ? _b : defaultMinPx));
113
113
  const max = def.maxSize != null ? Math.max(min, Number(def.maxSize)) : void 0;
114
114
  const resizedPxRaw = columnSizing[c.id];
115
- const resizedPx = resizedPxRaw != null ? Math.round(clamp(Number(resizedPxRaw), min, max)) : void 0;
116
- if (resizedPx != null) {
117
- return {
118
- id: c.id,
119
- width: resizedPx,
120
- weight: 0,
121
- min,
122
- max: resizedPx,
123
- fixed: true
124
- };
125
- }
115
+ const reserve = hasContextMenu && index === visibleColumns.length - 1 ? contextMenuReservePx : 0;
116
+ const fixedWidth = resizedPxRaw != null ? Math.round(clamp(Number(resizedPxRaw), min + reserve, (max != null ? max : Infinity) + reserve)) : void 0;
126
117
  const rawWeight = Number((_d = (_c = meta.weight) != null ? _c : def.size) != null ? _d : table_utils.DEFAULT_COLUMN_PX);
127
118
  const weight = Number.isFinite(rawWeight) && rawWeight > 0 ? rawWeight : table_utils.DEFAULT_COLUMN_PX;
128
- return {
129
- id: c.id,
130
- width: min,
131
- weight,
132
- min,
133
- max,
134
- fixed: false
135
- };
119
+ return { id: c.id, min, max, weight, fixedWidth };
120
+ });
121
+ return columnLayout_utils.distributeColumnWidths({
122
+ tracks,
123
+ hasSelection,
124
+ hasContextMenu,
125
+ contextMenuReservePx,
126
+ availableWidth
136
127
  });
137
- const lastTrack = tracks[tracks.length - 1];
138
- if (hasContextMenu && lastTrack && !lastTrack.fixed) {
139
- lastTrack.min += table_utils.ROW_ACTIONS_RESERVE_PX;
140
- lastTrack.width += table_utils.ROW_ACTIONS_RESERVE_PX;
141
- if (lastTrack.max != null) lastTrack.max += table_utils.ROW_ACTIONS_RESERVE_PX;
142
- }
143
- const fixedWidth = selectionWidth + contextMenuWidth + tracks.reduce((sum, track) => sum + track.width, 0);
144
- let remaining = Math.max(0, availableWidth - fixedWidth);
145
- let active = tracks.filter(
146
- (track) => !track.fixed && (track.max == null || track.width < track.max)
147
- );
148
- while (remaining > 0.5 && active.length > 0) {
149
- const totalWeight = active.reduce((sum, track) => sum + track.weight, 0);
150
- if (totalWeight <= 0) break;
151
- let consumed = 0;
152
- const nextActive = [];
153
- for (const track of active) {
154
- const share = remaining * track.weight / totalWeight;
155
- const maxGrow = track.max == null ? share : Math.max(0, track.max - track.width);
156
- const growth = Math.min(share, maxGrow);
157
- track.width += growth;
158
- consumed += growth;
159
- if (track.max == null || track.width < track.max - 0.5) {
160
- nextActive.push(track);
161
- }
162
- }
163
- if (consumed <= 0.5) break;
164
- remaining -= consumed;
165
- active = nextActive;
166
- }
167
- const flexible = tracks.filter((track) => !track.fixed);
168
- if (remaining > 0.5 && flexible.length > 0 && flexible.every((track) => track.max != null)) {
169
- const stretchable = flexible.filter((track) => track.max > track.min);
170
- const totalWeight = stretchable.reduce((sum, track) => sum + track.weight, 0);
171
- if (totalWeight > 0) {
172
- for (const track of stretchable) {
173
- track.width += remaining * track.weight / totalWeight;
174
- }
175
- }
176
- }
177
- const floatTrackTotal = tracks.reduce((sum, track) => sum + track.width, 0);
178
- const maxTrackWidth = availableWidth - selectionWidth - contextMenuWidth;
179
- const targetTrackWidth = Math.min(
180
- floatTrackTotal <= maxTrackWidth ? Math.floor(floatTrackTotal) : Math.ceil(floatTrackTotal),
181
- maxTrackWidth
182
- );
183
- const widths = toIntegerTrackWidths(tracks, targetTrackWidth);
184
- const totalWidth = selectionWidth + contextMenuWidth + Object.values(widths).reduce((sum, width) => sum + width, 0);
185
- return {
186
- selectionWidth: hasSelection ? table_utils.SELECTION_COLUMN_PX : void 0,
187
- contextMenuWidth: hasContextMenu ? 0 : void 0,
188
- widths,
189
- totalWidth,
190
- availableWidth,
191
- shouldOverflow: totalWidth > availableWidth
192
- };
193
128
  }
194
129
 
130
+ exports.applyNeighborResize = applyNeighborResize;
195
131
  exports.buildColumnVisibilityFromVisibleIds = buildColumnVisibilityFromVisibleIds;
196
132
  exports.buildDistributedColumnWidths = buildDistributedColumnWidths;
197
133
  exports.getColumnId = getColumnId;
@@ -1,4 +1,5 @@
1
1
  import type { ColumnDef, ColumnSizingState, SortingState, VisibilityState } from '@tanstack/react-table';
2
+ import { type DistributedColumnWidths } from './columnLayout.utils';
2
3
  import { ColumnItem } from './Table.types';
3
4
  type AnyRecord = Record<string, any>;
4
5
  export declare function getColumnId<T>(def: ColumnDef<T, any>, index: number): string;
@@ -11,19 +12,34 @@ export declare function getSortPropsFromSorting(sorting: SortingState): {
11
12
  sortById?: string;
12
13
  sortDirection: 'asc' | 'desc' | null;
13
14
  };
15
+ /** The two columns a drag moves width between, as captured on resize start. */
16
+ export type NeighborResize = {
17
+ columnId: string;
18
+ neighborId: string;
19
+ /** TanStack's own start size; its updates are this plus the drag offset. */
20
+ tanstackStartWidth: number;
21
+ startWidth: number;
22
+ neighborStartWidth: number;
23
+ min: number;
24
+ max: number;
25
+ neighborMin: number;
26
+ neighborMax: number;
27
+ };
28
+ /**
29
+ * Applies a TanStack resize update as a move of width between the dragged
30
+ * column and its right neighbour, so their sum (and every other column) stays
31
+ * put. Both columns stay within their bounds, widened to their start widths
32
+ * so a column already outside its bounds doesn't jump when grabbed.
33
+ */
34
+ export declare function applyNeighborResize(next: ColumnSizingState, resize: NeighborResize): ColumnSizingState;
14
35
  export declare function buildDistributedColumnWidths(args: {
15
36
  table: any;
16
37
  hasSelection: boolean;
17
38
  hasContextMenu?: boolean;
39
+ /** Space the last column reserves for actions; see `getActionsReserve`. */
40
+ contextMenuReservePx?: number;
18
41
  defaultMinPx: number;
19
42
  columnSizing: ColumnSizingState;
20
43
  availableWidth: number;
21
- }): {
22
- selectionWidth?: number;
23
- contextMenuWidth?: number;
24
- widths: Record<string, number>;
25
- totalWidth: number;
26
- availableWidth: number;
27
- shouldOverflow: boolean;
28
- };
44
+ }): DistributedColumnWidths;
29
45
  export {};
@@ -1,4 +1,5 @@
1
- import { DEFAULT_COLUMN_PX, ROW_ACTIONS_RESERVE_PX, SELECTION_COLUMN_PX } from './table.utils';
1
+ import { distributeColumnWidths } from './columnLayout.utils';
2
+ import { ROW_ACTIONS_RESERVE_PX, DEFAULT_COLUMN_PX } from './table.utils';
2
3
 
3
4
  function getColumnId(def, index) {
4
5
  const d = def;
@@ -72,122 +73,56 @@ function getSortPropsFromSorting(sorting) {
72
73
  };
73
74
  }
74
75
  function clamp(value, min, max) {
75
- const lowerBounded = Math.max(min, value);
76
- if (max == null || !Number.isFinite(max)) return lowerBounded;
77
- return Math.min(lowerBounded, max);
76
+ return Math.min(Math.max(value, min), max);
78
77
  }
79
- function toIntegerTrackWidths(tracks, targetWidth) {
80
- const floored = tracks.map((track) => ({
81
- id: track.id,
82
- width: Math.floor(track.width),
83
- fraction: track.width - Math.floor(track.width)
84
- }));
85
- const baseTotal = floored.reduce((sum, track) => sum + track.width, 0);
86
- let remainder = Math.max(0, targetWidth - baseTotal);
87
- floored.slice().sort((a, b) => b.fraction - a.fraction).forEach((track) => {
88
- if (remainder <= 0) return;
89
- track.width += 1;
90
- remainder -= 1;
91
- });
92
- return Object.fromEntries(floored.map((track) => [track.id, track.width]));
78
+ function applyNeighborResize(next, resize) {
79
+ const proposed = next[resize.columnId];
80
+ if (proposed == null) return next;
81
+ const total = resize.startWidth + resize.neighborStartWidth;
82
+ const min = Math.min(resize.min, resize.startWidth);
83
+ const max = Math.max(resize.max, resize.startWidth);
84
+ const neighborMin = Math.min(resize.neighborMin, resize.neighborStartWidth);
85
+ const neighborMax = Math.max(resize.neighborMax, resize.neighborStartWidth);
86
+ const width = Math.round(
87
+ clamp(
88
+ resize.startWidth + proposed - resize.tanstackStartWidth,
89
+ Math.max(min, total - neighborMax),
90
+ Math.min(max, total - neighborMin)
91
+ )
92
+ );
93
+ return { ...next, [resize.columnId]: width, [resize.neighborId]: total - width };
93
94
  }
94
95
  function buildDistributedColumnWidths(args) {
95
96
  const {
96
97
  table,
97
98
  hasSelection,
98
- hasContextMenu = false,
99
+ hasContextMenu,
100
+ contextMenuReservePx = ROW_ACTIONS_RESERVE_PX,
99
101
  defaultMinPx,
100
102
  columnSizing,
101
103
  availableWidth
102
104
  } = args;
103
- const leaf = table.getVisibleLeafColumns();
104
- const selectionWidth = hasSelection ? SELECTION_COLUMN_PX : 0;
105
- const contextMenuWidth = hasContextMenu ? 0 : 0;
106
- const tracks = leaf.map((c) => {
105
+ const visibleColumns = table.getVisibleLeafColumns();
106
+ const tracks = visibleColumns.map((c, index) => {
107
107
  var _a, _b, _c, _d;
108
108
  const def = c.columnDef;
109
109
  const meta = (_a = def.meta) != null ? _a : {};
110
110
  const min = Math.max(1, Number((_b = def.minSize) != null ? _b : defaultMinPx));
111
111
  const max = def.maxSize != null ? Math.max(min, Number(def.maxSize)) : void 0;
112
112
  const resizedPxRaw = columnSizing[c.id];
113
- const resizedPx = resizedPxRaw != null ? Math.round(clamp(Number(resizedPxRaw), min, max)) : void 0;
114
- if (resizedPx != null) {
115
- return {
116
- id: c.id,
117
- width: resizedPx,
118
- weight: 0,
119
- min,
120
- max: resizedPx,
121
- fixed: true
122
- };
123
- }
113
+ const reserve = hasContextMenu && index === visibleColumns.length - 1 ? contextMenuReservePx : 0;
114
+ const fixedWidth = resizedPxRaw != null ? Math.round(clamp(Number(resizedPxRaw), min + reserve, (max != null ? max : Infinity) + reserve)) : void 0;
124
115
  const rawWeight = Number((_d = (_c = meta.weight) != null ? _c : def.size) != null ? _d : DEFAULT_COLUMN_PX);
125
116
  const weight = Number.isFinite(rawWeight) && rawWeight > 0 ? rawWeight : DEFAULT_COLUMN_PX;
126
- return {
127
- id: c.id,
128
- width: min,
129
- weight,
130
- min,
131
- max,
132
- fixed: false
133
- };
117
+ return { id: c.id, min, max, weight, fixedWidth };
118
+ });
119
+ return distributeColumnWidths({
120
+ tracks,
121
+ hasSelection,
122
+ hasContextMenu,
123
+ contextMenuReservePx,
124
+ availableWidth
134
125
  });
135
- const lastTrack = tracks[tracks.length - 1];
136
- if (hasContextMenu && lastTrack && !lastTrack.fixed) {
137
- lastTrack.min += ROW_ACTIONS_RESERVE_PX;
138
- lastTrack.width += ROW_ACTIONS_RESERVE_PX;
139
- if (lastTrack.max != null) lastTrack.max += ROW_ACTIONS_RESERVE_PX;
140
- }
141
- const fixedWidth = selectionWidth + contextMenuWidth + tracks.reduce((sum, track) => sum + track.width, 0);
142
- let remaining = Math.max(0, availableWidth - fixedWidth);
143
- let active = tracks.filter(
144
- (track) => !track.fixed && (track.max == null || track.width < track.max)
145
- );
146
- while (remaining > 0.5 && active.length > 0) {
147
- const totalWeight = active.reduce((sum, track) => sum + track.weight, 0);
148
- if (totalWeight <= 0) break;
149
- let consumed = 0;
150
- const nextActive = [];
151
- for (const track of active) {
152
- const share = remaining * track.weight / totalWeight;
153
- const maxGrow = track.max == null ? share : Math.max(0, track.max - track.width);
154
- const growth = Math.min(share, maxGrow);
155
- track.width += growth;
156
- consumed += growth;
157
- if (track.max == null || track.width < track.max - 0.5) {
158
- nextActive.push(track);
159
- }
160
- }
161
- if (consumed <= 0.5) break;
162
- remaining -= consumed;
163
- active = nextActive;
164
- }
165
- const flexible = tracks.filter((track) => !track.fixed);
166
- if (remaining > 0.5 && flexible.length > 0 && flexible.every((track) => track.max != null)) {
167
- const stretchable = flexible.filter((track) => track.max > track.min);
168
- const totalWeight = stretchable.reduce((sum, track) => sum + track.weight, 0);
169
- if (totalWeight > 0) {
170
- for (const track of stretchable) {
171
- track.width += remaining * track.weight / totalWeight;
172
- }
173
- }
174
- }
175
- const floatTrackTotal = tracks.reduce((sum, track) => sum + track.width, 0);
176
- const maxTrackWidth = availableWidth - selectionWidth - contextMenuWidth;
177
- const targetTrackWidth = Math.min(
178
- floatTrackTotal <= maxTrackWidth ? Math.floor(floatTrackTotal) : Math.ceil(floatTrackTotal),
179
- maxTrackWidth
180
- );
181
- const widths = toIntegerTrackWidths(tracks, targetTrackWidth);
182
- const totalWidth = selectionWidth + contextMenuWidth + Object.values(widths).reduce((sum, width) => sum + width, 0);
183
- return {
184
- selectionWidth: hasSelection ? SELECTION_COLUMN_PX : void 0,
185
- contextMenuWidth: hasContextMenu ? 0 : void 0,
186
- widths,
187
- totalWidth,
188
- availableWidth,
189
- shouldOverflow: totalWidth > availableWidth
190
- };
191
126
  }
192
127
 
193
- export { buildColumnVisibilityFromVisibleIds, buildDistributedColumnWidths, getColumnId, getSortPropsFromSorting, mapDefsToColumnItems, sortingEqual };
128
+ export { applyNeighborResize, buildColumnVisibilityFromVisibleIds, buildDistributedColumnWidths, getColumnId, getSortPropsFromSorting, mapDefsToColumnItems, sortingEqual };
@@ -6,6 +6,7 @@ var lucideReact = require('lucide-react');
6
6
  var react = require('react');
7
7
  var Menu = require('../../components/menu/Menu');
8
8
  var Popover = require('../../components/popover/Popover');
9
+ var ScrollFade = require('../../components/scroll-fade/ScrollFade');
9
10
  var usePersistentState = require('../../hooks/usePersistentState');
10
11
  var tokenizeXml = require('./tokenizeXml');
11
12
  var styles = require('./XmlViewer.module.css');
@@ -72,10 +73,10 @@ function XmlViewer({
72
73
  className: [styles__default.default.viewer, borderless ? styles__default.default.borderless : ""].filter(Boolean).join(" "),
73
74
  "data-appearance": currentAppearance,
74
75
  children: [
75
- /* @__PURE__ */ jsxRuntime.jsx(
76
- "div",
76
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.toolbar, children: /* @__PURE__ */ jsxRuntime.jsx(
77
+ "span",
77
78
  {
78
- className: styles__default.default.toolbar,
79
+ className: styles__default.default.toolbarButton,
79
80
  "data-surface": currentAppearance === "dark" ? "inverse" : void 0,
80
81
  children: /* @__PURE__ */ jsxRuntime.jsx(
81
82
  Popover.Popover,
@@ -156,10 +157,11 @@ function XmlViewer({
156
157
  }
157
158
  )
158
159
  }
159
- ),
160
- /* @__PURE__ */ jsxRuntime.jsx(
160
+ ) }),
161
+ /* @__PURE__ */ jsxRuntime.jsx(ScrollFade.ScrollFade, { children: (scrollRef) => /* @__PURE__ */ jsxRuntime.jsxs(
161
162
  "pre",
162
163
  {
164
+ ref: scrollRef,
163
165
  className: [
164
166
  styles__default.default.codePane,
165
167
  styles__default.default[size],
@@ -168,12 +170,15 @@ function XmlViewer({
168
170
  ].filter(Boolean).join(" "),
169
171
  tabIndex: 0,
170
172
  onDoubleClick: handleDoubleClick,
171
- children: /* @__PURE__ */ jsxRuntime.jsx("code", { ref: codeRef, children: viewMode === "raw" ? xml : tokens.map((token, index) => {
172
- const tokenClassName = TOKEN_CLASS[token.type];
173
- return tokenClassName ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: tokenClassName, children: token.value }, index) : token.value;
174
- }) })
173
+ children: [
174
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.toolbarSpacer, "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(Button.Button, { variant: "inline", tabIndex: -1, children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { customIcon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Settings2, {}) }) }) }),
175
+ /* @__PURE__ */ jsxRuntime.jsx("code", { ref: codeRef, children: viewMode === "raw" ? xml : tokens.map((token, index) => {
176
+ const tokenClassName = TOKEN_CLASS[token.type];
177
+ return tokenClassName ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: tokenClassName, children: token.value }, index) : token.value;
178
+ }) })
179
+ ]
175
180
  }
176
- )
181
+ ) })
177
182
  ]
178
183
  }
179
184
  );
@@ -16,7 +16,7 @@ export interface XmlViewerProps {
16
16
  */
17
17
  storageKey?: string;
18
18
  /**
19
- * Removes the outer border, shadow and corner radius, so the viewer sits
19
+ * Removes the outer border, shadow, corner radius and padding, so the viewer sits
20
20
  * flush in a container that already provides its own framing (e.g. a
21
21
  * modal).
22
22
  */
@@ -4,6 +4,7 @@ import { TextWrap, ListTree, Code2, Sun, Moon, ClipboardCopy, Settings2 } from '
4
4
  import { useMemo, useRef } from 'react';
5
5
  import { Menu } from '../../components/menu/Menu';
6
6
  import { Popover } from '../../components/popover/Popover';
7
+ import { ScrollFade } from '../../components/scroll-fade/ScrollFade';
7
8
  import { usePersistentState } from '../../hooks/usePersistentState';
8
9
  import { tokenizeXml } from './tokenizeXml';
9
10
  import styles from './XmlViewer.module.css';
@@ -66,10 +67,10 @@ function XmlViewer({
66
67
  className: [styles.viewer, borderless ? styles.borderless : ""].filter(Boolean).join(" "),
67
68
  "data-appearance": currentAppearance,
68
69
  children: [
69
- /* @__PURE__ */ jsx(
70
- "div",
70
+ /* @__PURE__ */ jsx("div", { className: styles.toolbar, children: /* @__PURE__ */ jsx(
71
+ "span",
71
72
  {
72
- className: styles.toolbar,
73
+ className: styles.toolbarButton,
73
74
  "data-surface": currentAppearance === "dark" ? "inverse" : void 0,
74
75
  children: /* @__PURE__ */ jsx(
75
76
  Popover,
@@ -150,10 +151,11 @@ function XmlViewer({
150
151
  }
151
152
  )
152
153
  }
153
- ),
154
- /* @__PURE__ */ jsx(
154
+ ) }),
155
+ /* @__PURE__ */ jsx(ScrollFade, { children: (scrollRef) => /* @__PURE__ */ jsxs(
155
156
  "pre",
156
157
  {
158
+ ref: scrollRef,
157
159
  className: [
158
160
  styles.codePane,
159
161
  styles[size],
@@ -162,12 +164,15 @@ function XmlViewer({
162
164
  ].filter(Boolean).join(" "),
163
165
  tabIndex: 0,
164
166
  onDoubleClick: handleDoubleClick,
165
- children: /* @__PURE__ */ jsx("code", { ref: codeRef, children: viewMode === "raw" ? xml : tokens.map((token, index) => {
166
- const tokenClassName = TOKEN_CLASS[token.type];
167
- return tokenClassName ? /* @__PURE__ */ jsx("span", { className: tokenClassName, children: token.value }, index) : token.value;
168
- }) })
167
+ children: [
168
+ /* @__PURE__ */ jsx("span", { className: styles.toolbarSpacer, "aria-hidden": "true", children: /* @__PURE__ */ jsx(Button, { variant: "inline", tabIndex: -1, children: /* @__PURE__ */ jsx(Icon, { customIcon: /* @__PURE__ */ jsx(Settings2, {}) }) }) }),
169
+ /* @__PURE__ */ jsx("code", { ref: codeRef, children: viewMode === "raw" ? xml : tokens.map((token, index) => {
170
+ const tokenClassName = TOKEN_CLASS[token.type];
171
+ return tokenClassName ? /* @__PURE__ */ jsx("span", { className: tokenClassName, children: token.value }, index) : token.value;
172
+ }) })
173
+ ]
169
174
  }
170
- )
175
+ ) })
171
176
  ]
172
177
  }
173
178
  );
@@ -2,6 +2,7 @@
2
2
  --xml-pane-bg: var(--color-bg-inverse);
3
3
  --xml-pane-border: color-mix(in oklab, var(--color-fg-inverse) 12%, transparent);
4
4
  --xml-pane-text: var(--color-fg-inverse);
5
+ --scroll-fade-color: var(--xml-pane-bg);
5
6
 
6
7
  --xml-tag: color-mix(in oklab, var(--color-fg-inverse) 66%, var(--color-syntax-accent) 34%);
7
8
  --xml-attr-name: color-mix(in oklab, var(--color-fg-inverse) 50%, var(--color-syntax-accent) 50%);
@@ -14,6 +15,10 @@
14
15
  --xml-punctuation: color-mix(in oklab, var(--color-fg-inverse) 60%, transparent);
15
16
 
16
17
  position: relative;
18
+ display: flex;
19
+ flex-direction: column;
20
+ /* In a parent with a definite height, scroll inside instead of overflowing it. */
21
+ max-height: 100%;
17
22
  background: var(--xml-pane-bg);
18
23
  border: var(--border-width-thin) solid var(--xml-pane-border);
19
24
  border-radius: var(--border-radius-default);
@@ -41,11 +46,61 @@
41
46
  box-shadow: none;
42
47
  }
43
48
 
49
+ .borderless .codePane {
50
+ padding: 0;
51
+ }
52
+
53
+ /* Zero-height sticky row: sticks within the nearest scrolling ancestor, which may be outside the viewer. */
44
54
  .toolbar {
45
- position: absolute;
46
- top: var(--spacing-xs);
47
- right: var(--spacing-xs);
55
+ position: sticky;
56
+ top: 0;
48
57
  z-index: 1;
58
+ display: flex;
59
+ justify-content: flex-end;
60
+ flex: none;
61
+ height: 0;
62
+ }
63
+
64
+ .toolbar,
65
+ .toolbarSpacer {
66
+ font-size: var(--font-size-md);
67
+ line-height: normal;
68
+ white-space: normal;
69
+ }
70
+
71
+ .toolbarButton {
72
+ display: block;
73
+ margin: var(--spacing-xs) var(--spacing-xs) 0 0;
74
+ border-radius: var(--border-radius-default);
75
+ /* Opaque so scrolled text doesn't show through once it's stuck. */
76
+ background: var(--xml-pane-bg);
77
+ }
78
+
79
+ .toolbarSpacer {
80
+ float: right;
81
+ visibility: hidden;
82
+ margin-block-start: calc(var(--spacing-xs) - var(--spacing-sm));
83
+ margin-inline: var(--spacing-xs) calc(var(--spacing-xs) - var(--spacing-sm));
84
+ }
85
+
86
+ .borderless .toolbarSpacer {
87
+ margin: 0;
88
+ padding: var(--spacing-xs) var(--spacing-xs) 0;
89
+ }
90
+
91
+ /* While the pane scrolls vertically, keep the button clear of its scrollbar
92
+ (overlay scrollbars take no space, so nothing else moves it). */
93
+ .viewer:has([data-scrollable-y='true']) .toolbarButton {
94
+ margin-inline-end: var(--spacing-md);
95
+ }
96
+
97
+ .viewer:has([data-scrollable-y='true']) .toolbarSpacer {
98
+ margin-inline-end: calc(var(--spacing-md) - var(--spacing-sm));
99
+ }
100
+
101
+ .viewer.borderless:has([data-scrollable-y='true']) .toolbarSpacer {
102
+ margin-inline-end: 0;
103
+ padding-inline-end: var(--spacing-md);
49
104
  }
50
105
 
51
106
  .codePane {
@@ -0,0 +1,71 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ var react = require('react');
5
+
6
+ const NONE = {
7
+ top: false,
8
+ bottom: false,
9
+ left: false,
10
+ right: false,
11
+ scrollbarWidth: 0,
12
+ scrollbarHeight: 0
13
+ };
14
+ const THRESHOLD_PX = 1;
15
+ function measureScrollbars(el) {
16
+ const style = getComputedStyle(el);
17
+ const borderX = parseFloat(style.borderLeftWidth) + parseFloat(style.borderRightWidth) || 0;
18
+ const borderY = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth) || 0;
19
+ return {
20
+ scrollbarWidth: Math.max(0, el.offsetWidth - el.clientWidth - borderX),
21
+ scrollbarHeight: Math.max(0, el.offsetHeight - el.clientHeight - borderY)
22
+ };
23
+ }
24
+ function measure(el, scrollbars) {
25
+ const { scrollTop, scrollLeft, scrollHeight, scrollWidth, clientHeight, clientWidth } = el;
26
+ return {
27
+ top: scrollTop > THRESHOLD_PX,
28
+ bottom: scrollHeight - clientHeight - scrollTop > THRESHOLD_PX,
29
+ left: scrollLeft > THRESHOLD_PX,
30
+ right: scrollWidth - clientWidth - scrollLeft > THRESHOLD_PX,
31
+ ...scrollbars
32
+ };
33
+ }
34
+ function sameOverflow(a, b) {
35
+ return Object.keys(a).every((key) => a[key] === b[key]);
36
+ }
37
+ function useScrollOverflow(el) {
38
+ const [overflow, setOverflow] = react.useState(NONE);
39
+ react.useLayoutEffect(() => {
40
+ if (!el) return;
41
+ let scrollbars = measureScrollbars(el);
42
+ const update = () => {
43
+ const next = measure(el, scrollbars);
44
+ setOverflow((prev) => sameOverflow(prev, next) ? prev : next);
45
+ };
46
+ const updateLayout = () => {
47
+ scrollbars = measureScrollbars(el);
48
+ update();
49
+ };
50
+ update();
51
+ el.addEventListener("scroll", update, { passive: true });
52
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(updateLayout) : null;
53
+ resizeObserver == null ? void 0 : resizeObserver.observe(el);
54
+ const mutationObserver = typeof MutationObserver !== "undefined" ? new MutationObserver(updateLayout) : null;
55
+ mutationObserver == null ? void 0 : mutationObserver.observe(el, {
56
+ subtree: true,
57
+ childList: true,
58
+ characterData: true,
59
+ attributes: true,
60
+ attributeFilter: ["class", "style"]
61
+ });
62
+ return () => {
63
+ el.removeEventListener("scroll", update);
64
+ resizeObserver == null ? void 0 : resizeObserver.disconnect();
65
+ mutationObserver == null ? void 0 : mutationObserver.disconnect();
66
+ };
67
+ }, [el]);
68
+ return el ? overflow : NONE;
69
+ }
70
+
71
+ exports.useScrollOverflow = useScrollOverflow;