@dbcdk/react-components 0.0.197 → 0.0.199

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 (68) hide show
  1. package/dist/components/json-viewer/JsonViewer.module.css +53 -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 -21
  5. package/dist/components/line-viewer/LineViewer.d.ts +1 -1
  6. package/dist/components/line-viewer/LineViewer.js +16 -22
  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 +63 -106
  22. package/dist/components/table/TanstackTable.d.ts +5 -0
  23. package/dist/components/table/TanstackTable.js +66 -106
  24. package/dist/components/table/columnLayout.utils.cjs +146 -0
  25. package/dist/components/table/columnLayout.utils.d.ts +44 -0
  26. package/dist/components/table/columnLayout.utils.js +141 -0
  27. package/dist/components/table/components/TableHeader.cjs +3 -1
  28. package/dist/components/table/components/TableHeader.d.ts +2 -1
  29. package/dist/components/table/components/TableHeader.js +3 -1
  30. package/dist/components/table/components/TableHeaderCell.cjs +29 -19
  31. package/dist/components/table/components/TableHeaderCell.d.ts +2 -1
  32. package/dist/components/table/components/TableHeaderCell.js +29 -19
  33. package/dist/components/table/hooks/useClientPagination.cjs +66 -0
  34. package/dist/components/table/hooks/useClientPagination.d.ts +32 -0
  35. package/dist/components/table/hooks/useClientPagination.js +64 -0
  36. package/dist/components/table/hooks/useDistributedLayout.cjs +44 -0
  37. package/dist/components/table/hooks/useDistributedLayout.d.ts +16 -0
  38. package/dist/components/table/hooks/useDistributedLayout.js +42 -0
  39. package/dist/components/table/hooks/useElementWidth.cjs +40 -0
  40. package/dist/components/table/hooks/useElementWidth.d.ts +7 -0
  41. package/dist/components/table/hooks/useElementWidth.js +38 -0
  42. package/dist/components/table/hooks/useResizeHeaderExtras.cjs +87 -0
  43. package/dist/components/table/hooks/useResizeHeaderExtras.d.ts +17 -0
  44. package/dist/components/table/hooks/useResizeHeaderExtras.js +62 -0
  45. package/dist/components/table/hooks/useUiSorting.cjs +18 -0
  46. package/dist/components/table/hooks/useUiSorting.d.ts +3 -0
  47. package/dist/components/table/hooks/useUiSorting.js +16 -0
  48. package/dist/components/table/table.utils.cjs +24 -2
  49. package/dist/components/table/table.utils.d.ts +40 -2
  50. package/dist/components/table/table.utils.js +20 -2
  51. package/dist/components/table/tanstackTable.utils.cjs +33 -97
  52. package/dist/components/table/tanstackTable.utils.d.ts +24 -8
  53. package/dist/components/table/tanstackTable.utils.js +34 -99
  54. package/dist/components/xml-viewer/XmlViewer.cjs +15 -21
  55. package/dist/components/xml-viewer/XmlViewer.d.ts +1 -1
  56. package/dist/components/xml-viewer/XmlViewer.js +16 -22
  57. package/dist/components/xml-viewer/XmlViewer.module.css +58 -3
  58. package/dist/hooks/useScrollOverflow.cjs +71 -0
  59. package/dist/hooks/useScrollOverflow.d.ts +23 -0
  60. package/dist/hooks/useScrollOverflow.js +69 -0
  61. package/dist/hooks/useTableSettings.cjs +2 -1
  62. package/dist/hooks/useTableSettings.d.ts +1 -0
  63. package/dist/hooks/useTableSettings.js +2 -1
  64. package/dist/index.css +1 -0
  65. package/dist/utils/refs.utils.cjs +13 -0
  66. package/dist/utils/refs.utils.d.ts +7 -0
  67. package/dist/utils/refs.utils.js +11 -0
  68. package/package.json +1 -1
@@ -1,8 +1,9 @@
1
1
  'use client';
2
- import { jsxs, jsx } from 'react/jsx-runtime';
3
- import { Search, X, ArrowDown, ArrowUp, ListTree, Braces, Sun, Moon, ClipboardCopy, Settings2 } from 'lucide-react';
2
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
+ import { Search, X, Settings2, ArrowDown, ArrowUp, ListTree, Braces, Sun, Moon, ClipboardCopy } from 'lucide-react';
4
4
  import { Menu } from '../../components/menu/Menu';
5
5
  import { Popover } from '../../components/popover/Popover';
6
+ import { ScrollFade } from '../../components/scroll-fade/ScrollFade';
6
7
  import styles from './JsonViewer.module.css';
7
8
  import { Button } from '../button/Button';
8
9
  import { Icon } from '../icon/Icon';
@@ -36,164 +37,167 @@ function JsonViewerShell({
36
37
  handleExpandAll,
37
38
  handleCollapseAll
38
39
  } = state;
39
- return /* @__PURE__ */ jsxs(
40
- "section",
40
+ const surface = currentAppearance === "dark" ? "inverse" : void 0;
41
+ const showMetaRow = Boolean(helperText) || viewMode === "tree" && Boolean(normalizedQuery);
42
+ const settingsMenu = /* @__PURE__ */ jsx(
43
+ Popover,
41
44
  {
42
- className: [styles.viewer, borderless ? styles.borderless : "", className].filter(Boolean).join(" "),
43
- "data-appearance": currentAppearance,
44
- children: [
45
- /* @__PURE__ */ jsxs(
46
- "div",
45
+ matchTriggerWidth: false,
46
+ minWidth: "200px",
47
+ trigger: (toggle, _icon, isOpen) => /* @__PURE__ */ jsx(
48
+ Button,
49
+ {
50
+ variant: "inline",
51
+ "aria-label": "Flere handlinger",
52
+ "aria-haspopup": "menu",
53
+ "aria-expanded": isOpen ? true : false,
54
+ onClick: toggle,
55
+ children: /* @__PURE__ */ jsx(Icon, { customIcon: /* @__PURE__ */ jsx(Settings2, { "aria-hidden": "true" }) })
56
+ }
57
+ ),
58
+ children: (close) => /* @__PURE__ */ jsxs(Menu, { children: [
59
+ /* @__PURE__ */ jsx(Menu.Item, { disabled: viewMode === "raw" || Boolean(normalizedQuery), children: /* @__PURE__ */ jsxs(
60
+ "button",
47
61
  {
48
- className: styles.toolbar,
49
- "data-surface": currentAppearance === "dark" ? "inverse" : void 0,
62
+ type: "button",
63
+ onClick: () => {
64
+ handleExpandAll();
65
+ close();
66
+ },
50
67
  children: [
51
- /* @__PURE__ */ jsxs("div", { className: styles.toolbarTop, children: [
52
- viewMode === "tree" ? /* @__PURE__ */ jsxs("label", { htmlFor: inputId, className: styles.searchField, children: [
53
- /* @__PURE__ */ jsx(Search, { className: styles.searchIcon, "aria-hidden": "true" }),
54
- /* @__PURE__ */ jsx(
55
- "input",
56
- {
57
- id: inputId,
58
- type: "search",
59
- value: queryInput,
60
- onChange: (event) => setQueryInput(event.target.value),
61
- placeholder: searchPlaceholder,
62
- className: styles.searchInput
63
- }
64
- ),
65
- queryInput ? /* @__PURE__ */ jsx(
66
- "button",
67
- {
68
- type: "button",
69
- className: styles.clearButton,
70
- "aria-label": "Ryd s\xF8gning",
71
- onClick: () => {
72
- setQueryInput("");
73
- setQuery("");
74
- },
75
- children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
76
- }
77
- ) : null
78
- ] }) : /* @__PURE__ */ jsx("span", {}),
79
- /* @__PURE__ */ jsx("div", { className: styles.actions, children: /* @__PURE__ */ jsx(
80
- Popover,
81
- {
82
- matchTriggerWidth: false,
83
- minWidth: "200px",
84
- trigger: (toggle, _icon, isOpen) => /* @__PURE__ */ jsx(
85
- Button,
86
- {
87
- variant: "inline",
88
- "aria-label": "Flere handlinger",
89
- "aria-haspopup": "menu",
90
- "aria-expanded": isOpen ? true : false,
91
- onClick: toggle,
92
- children: /* @__PURE__ */ jsx(Icon, { customIcon: /* @__PURE__ */ jsx(Settings2, { "aria-hidden": "true" }) })
93
- }
94
- ),
95
- children: (close) => /* @__PURE__ */ jsxs(Menu, { children: [
96
- /* @__PURE__ */ jsx(Menu.Item, { disabled: viewMode === "raw" || Boolean(normalizedQuery), children: /* @__PURE__ */ jsxs(
97
- "button",
98
- {
99
- type: "button",
100
- onClick: () => {
101
- handleExpandAll();
102
- close();
103
- },
104
- children: [
105
- /* @__PURE__ */ jsx(ArrowDown, { "aria-hidden": "true" }),
106
- /* @__PURE__ */ jsx("span", { children: "Fold alle ud" })
107
- ]
108
- }
109
- ) }),
110
- /* @__PURE__ */ jsx(Menu.Item, { disabled: viewMode === "raw" || Boolean(normalizedQuery), children: /* @__PURE__ */ jsxs(
111
- "button",
112
- {
113
- type: "button",
114
- onClick: () => {
115
- handleCollapseAll();
116
- close();
117
- },
118
- children: [
119
- /* @__PURE__ */ jsx(ArrowUp, { "aria-hidden": "true" }),
120
- /* @__PURE__ */ jsx("span", { children: "Fold alle sammen" })
121
- ]
122
- }
123
- ) }),
124
- /* @__PURE__ */ jsx(Menu.Separator, {}),
125
- /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
126
- "button",
127
- {
128
- type: "button",
129
- onClick: () => {
130
- setViewMode((mode) => mode === "raw" ? "tree" : "raw");
131
- close();
132
- },
133
- children: [
134
- viewMode === "raw" ? /* @__PURE__ */ jsx(ListTree, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Braces, { "aria-hidden": "true" }),
135
- /* @__PURE__ */ jsx("span", { children: viewMode === "raw" ? "Vis tr\xE6" : "Vis raw" })
136
- ]
137
- }
138
- ) }),
139
- /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
140
- "button",
141
- {
142
- type: "button",
143
- onClick: () => {
144
- handleToggleAppearance();
145
- close();
146
- },
147
- children: [
148
- currentAppearance === "dark" ? /* @__PURE__ */ jsx(Sun, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Moon, { "aria-hidden": "true" }),
149
- /* @__PURE__ */ jsx("span", { children: currentAppearance === "dark" ? "Lys visning" : "M\xF8rk visning" })
150
- ]
151
- }
152
- ) }),
153
- /* @__PURE__ */ jsx(Menu.Separator, {}),
154
- /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
155
- "button",
156
- {
157
- type: "button",
158
- onClick: () => {
159
- handleCopyAll();
160
- close();
161
- },
162
- children: [
163
- /* @__PURE__ */ jsx(ClipboardCopy, { "aria-hidden": "true" }),
164
- /* @__PURE__ */ jsx("span", { children: "Kopi\xE9r JSON" })
165
- ]
166
- }
167
- ) })
168
- ] })
169
- }
170
- ) })
171
- ] }),
172
- helperText || viewMode === "tree" && normalizedQuery ? /* @__PURE__ */ jsxs("div", { className: styles.metaRow, children: [
173
- helperText ? /* @__PURE__ */ jsx("span", { className: styles.helperText, children: helperText }) : /* @__PURE__ */ jsx("span", {}),
174
- /* @__PURE__ */ jsx("span", { className: styles.statusText, children: viewMode === "tree" && normalizedQuery ? `${matches.size} matchende node${matches.size === 1 ? "" : "r"}` : "" })
175
- ] }) : null
68
+ /* @__PURE__ */ jsx(ArrowDown, { "aria-hidden": "true" }),
69
+ /* @__PURE__ */ jsx("span", { children: "Fold alle ud" })
176
70
  ]
177
71
  }
178
- ),
179
- viewMode === "raw" ? /* @__PURE__ */ jsx(
180
- "pre",
72
+ ) }),
73
+ /* @__PURE__ */ jsx(Menu.Item, { disabled: viewMode === "raw" || Boolean(normalizedQuery), children: /* @__PURE__ */ jsxs(
74
+ "button",
181
75
  {
182
- className: [styles.rawPane, deleted ? styles.deleted : ""].filter(Boolean).join(" "),
183
- style: maxHeight !== void 0 ? { maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight } : void 0,
184
- children: rawJson
76
+ type: "button",
77
+ onClick: () => {
78
+ handleCollapseAll();
79
+ close();
80
+ },
81
+ children: [
82
+ /* @__PURE__ */ jsx(ArrowUp, { "aria-hidden": "true" }),
83
+ /* @__PURE__ */ jsx("span", { children: "Fold alle sammen" })
84
+ ]
185
85
  }
186
- ) : /* @__PURE__ */ jsx(
86
+ ) }),
87
+ /* @__PURE__ */ jsx(Menu.Separator, {}),
88
+ /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
89
+ "button",
90
+ {
91
+ type: "button",
92
+ onClick: () => {
93
+ setViewMode((mode) => mode === "raw" ? "tree" : "raw");
94
+ close();
95
+ },
96
+ children: [
97
+ viewMode === "raw" ? /* @__PURE__ */ jsx(ListTree, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Braces, { "aria-hidden": "true" }),
98
+ /* @__PURE__ */ jsx("span", { children: viewMode === "raw" ? "Vis tr\xE6" : "Vis raw" })
99
+ ]
100
+ }
101
+ ) }),
102
+ /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
103
+ "button",
104
+ {
105
+ type: "button",
106
+ onClick: () => {
107
+ handleToggleAppearance();
108
+ close();
109
+ },
110
+ children: [
111
+ currentAppearance === "dark" ? /* @__PURE__ */ jsx(Sun, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Moon, { "aria-hidden": "true" }),
112
+ /* @__PURE__ */ jsx("span", { children: currentAppearance === "dark" ? "Lys visning" : "M\xF8rk visning" })
113
+ ]
114
+ }
115
+ ) }),
116
+ /* @__PURE__ */ jsx(Menu.Separator, {}),
117
+ /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
118
+ "button",
119
+ {
120
+ type: "button",
121
+ onClick: () => {
122
+ handleCopyAll();
123
+ close();
124
+ },
125
+ children: [
126
+ /* @__PURE__ */ jsx(ClipboardCopy, { "aria-hidden": "true" }),
127
+ /* @__PURE__ */ jsx("span", { children: "Kopi\xE9r JSON" })
128
+ ]
129
+ }
130
+ ) })
131
+ ] })
132
+ }
133
+ );
134
+ return /* @__PURE__ */ jsxs(
135
+ "section",
136
+ {
137
+ className: [styles.viewer, borderless ? styles.borderless : "", className].filter(Boolean).join(" "),
138
+ "data-appearance": currentAppearance,
139
+ children: [
140
+ viewMode === "tree" || showMetaRow ? /* @__PURE__ */ jsxs("div", { className: styles.toolbar, "data-surface": surface, children: [
141
+ viewMode === "tree" ? /* @__PURE__ */ jsxs("div", { className: styles.toolbarTop, children: [
142
+ /* @__PURE__ */ jsxs("label", { htmlFor: inputId, className: styles.searchField, children: [
143
+ /* @__PURE__ */ jsx(Search, { className: styles.searchIcon, "aria-hidden": "true" }),
144
+ /* @__PURE__ */ jsx(
145
+ "input",
146
+ {
147
+ id: inputId,
148
+ type: "search",
149
+ value: queryInput,
150
+ onChange: (event) => setQueryInput(event.target.value),
151
+ placeholder: searchPlaceholder,
152
+ className: styles.searchInput
153
+ }
154
+ ),
155
+ queryInput ? /* @__PURE__ */ jsx(
156
+ "button",
157
+ {
158
+ type: "button",
159
+ className: styles.clearButton,
160
+ "aria-label": "Ryd s\xF8gning",
161
+ onClick: () => {
162
+ setQueryInput("");
163
+ setQuery("");
164
+ },
165
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
166
+ }
167
+ ) : null
168
+ ] }),
169
+ /* @__PURE__ */ jsx("div", { className: styles.actions, children: settingsMenu })
170
+ ] }) : null,
171
+ showMetaRow ? /* @__PURE__ */ jsxs("div", { className: styles.metaRow, children: [
172
+ helperText ? /* @__PURE__ */ jsx("span", { className: styles.helperText, children: helperText }) : /* @__PURE__ */ jsx("span", {}),
173
+ /* @__PURE__ */ jsx("span", { className: styles.statusText, children: viewMode === "tree" && normalizedQuery ? `${matches.size} matchende node${matches.size === 1 ? "" : "r"}` : "" })
174
+ ] }) : null
175
+ ] }) : null,
176
+ viewMode === "raw" ? /* @__PURE__ */ jsxs(Fragment, { children: [
177
+ /* @__PURE__ */ jsx("div", { className: styles.floatingToolbar, children: /* @__PURE__ */ jsx("span", { className: styles.floatingButton, "data-surface": surface, children: settingsMenu }) }),
178
+ /* @__PURE__ */ jsx(ScrollFade, { children: (scrollRef) => /* @__PURE__ */ jsxs(
179
+ "pre",
180
+ {
181
+ ref: scrollRef,
182
+ className: [styles.rawPane, deleted ? styles.deleted : ""].filter(Boolean).join(" "),
183
+ style: maxHeight !== void 0 ? { maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight } : void 0,
184
+ children: [
185
+ /* @__PURE__ */ jsx("span", { className: styles.floatingSpacer, "aria-hidden": "true", children: /* @__PURE__ */ jsx(Button, { variant: "inline", tabIndex: -1, children: /* @__PURE__ */ jsx(Icon, { customIcon: /* @__PURE__ */ jsx(Settings2, {}) }) }) }),
186
+ rawJson
187
+ ]
188
+ }
189
+ ) })
190
+ ] }) : /* @__PURE__ */ jsx(ScrollFade, { scrollRef: treePaneRef, children: (scrollRef) => /* @__PURE__ */ jsx(
187
191
  "div",
188
192
  {
189
193
  className: [styles.treePane, deleted ? styles.deleted : ""].filter(Boolean).join(" "),
190
194
  role: "tree",
191
195
  "aria-label": "JSON-visning",
192
- ref: treePaneRef,
196
+ ref: scrollRef,
193
197
  style: maxHeight !== void 0 ? { maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight } : void 0,
194
198
  children: hasResults ? children : /* @__PURE__ */ jsx("div", { className: styles.emptyState, children: emptySearchText })
195
199
  }
196
- )
200
+ ) })
197
201
  ]
198
202
  }
199
203
  );
@@ -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 styles = require('./LineViewer.module.css');
11
12
  var tokenizeLineFormat = require('./tokenizeLineFormat');
@@ -37,7 +38,6 @@ function LineViewer({
37
38
  borderless = false
38
39
  }) {
39
40
  const tokens = react.useMemo(() => tokenizeLineFormat.tokenizeLineFormat(lines), [lines]);
40
- const codeRef = react.useRef(null);
41
41
  const { value: currentAppearance, setValue: setCurrentAppearance } = usePersistentState.usePersistentState({
42
42
  initialValue: appearance,
43
43
  storageKey: storageKey ? `${storageKey}:appearance` : void 0,
@@ -56,25 +56,16 @@ function LineViewer({
56
56
  const handleCopy = () => {
57
57
  void clipboard_utils.copyToClipboard(lines);
58
58
  };
59
- const handleDoubleClick = () => {
60
- const el = codeRef.current;
61
- const selection = typeof window !== "undefined" ? window.getSelection() : null;
62
- if (!el || !selection) return;
63
- const range = document.createRange();
64
- range.selectNodeContents(el);
65
- selection.removeAllRanges();
66
- selection.addRange(range);
67
- };
68
59
  return /* @__PURE__ */ jsxRuntime.jsxs(
69
60
  "section",
70
61
  {
71
62
  className: [styles__default.default.viewer, borderless ? styles__default.default.borderless : ""].filter(Boolean).join(" "),
72
63
  "data-appearance": currentAppearance,
73
64
  children: [
74
- /* @__PURE__ */ jsxRuntime.jsx(
75
- "div",
65
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.toolbar, children: /* @__PURE__ */ jsxRuntime.jsx(
66
+ "span",
76
67
  {
77
- className: styles__default.default.toolbar,
68
+ className: styles__default.default.toolbarButton,
78
69
  "data-surface": currentAppearance === "dark" ? "inverse" : void 0,
79
70
  children: /* @__PURE__ */ jsxRuntime.jsx(
80
71
  Popover.Popover,
@@ -155,10 +146,11 @@ function LineViewer({
155
146
  }
156
147
  )
157
148
  }
158
- ),
159
- /* @__PURE__ */ jsxRuntime.jsx(
149
+ ) }),
150
+ /* @__PURE__ */ jsxRuntime.jsx(ScrollFade.ScrollFade, { children: (scrollRef) => /* @__PURE__ */ jsxRuntime.jsxs(
160
151
  "pre",
161
152
  {
153
+ ref: scrollRef,
162
154
  className: [
163
155
  styles__default.default.codePane,
164
156
  styles__default.default[size],
@@ -166,13 +158,15 @@ function LineViewer({
166
158
  deleted ? styles__default.default.deleted : ""
167
159
  ].filter(Boolean).join(" "),
168
160
  tabIndex: 0,
169
- onDoubleClick: handleDoubleClick,
170
- children: /* @__PURE__ */ jsxRuntime.jsx("code", { ref: codeRef, children: viewMode === "raw" ? lines : tokens.map((token, index) => {
171
- const tokenClassName = TOKEN_CLASS[token.type];
172
- return tokenClassName ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: tokenClassName, children: token.value }, index) : token.value;
173
- }) })
161
+ children: [
162
+ /* @__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, {}) }) }) }),
163
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: viewMode === "raw" ? lines : tokens.map((token, index) => {
164
+ const tokenClassName = TOKEN_CLASS[token.type];
165
+ return tokenClassName ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: tokenClassName, children: token.value }, index) : token.value;
166
+ }) })
167
+ ]
174
168
  }
175
- )
169
+ ) })
176
170
  ]
177
171
  }
178
172
  );
@@ -16,7 +16,7 @@ export interface LineViewerProps {
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
  */
@@ -1,9 +1,10 @@
1
1
  'use client';
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
3
  import { TextWrap, ListTree, Code2, Sun, Moon, ClipboardCopy, Settings2 } from 'lucide-react';
4
- import { useMemo, useRef } from 'react';
4
+ import { useMemo } 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 styles from './LineViewer.module.css';
9
10
  import { tokenizeLineFormat } from './tokenizeLineFormat';
@@ -31,7 +32,6 @@ function LineViewer({
31
32
  borderless = false
32
33
  }) {
33
34
  const tokens = useMemo(() => tokenizeLineFormat(lines), [lines]);
34
- const codeRef = useRef(null);
35
35
  const { value: currentAppearance, setValue: setCurrentAppearance } = usePersistentState({
36
36
  initialValue: appearance,
37
37
  storageKey: storageKey ? `${storageKey}:appearance` : void 0,
@@ -50,25 +50,16 @@ function LineViewer({
50
50
  const handleCopy = () => {
51
51
  void copyToClipboard(lines);
52
52
  };
53
- const handleDoubleClick = () => {
54
- const el = codeRef.current;
55
- const selection = typeof window !== "undefined" ? window.getSelection() : null;
56
- if (!el || !selection) return;
57
- const range = document.createRange();
58
- range.selectNodeContents(el);
59
- selection.removeAllRanges();
60
- selection.addRange(range);
61
- };
62
53
  return /* @__PURE__ */ jsxs(
63
54
  "section",
64
55
  {
65
56
  className: [styles.viewer, borderless ? styles.borderless : ""].filter(Boolean).join(" "),
66
57
  "data-appearance": currentAppearance,
67
58
  children: [
68
- /* @__PURE__ */ jsx(
69
- "div",
59
+ /* @__PURE__ */ jsx("div", { className: styles.toolbar, children: /* @__PURE__ */ jsx(
60
+ "span",
70
61
  {
71
- className: styles.toolbar,
62
+ className: styles.toolbarButton,
72
63
  "data-surface": currentAppearance === "dark" ? "inverse" : void 0,
73
64
  children: /* @__PURE__ */ jsx(
74
65
  Popover,
@@ -149,10 +140,11 @@ function LineViewer({
149
140
  }
150
141
  )
151
142
  }
152
- ),
153
- /* @__PURE__ */ jsx(
143
+ ) }),
144
+ /* @__PURE__ */ jsx(ScrollFade, { children: (scrollRef) => /* @__PURE__ */ jsxs(
154
145
  "pre",
155
146
  {
147
+ ref: scrollRef,
156
148
  className: [
157
149
  styles.codePane,
158
150
  styles[size],
@@ -160,13 +152,15 @@ function LineViewer({
160
152
  deleted ? styles.deleted : ""
161
153
  ].filter(Boolean).join(" "),
162
154
  tabIndex: 0,
163
- onDoubleClick: handleDoubleClick,
164
- children: /* @__PURE__ */ jsx("code", { ref: codeRef, children: viewMode === "raw" ? lines : tokens.map((token, index) => {
165
- const tokenClassName = TOKEN_CLASS[token.type];
166
- return tokenClassName ? /* @__PURE__ */ jsx("span", { className: tokenClassName, children: token.value }, index) : token.value;
167
- }) })
155
+ children: [
156
+ /* @__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, {}) }) }) }),
157
+ /* @__PURE__ */ jsx("code", { children: viewMode === "raw" ? lines : tokens.map((token, index) => {
158
+ const tokenClassName = TOKEN_CLASS[token.type];
159
+ return tokenClassName ? /* @__PURE__ */ jsx("span", { className: tokenClassName, children: token.value }, index) : token.value;
160
+ }) })
161
+ ]
168
162
  }
169
- )
163
+ ) })
170
164
  ]
171
165
  }
172
166
  );
@@ -2,6 +2,7 @@
2
2
  --line-pane-bg: var(--color-bg-inverse);
3
3
  --line-pane-border: color-mix(in oklab, var(--color-fg-inverse) 12%, transparent);
4
4
  --line-pane-text: var(--color-fg-inverse);
5
+ --scroll-fade-color: var(--line-pane-bg);
5
6
 
6
7
  --line-tag: color-mix(in oklab, var(--color-fg-inverse) 66%, var(--color-syntax-accent) 34%);
7
8
  --line-indicator: color-mix(in oklab, var(--color-fg-inverse) 46%, transparent);
@@ -20,6 +21,10 @@
20
21
  --line-continuation: color-mix(in oklab, var(--color-fg-inverse) 46%, transparent);
21
22
 
22
23
  position: relative;
24
+ display: flex;
25
+ flex-direction: column;
26
+ /* In a parent with a definite height, scroll inside instead of overflowing it. */
27
+ max-height: 100%;
23
28
  background: var(--line-pane-bg);
24
29
  border: var(--border-width-thin) solid var(--line-pane-border);
25
30
  border-radius: var(--border-radius-default);
@@ -49,11 +54,61 @@
49
54
  box-shadow: none;
50
55
  }
51
56
 
57
+ .borderless .codePane {
58
+ padding: 0;
59
+ }
60
+
61
+ /* Zero-height sticky row: sticks within the nearest scrolling ancestor, which may be outside the viewer. */
52
62
  .toolbar {
53
- position: absolute;
54
- top: var(--spacing-xs);
55
- right: var(--spacing-xs);
63
+ position: sticky;
64
+ top: 0;
56
65
  z-index: 1;
66
+ display: flex;
67
+ justify-content: flex-end;
68
+ flex: none;
69
+ height: 0;
70
+ }
71
+
72
+ .toolbar,
73
+ .toolbarSpacer {
74
+ font-size: var(--font-size-md);
75
+ line-height: normal;
76
+ white-space: normal;
77
+ }
78
+
79
+ .toolbarButton {
80
+ display: block;
81
+ margin: var(--spacing-xs) var(--spacing-xs) 0 0;
82
+ border-radius: var(--border-radius-default);
83
+ /* Opaque so scrolled text doesn't show through once it's stuck. */
84
+ background: var(--line-pane-bg);
85
+ }
86
+
87
+ .toolbarSpacer {
88
+ float: right;
89
+ visibility: hidden;
90
+ margin-block-start: calc(var(--spacing-xs) - var(--spacing-sm));
91
+ margin-inline: var(--spacing-xs) calc(var(--spacing-xs) - var(--spacing-sm));
92
+ }
93
+
94
+ .borderless .toolbarSpacer {
95
+ margin: 0;
96
+ padding: var(--spacing-xs) var(--spacing-xs) 0;
97
+ }
98
+
99
+ /* While the pane scrolls vertically, keep the button clear of its scrollbar
100
+ (overlay scrollbars take no space, so nothing else moves it). */
101
+ .viewer:has([data-scrollable-y='true']) .toolbarButton {
102
+ margin-inline-end: var(--spacing-md);
103
+ }
104
+
105
+ .viewer:has([data-scrollable-y='true']) .toolbarSpacer {
106
+ margin-inline-end: calc(var(--spacing-md) - var(--spacing-sm));
107
+ }
108
+
109
+ .viewer.borderless:has([data-scrollable-y='true']) .toolbarSpacer {
110
+ margin-inline-end: 0;
111
+ padding-inline-end: var(--spacing-md);
57
112
  }
58
113
 
59
114
  .codePane {
@@ -19,7 +19,7 @@
19
19
 
20
20
  /* Structural bottom breathing room for all page content, so it doesn't sit
21
21
  flush against the viewport edge. */
22
- --page-layout-content-padding-bottom: var(--spacing-xl);
22
+ --page-layout-content-padding-bottom: var(--spacing-md);
23
23
  }
24
24
 
25
25
  /* Viewport handling:
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var react = require('react');
6
+ var useScrollOverflow = require('../../hooks/useScrollOverflow');
7
+ var refs_utils = require('../../utils/refs.utils');
8
+ var styles = require('./ScrollFade.module.css');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var styles__default = /*#__PURE__*/_interopDefault(styles);
13
+
14
+ function ScrollFade({
15
+ children,
16
+ scrollRef,
17
+ className
18
+ }) {
19
+ const [scrollElement, setScrollElement] = react.useState(null);
20
+ const overflow = useScrollOverflow.useScrollOverflow(scrollElement);
21
+ const ref = react.useMemo(() => refs_utils.mergeRefs(setScrollElement, scrollRef), [scrollRef]);
22
+ return /* @__PURE__ */ jsxRuntime.jsxs(
23
+ "div",
24
+ {
25
+ className: [styles__default.default.frame, className].filter(Boolean).join(" "),
26
+ "data-scrollable-y": overflow.top || overflow.bottom,
27
+ style: {
28
+ "--scroll-fade-scrollbar-width": `${overflow.scrollbarWidth}px`,
29
+ "--scroll-fade-scrollbar-height": `${overflow.scrollbarHeight}px`
30
+ },
31
+ children: [
32
+ children(ref),
33
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.fadeTop, "data-visible": overflow.top, "aria-hidden": "true" }),
34
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.fadeBottom, "data-visible": overflow.bottom, "aria-hidden": "true" }),
35
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.fadeLeft, "data-visible": overflow.left, "aria-hidden": "true" }),
36
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.fadeRight, "data-visible": overflow.right, "aria-hidden": "true" })
37
+ ]
38
+ }
39
+ );
40
+ }
41
+
42
+ exports.ScrollFade = ScrollFade;
@@ -0,0 +1,18 @@
1
+ import type { JSX, ReactNode, Ref } from 'react';
2
+ export interface ScrollFadeProps<E extends HTMLElement = HTMLElement> {
3
+ /**
4
+ * Renders the scroll container. Attach the given ref to the element that
5
+ * scrolls (`overflow: auto`). It fills the frame and shrinks with it.
6
+ */
7
+ children: (scrollRef: (node: E | null) => void) => ReactNode;
8
+ /** Also receives the scroll container, e.g. for a virtualizer. */
9
+ scrollRef?: Ref<E>;
10
+ className?: string;
11
+ }
12
+ /**
13
+ * Fades the edges of a scroll container that hide more content, so overflow is
14
+ * discoverable without restyling the native scrollbars. The fades blend into
15
+ * `--scroll-fade-color` (default: the surface background), which callers set
16
+ * to the background behind the content; `--scroll-fade-size` sets their depth.
17
+ */
18
+ export declare function ScrollFade<E extends HTMLElement = HTMLElement>({ children, scrollRef, className, }: ScrollFadeProps<E>): JSX.Element;