@script-it/json-views-react 0.1.0-beta.1

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 (172) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +301 -0
  3. package/THIRD_PARTY_NOTICES.txt +4917 -0
  4. package/dist/browser-device.d.ts +21 -0
  5. package/dist/browser-device.js +22 -0
  6. package/dist/file-content-edit-state.d.ts +10 -0
  7. package/dist/file-content-edit-state.js +1 -0
  8. package/dist/file-content-view.d.ts +8 -0
  9. package/dist/file-content-view.js +7 -0
  10. package/dist/index.d.ts +11 -0
  11. package/dist/index.js +8 -0
  12. package/dist/json/copy-json.d.ts +15 -0
  13. package/dist/json/copy-json.js +52 -0
  14. package/dist/json/html-preview.d.ts +5 -0
  15. package/dist/json/html-preview.js +12 -0
  16. package/dist/json/html-view/native-control.d.ts +44 -0
  17. package/dist/json/html-view/native-control.js +96 -0
  18. package/dist/json/html-view/settings.d.ts +2 -0
  19. package/dist/json/html-view/settings.js +58 -0
  20. package/dist/json/html-view/template.d.ts +12 -0
  21. package/dist/json/html-view/template.js +55 -0
  22. package/dist/json/html-view.d.ts +9 -0
  23. package/dist/json/html-view.js +163 -0
  24. package/dist/json/json-content.d.ts +61 -0
  25. package/dist/json/json-content.js +433 -0
  26. package/dist/json/json-source-editor.d.ts +36 -0
  27. package/dist/json/json-source-editor.js +203 -0
  28. package/dist/json/json-view.d.ts +3 -0
  29. package/dist/json/json-view.js +322 -0
  30. package/dist/json/kanban-view.d.ts +20 -0
  31. package/dist/json/kanban-view.js +328 -0
  32. package/dist/json/long-text.d.ts +3 -0
  33. package/dist/json/long-text.js +6 -0
  34. package/dist/json/record-view.d.ts +38 -0
  35. package/dist/json/record-view.js +85 -0
  36. package/dist/json/row-mutations.d.ts +7 -0
  37. package/dist/json/row-mutations.js +93 -0
  38. package/dist/json/schema-value.d.ts +57 -0
  39. package/dist/json/schema-value.js +294 -0
  40. package/dist/json/source-default.d.ts +1 -0
  41. package/dist/json/source-default.js +1 -0
  42. package/dist/json/source-edit-capabilities.d.ts +8 -0
  43. package/dist/json/source-edit-capabilities.js +22 -0
  44. package/dist/json/source-literals.d.ts +15 -0
  45. package/dist/json/source-literals.js +24 -0
  46. package/dist/json/source-recovery.d.ts +32 -0
  47. package/dist/json/source-recovery.js +38 -0
  48. package/dist/json/source-selection.d.ts +3 -0
  49. package/dist/json/source-selection.js +16 -0
  50. package/dist/json/table-view.d.ts +20 -0
  51. package/dist/json/table-view.js +300 -0
  52. package/dist/json/use-kanban-card-drag.d.ts +13 -0
  53. package/dist/json/use-kanban-card-drag.js +255 -0
  54. package/dist/json/use-kanban-column-drag.d.ts +14 -0
  55. package/dist/json/use-kanban-column-drag.js +179 -0
  56. package/dist/json/view-diagnostics.d.ts +4 -0
  57. package/dist/json/view-diagnostics.js +26 -0
  58. package/dist/json/view-model.d.ts +27 -0
  59. package/dist/json/view-model.js +180 -0
  60. package/dist/json/view-save-status.d.ts +12 -0
  61. package/dist/json/view-save-status.js +38 -0
  62. package/dist/json/view-settings.d.ts +36 -0
  63. package/dist/json/view-settings.js +661 -0
  64. package/dist/json/view-types.d.ts +47 -0
  65. package/dist/json/view-types.js +4 -0
  66. package/dist/json/viewers/general/general-json-view.d.ts +46 -0
  67. package/dist/json/viewers/general/general-json-view.js +163 -0
  68. package/dist/json/viewers/general/json-flatten.d.ts +33 -0
  69. package/dist/json/viewers/general/json-flatten.js +51 -0
  70. package/dist/json/viewers/general/json-node-view.d.ts +43 -0
  71. package/dist/json/viewers/general/json-node-view.js +215 -0
  72. package/dist/lib/clipboard.d.ts +1 -0
  73. package/dist/lib/clipboard.js +24 -0
  74. package/dist/lib/cn.d.ts +2 -0
  75. package/dist/lib/cn.js +5 -0
  76. package/dist/lib/file-data.d.ts +7 -0
  77. package/dist/lib/file-data.js +5 -0
  78. package/dist/lib/floating-portal-selectors.d.ts +4 -0
  79. package/dist/lib/floating-portal-selectors.js +4 -0
  80. package/dist/lib/time.d.ts +24 -0
  81. package/dist/lib/time.js +60 -0
  82. package/dist/lib/ui-size.d.ts +28 -0
  83. package/dist/lib/ui-size.js +39 -0
  84. package/dist/lib/use-anchored-position.d.ts +5 -0
  85. package/dist/lib/use-anchored-position.js +65 -0
  86. package/dist/lib/use-dismiss.d.ts +31 -0
  87. package/dist/lib/use-dismiss.js +65 -0
  88. package/dist/primitives/boolean-toggle.d.ts +10 -0
  89. package/dist/primitives/boolean-toggle.js +11 -0
  90. package/dist/primitives/button.d.ts +11 -0
  91. package/dist/primitives/button.js +34 -0
  92. package/dist/primitives/checkbox.d.ts +4 -0
  93. package/dist/primitives/checkbox.js +8 -0
  94. package/dist/primitives/choice-control.d.ts +29 -0
  95. package/dist/primitives/choice-control.js +131 -0
  96. package/dist/primitives/dialog.d.ts +29 -0
  97. package/dist/primitives/dialog.js +25 -0
  98. package/dist/primitives/dropdown-menu.d.ts +37 -0
  99. package/dist/primitives/dropdown-menu.js +39 -0
  100. package/dist/primitives/inline-feedback-action.d.ts +13 -0
  101. package/dist/primitives/inline-feedback-action.js +6 -0
  102. package/dist/primitives/menu-select.d.ts +12 -0
  103. package/dist/primitives/menu-select.js +7 -0
  104. package/dist/primitives/multi-select.d.ts +18 -0
  105. package/dist/primitives/multi-select.js +7 -0
  106. package/dist/primitives/number-input.d.ts +10 -0
  107. package/dist/primitives/number-input.js +8 -0
  108. package/dist/primitives/option-pill.d.ts +13 -0
  109. package/dist/primitives/option-pill.js +50 -0
  110. package/dist/primitives/tabs.d.ts +7 -0
  111. package/dist/primitives/tabs.js +12 -0
  112. package/dist/primitives/textarea.d.ts +4 -0
  113. package/dist/primitives/textarea.js +8 -0
  114. package/dist/primitives/tooltip.d.ts +31 -0
  115. package/dist/primitives/tooltip.js +37 -0
  116. package/dist/standalone.css +113 -0
  117. package/dist/structured-data/atomic-value-editor.d.ts +33 -0
  118. package/dist/structured-data/atomic-value-editor.js +195 -0
  119. package/dist/structured-data/atomic-value.d.ts +3 -0
  120. package/dist/structured-data/atomic-value.js +6 -0
  121. package/dist/structured-data/column-controls.d.ts +21 -0
  122. package/dist/structured-data/column-controls.js +180 -0
  123. package/dist/structured-data/copy-json-action.d.ts +10 -0
  124. package/dist/structured-data/copy-json-action.js +87 -0
  125. package/dist/structured-data/delete-row-action.d.ts +10 -0
  126. package/dist/structured-data/delete-row-action.js +37 -0
  127. package/dist/structured-data/edit-base.d.ts +3 -0
  128. package/dist/structured-data/edit-base.js +8 -0
  129. package/dist/structured-data/filter-value.d.ts +2 -0
  130. package/dist/structured-data/filter-value.js +28 -0
  131. package/dist/structured-data/row-selection-icon.d.ts +9 -0
  132. package/dist/structured-data/row-selection-icon.js +6 -0
  133. package/dist/structured-data/structured-cell-fill-context.d.ts +6 -0
  134. package/dist/structured-data/structured-cell-fill-context.js +7 -0
  135. package/dist/structured-data/structured-data-content.d.ts +1 -0
  136. package/dist/structured-data/structured-data-content.js +1 -0
  137. package/dist/structured-data/table-types.d.ts +108 -0
  138. package/dist/structured-data/table-types.js +1 -0
  139. package/dist/structured-data/use-metadata-persistence.d.ts +15 -0
  140. package/dist/structured-data/use-metadata-persistence.js +18 -0
  141. package/dist/structured-data/use-optimistic-text-document.d.ts +22 -0
  142. package/dist/structured-data/use-optimistic-text-document.js +30 -0
  143. package/dist/structured-data/value-cell.d.ts +15 -0
  144. package/dist/structured-data/value-cell.js +81 -0
  145. package/dist/styles.css +2 -0
  146. package/dist/surface.d.ts +14 -0
  147. package/dist/surface.js +73 -0
  148. package/dist/tabular-data-view.d.ts +5 -0
  149. package/dist/tabular-data-view.js +493 -0
  150. package/dist/viewer-state.d.ts +72 -0
  151. package/dist/viewer-state.js +79 -0
  152. package/dist/widget-registry.d.ts +60 -0
  153. package/dist/widget-registry.js +126 -0
  154. package/dist/widgets/boolean-widget.d.ts +2 -0
  155. package/dist/widgets/boolean-widget.js +15 -0
  156. package/dist/widgets/date-picker-widget.d.ts +18 -0
  157. package/dist/widgets/date-picker-widget.js +265 -0
  158. package/dist/widgets/dropdown-widget.d.ts +10 -0
  159. package/dist/widgets/dropdown-widget.js +19 -0
  160. package/dist/widgets/multi-select-widget.d.ts +11 -0
  161. package/dist/widgets/multi-select-widget.js +46 -0
  162. package/dist/widgets/number-widget.d.ts +2 -0
  163. package/dist/widgets/number-widget.js +19 -0
  164. package/dist/widgets/read-only-field.d.ts +36 -0
  165. package/dist/widgets/read-only-field.js +39 -0
  166. package/dist/widgets/text-widget.d.ts +2 -0
  167. package/dist/widgets/text-widget.js +19 -0
  168. package/dist/widgets/textarea-widget.d.ts +2 -0
  169. package/dist/widgets/textarea-widget.js +34 -0
  170. package/dist/widgets/types.d.ts +53 -0
  171. package/dist/widgets/types.js +1 -0
  172. package/package.json +83 -0
@@ -0,0 +1,24 @@
1
+ export async function copyTextToClipboard(value) {
2
+ if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
3
+ try {
4
+ await navigator.clipboard.writeText(value);
5
+ return;
6
+ }
7
+ catch { /* Fall back when the browser exposes but denies the Clipboard API. */ }
8
+ }
9
+ if (typeof document === 'undefined')
10
+ throw new Error('Clipboard access is unavailable');
11
+ const textarea = document.createElement('textarea');
12
+ textarea.value = value;
13
+ textarea.style.position = 'fixed';
14
+ textarea.style.opacity = '0';
15
+ document.body.appendChild(textarea);
16
+ textarea.select();
17
+ try {
18
+ if (!document.execCommand('copy'))
19
+ throw new Error('Clipboard access was denied');
20
+ }
21
+ finally {
22
+ textarea.remove();
23
+ }
24
+ }
@@ -0,0 +1,2 @@
1
+ import { type ClassValue } from 'clsx';
2
+ export declare function cn(...inputs: ClassValue[]): string;
package/dist/lib/cn.js ADDED
@@ -0,0 +1,5 @@
1
+ import { clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+ export function cn(...inputs) {
4
+ return twMerge(clsx(inputs));
5
+ }
@@ -0,0 +1,7 @@
1
+ export interface FileContentData {
2
+ source: {
3
+ base64: string;
4
+ };
5
+ mimeType?: string | null;
6
+ }
7
+ export declare function fileDataToText(data: FileContentData): string;
@@ -0,0 +1,5 @@
1
+ export function fileDataToText(data) {
2
+ const binary = atob(data.source.base64);
3
+ const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
4
+ return new TextDecoder().decode(bytes);
5
+ }
@@ -0,0 +1,4 @@
1
+ export declare const FLOATING_PORTAL_SELECTORS: {
2
+ readonly radixPopper: "[data-radix-popper-content-wrapper]";
3
+ readonly anchoredPopup: "[data-anchored-popup]";
4
+ };
@@ -0,0 +1,4 @@
1
+ export const FLOATING_PORTAL_SELECTORS = {
2
+ radixPopper: '[data-radix-popper-content-wrapper]',
3
+ anchoredPopup: '[data-anchored-popup]',
4
+ };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Parse a value as an ISO-8601 datetime string. Returns the epoch millis, or
3
+ * null when the value isn't a string in ISO datetime shape.
4
+ */
5
+ export declare function parseIsoDateString(value: unknown): number | null;
6
+ /**
7
+ * Format an epoch-millis timestamp as a readable local date-time, e.g.
8
+ * "Jul 5, 2026, 1:05 PM" (locale-dependent).
9
+ */
10
+ export declare function formatAbsoluteTimestamp(timestamp: number): string;
11
+ /** Compact relative form: "just now", "12s ago", "2m ago", "3h ago", "5d ago",
12
+ * else a local date. Sub-minute rendering is per-surface: the default
13
+ * `'seconds'` shows "12s ago" (with "just now" under 5s) for surfaces where
14
+ * events land moments apart (e.g. a trigger's run history);
15
+ * `'just-now'` collapses everything under a minute for surfaces where
16
+ * second-level churn is noise (e.g. file modification times). */
17
+ export declare function formatRelativeTimeShort(timestamp: number, { subMinute }?: {
18
+ subMinute?: 'seconds' | 'just-now';
19
+ }): string;
20
+ /**
21
+ * Short relative form ("2m ago") for past timestamps, null for future ones —
22
+ * "ago" phrasing makes no sense for a time that hasn't happened yet.
23
+ */
24
+ export declare function formatRelativeTimeShortIfPast(timestamp: number): string | null;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * ISO-8601 datetime shape: `2026-07-05T13:05:56Z`, with optional fractional
3
+ * seconds and either a `Z` or `±HH[:]MM` offset (a timezone-less string parses
4
+ * as local time). Date-only strings and epoch numbers are deliberately NOT
5
+ * matched — bare `2026-07-05` or a 13-digit integer could just as well be an
6
+ * identifier, and misformatting data is worse than leaving it raw.
7
+ */
8
+ const ISO_DATETIME_RE = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?(Z|[+-]\d{2}:?\d{2})?$/;
9
+ /**
10
+ * Parse a value as an ISO-8601 datetime string. Returns the epoch millis, or
11
+ * null when the value isn't a string in ISO datetime shape.
12
+ */
13
+ export function parseIsoDateString(value) {
14
+ if (typeof value !== 'string' || !ISO_DATETIME_RE.test(value))
15
+ return null;
16
+ const ms = Date.parse(value);
17
+ return Number.isNaN(ms) ? null : ms;
18
+ }
19
+ /**
20
+ * Format an epoch-millis timestamp as a readable local date-time, e.g.
21
+ * "Jul 5, 2026, 1:05 PM" (locale-dependent).
22
+ */
23
+ export function formatAbsoluteTimestamp(timestamp) {
24
+ return new Date(timestamp).toLocaleString(undefined, {
25
+ dateStyle: 'medium',
26
+ timeStyle: 'short',
27
+ });
28
+ }
29
+ /** Compact relative form: "just now", "12s ago", "2m ago", "3h ago", "5d ago",
30
+ * else a local date. Sub-minute rendering is per-surface: the default
31
+ * `'seconds'` shows "12s ago" (with "just now" under 5s) for surfaces where
32
+ * events land moments apart (e.g. a trigger's run history);
33
+ * `'just-now'` collapses everything under a minute for surfaces where
34
+ * second-level churn is noise (e.g. file modification times). */
35
+ export function formatRelativeTimeShort(timestamp, { subMinute = 'seconds' } = {}) {
36
+ const now = Date.now();
37
+ const diffSeconds = Math.floor((now - timestamp) / 1000);
38
+ const diffMinutes = Math.floor(diffSeconds / 60);
39
+ const diffHours = Math.floor(diffMinutes / 60);
40
+ const diffDays = Math.floor(diffHours / 24);
41
+ if (diffSeconds < 60) {
42
+ if (subMinute === 'just-now' || diffSeconds < 5)
43
+ return 'just now';
44
+ return `${diffSeconds}s ago`;
45
+ }
46
+ if (diffMinutes < 60)
47
+ return `${diffMinutes}m ago`;
48
+ if (diffHours < 24)
49
+ return `${diffHours}h ago`;
50
+ if (diffDays < 7)
51
+ return `${diffDays}d ago`;
52
+ return new Date(timestamp).toLocaleDateString();
53
+ }
54
+ /**
55
+ * Short relative form ("2m ago") for past timestamps, null for future ones —
56
+ * "ago" phrasing makes no sense for a time that hasn't happened yet.
57
+ */
58
+ export function formatRelativeTimeShortIfPast(timestamp) {
59
+ return timestamp <= Date.now() ? formatRelativeTimeShort(timestamp) : null;
60
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * App-wide UI size: the three stops a user can pick in Settings → Appearance.
3
+ *
4
+ * The CSS side lives in `src/tokens/ui-size.css`, which keys the root
5
+ * font-size off a `data-ui-size` attribute on <html> — that cascade is what
6
+ * scales the whole rem-based UI. This module is the JS mirror: the stop names,
7
+ * the attribute contract, and the numeric multiplier for consumers that need
8
+ * the scale outside CSS (e.g. virtualized row heights). Keep in sync with the
9
+ * token file.
10
+ */
11
+ export declare const UI_SIZES: readonly ["small", "medium", "large"];
12
+ export type UiSize = (typeof UI_SIZES)[number];
13
+ /** Attribute on <html> that `tokens/ui-size.css` keys the root font-size off. */
14
+ export declare const UI_SIZE_ATTRIBUTE = "data-ui-size";
15
+ /** Root font-size multiplier per stop — mirrors `tokens/ui-size.css`. */
16
+ export declare const UI_SIZE_SCALE: Record<UiSize, number>;
17
+ /**
18
+ * Explorer floor width per stop, in px — the JS mirror of the
19
+ * `--explorer-sidebar-min-width` custom property in `tokens/ui-size.css`
20
+ * (which carries the same numbers for CSS-side pins). Kept as its own stop
21
+ * table rather than `UI_SIZE_SCALE × a base`, because the explorer's tab row
22
+ * grows more slowly than the root font-size: each value is that row's
23
+ * measured content plus a few px, so its trailing controls stay snug against
24
+ * the tabs at every stop. Consumed by hosts whose sizing API takes numbers
25
+ * (react-resizable-panels' Panel props). Keep in sync with the token file.
26
+ */
27
+ export declare const EXPLORER_SIDEBAR_MIN_WIDTH_PX: Record<UiSize, number>;
28
+ export declare function isUiSize(value: unknown): value is UiSize;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * App-wide UI size: the three stops a user can pick in Settings → Appearance.
3
+ *
4
+ * The CSS side lives in `src/tokens/ui-size.css`, which keys the root
5
+ * font-size off a `data-ui-size` attribute on <html> — that cascade is what
6
+ * scales the whole rem-based UI. This module is the JS mirror: the stop names,
7
+ * the attribute contract, and the numeric multiplier for consumers that need
8
+ * the scale outside CSS (e.g. virtualized row heights). Keep in sync with the
9
+ * token file.
10
+ */
11
+ export const UI_SIZES = ['small', 'medium', 'large'];
12
+ /** Attribute on <html> that `tokens/ui-size.css` keys the root font-size off. */
13
+ export const UI_SIZE_ATTRIBUTE = 'data-ui-size';
14
+ /** Root font-size multiplier per stop — mirrors `tokens/ui-size.css`. */
15
+ export const UI_SIZE_SCALE = {
16
+ small: 1,
17
+ medium: 1.125,
18
+ large: 1.25,
19
+ };
20
+ /**
21
+ * Explorer floor width per stop, in px — the JS mirror of the
22
+ * `--explorer-sidebar-min-width` custom property in `tokens/ui-size.css`
23
+ * (which carries the same numbers for CSS-side pins). Kept as its own stop
24
+ * table rather than `UI_SIZE_SCALE × a base`, because the explorer's tab row
25
+ * grows more slowly than the root font-size: each value is that row's
26
+ * measured content plus a few px, so its trailing controls stay snug against
27
+ * the tabs at every stop. Consumed by hosts whose sizing API takes numbers
28
+ * (react-resizable-panels' Panel props). Keep in sync with the token file.
29
+ */
30
+ export const EXPLORER_SIDEBAR_MIN_WIDTH_PX = {
31
+ small: 270,
32
+ medium: 294,
33
+ // 318 + 0.5rem (10px at this stop's 20px root), matching the token file's
34
+ // calc(): `large` needs the extra breathing room the others don't.
35
+ large: 328,
36
+ };
37
+ export function isUiSize(value) {
38
+ return typeof value === 'string' && UI_SIZES.includes(value);
39
+ }
@@ -0,0 +1,5 @@
1
+ import { type CSSProperties, type RefObject } from 'react';
2
+ /** Fixed popovers use viewport coordinates and stay within its visible edges. */
3
+ export declare function useAnchoredPosition(enabled: boolean, anchorRef: RefObject<HTMLElement | null>, contentRef: RefObject<HTMLElement | null>, side?: 'bottom' | 'right', align?: 'start' | 'end',
4
+ /** Floors the content at the anchor's width, for an editor that replaces it. */
5
+ matchAnchorWidth?: boolean): CSSProperties;
@@ -0,0 +1,65 @@
1
+ import { useEffect, useLayoutEffect, useState } from 'react';
2
+ const useBrowserLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
3
+ const PADDING = 8;
4
+ const GAP = 4;
5
+ /** Fixed popovers use viewport coordinates and stay within its visible edges. */
6
+ export function useAnchoredPosition(enabled, anchorRef, contentRef, side = 'bottom', align = 'start',
7
+ /** Floors the content at the anchor's width, for an editor that replaces it. */
8
+ matchAnchorWidth = false) {
9
+ const [position, setPosition] = useState();
10
+ const update = () => {
11
+ const anchor = anchorRef.current;
12
+ const content = contentRef.current;
13
+ if (!enabled || !anchor || !content)
14
+ return;
15
+ const local = anchor.getBoundingClientRect();
16
+ const frame = anchor.ownerDocument.defaultView?.frameElement;
17
+ const offset = frame?.getBoundingClientRect();
18
+ const bounds = { left: local.left + (offset?.left ?? 0), right: local.right + (offset?.left ?? 0), top: local.top + (offset?.top ?? 0), bottom: local.bottom + (offset?.top ?? 0) };
19
+ const size = content.getBoundingClientRect();
20
+ const width = Math.min(size.width, window.innerWidth - PADDING * 2);
21
+ const height = Math.min(size.height, window.innerHeight - PADDING * 2);
22
+ const fitsBelow = bounds.bottom + GAP + height <= window.innerHeight - PADDING;
23
+ const fitsRight = bounds.right + GAP + width <= window.innerWidth - PADDING;
24
+ const left = side === 'right'
25
+ ? fitsRight ? bounds.right + GAP : bounds.left - width - GAP
26
+ : align === 'end' ? bounds.right - width : bounds.left;
27
+ const top = side === 'bottom'
28
+ ? fitsBelow ? bounds.bottom + GAP : bounds.top - height - GAP
29
+ : bounds.top;
30
+ const next = {
31
+ left: Math.max(PADDING, Math.min(left, window.innerWidth - width - PADDING)),
32
+ top: Math.max(PADDING, Math.min(top, window.innerHeight - height - PADDING)),
33
+ minWidth: matchAnchorWidth ? Math.min(bounds.right - bounds.left, window.innerWidth - PADDING * 2) : undefined,
34
+ };
35
+ setPosition((current) => current?.left === next.left && current.top === next.top && current.minWidth === next.minWidth ? current : next);
36
+ };
37
+ useBrowserLayoutEffect(update);
38
+ useEffect(() => {
39
+ if (!enabled)
40
+ return;
41
+ const observer = typeof ResizeObserver === 'undefined' ? undefined : new ResizeObserver(update);
42
+ if (anchorRef.current)
43
+ observer?.observe(anchorRef.current);
44
+ if (contentRef.current)
45
+ observer?.observe(contentRef.current);
46
+ anchorRef.current?.ownerDocument.addEventListener('scroll', update, true);
47
+ window.addEventListener('resize', update);
48
+ window.addEventListener('scroll', update, true);
49
+ return () => {
50
+ anchorRef.current?.ownerDocument.removeEventListener('scroll', update, true);
51
+ observer?.disconnect();
52
+ window.removeEventListener('resize', update);
53
+ window.removeEventListener('scroll', update, true);
54
+ };
55
+ }, [enabled, anchorRef, contentRef, side, align, matchAnchorWidth]);
56
+ return {
57
+ position: 'fixed',
58
+ left: position?.left ?? 0,
59
+ top: position?.top ?? 0,
60
+ minWidth: position?.minWidth,
61
+ maxWidth: 'calc(100vw - 16px)',
62
+ maxHeight: 'calc(100vh - 16px)',
63
+ overflowY: 'auto',
64
+ };
65
+ }
@@ -0,0 +1,31 @@
1
+ import { type RefObject } from 'react';
2
+ export interface UseDismissOptions {
3
+ /** When false the listeners are detached (the popup is closed). */
4
+ enabled: boolean;
5
+ /** Called on Escape or a click outside every `insideRefs` subtree. */
6
+ onDismiss: () => void;
7
+ /** Elements whose subtree counts as "inside" — a click within any of them
8
+ * (the popup itself, its anchor row, etc.) does not dismiss. Read live at
9
+ * event time, so the array identity may change between renders without
10
+ * re-subscribing the listeners. */
11
+ insideRefs: Array<RefObject<HTMLElement | null>>;
12
+ /** Delay (ms) before arming click-outside, so the click that opened the popup
13
+ * doesn't immediately dismiss it. Only needed when the opening click lands
14
+ * outside every `insideRefs` subtree. @default 0 */
15
+ armDelayMs?: number;
16
+ /** Also treat clicks inside any portalled `[data-anchored-popup]` layer as
17
+ * inside — for popups that themselves contain nested `AnchoredPopup`s (e.g.
18
+ * a widget dropdown), which render at `document.body` and so escape the
19
+ * `insideRefs` subtree checks. @default false */
20
+ ignoreAnchoredPopups?: boolean;
21
+ /** Host-owned layers that should not dismiss this popup. */
22
+ ignoreSelectors?: readonly string[];
23
+ }
24
+ /**
25
+ * Click-outside + Escape dismissal for portalled popups. Treats Radix popper
26
+ * content (menus, tooltips) — which renders at `document.body`, outside any
27
+ * anchor subtree — as inside, so interacting with a popup's own menu never
28
+ * dismisses it. Shared by `AnchoredPopup` and the inputs-table inline editor so
29
+ * dismissal behaves identically across portalled surfaces.
30
+ */
31
+ export declare function useDismiss({ enabled, onDismiss, insideRefs, armDelayMs, ignoreAnchoredPopups, ignoreSelectors, }: UseDismissOptions): void;
@@ -0,0 +1,65 @@
1
+ import { useEffect, useLayoutEffect, useRef } from 'react';
2
+ import { FLOATING_PORTAL_SELECTORS } from './floating-portal-selectors.js';
3
+ const useBrowserLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
4
+ /**
5
+ * Click-outside + Escape dismissal for portalled popups. Treats Radix popper
6
+ * content (menus, tooltips) — which renders at `document.body`, outside any
7
+ * anchor subtree — as inside, so interacting with a popup's own menu never
8
+ * dismisses it. Shared by `AnchoredPopup` and the inputs-table inline editor so
9
+ * dismissal behaves identically across portalled surfaces.
10
+ */
11
+ export function useDismiss({ enabled, onDismiss, insideRefs, armDelayMs = 0, ignoreAnchoredPopups = false, ignoreSelectors = [], }) {
12
+ // Read `insideRefs` and `onDismiss` live inside the handler so unstable
13
+ // identities each render (a fresh refs array, an inline `onDismiss` closure)
14
+ // don't force the listeners to re-subscribe. Re-subscribing would also clear
15
+ // and restart the arming timer every render, so with `armDelayMs > 0` rapid
16
+ // renders (typing, hovering) could keep `armed` false forever and break
17
+ // click-outside dismissal. Synced in a layout effect — before the browser
18
+ // dispatches the next mousedown, so a click landing between commit and this
19
+ // sync reads the current refs rather than the previous render's. Never
20
+ // written during render, and read only inside the event handlers below.
21
+ const insideRefsRef = useRef(insideRefs);
22
+ const onDismissRef = useRef(onDismiss);
23
+ useBrowserLayoutEffect(() => {
24
+ insideRefsRef.current = insideRefs;
25
+ onDismissRef.current = onDismiss;
26
+ });
27
+ useEffect(() => {
28
+ if (!enabled)
29
+ return;
30
+ let armed = armDelayMs === 0;
31
+ const handlePointerDown = (event) => {
32
+ if (!armed)
33
+ return;
34
+ const target = event.target instanceof Element ? event.target : null;
35
+ if (!target)
36
+ return;
37
+ if (insideRefsRef.current.some((ref) => ref.current?.contains(target)))
38
+ return;
39
+ if (target.closest(FLOATING_PORTAL_SELECTORS.radixPopper))
40
+ return;
41
+ if (ignoreAnchoredPopups && target.closest(FLOATING_PORTAL_SELECTORS.anchoredPopup))
42
+ return;
43
+ if (ignoreSelectors.some((selector) => target.closest(selector)))
44
+ return;
45
+ onDismissRef.current();
46
+ };
47
+ const handleKeyDown = (event) => {
48
+ if (event.key === 'Escape')
49
+ onDismissRef.current();
50
+ };
51
+ const timerId = armDelayMs > 0
52
+ ? window.setTimeout(() => {
53
+ armed = true;
54
+ }, armDelayMs)
55
+ : undefined;
56
+ document.addEventListener('mousedown', handlePointerDown);
57
+ document.addEventListener('keydown', handleKeyDown);
58
+ return () => {
59
+ if (timerId !== undefined)
60
+ window.clearTimeout(timerId);
61
+ document.removeEventListener('mousedown', handlePointerDown);
62
+ document.removeEventListener('keydown', handleKeyDown);
63
+ };
64
+ }, [enabled, armDelayMs, ignoreAnchoredPopups, ignoreSelectors.join('\n')]);
65
+ }
@@ -0,0 +1,10 @@
1
+ import * as React from 'react';
2
+ export interface BooleanToggleProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
3
+ value: boolean;
4
+ onChange: (next: boolean) => void;
5
+ disabled?: boolean;
6
+ trueLabel?: string;
7
+ falseLabel?: string;
8
+ }
9
+ declare const BooleanToggle: React.ForwardRefExoticComponent<BooleanToggleProps & React.RefAttributes<HTMLDivElement>>;
10
+ export { BooleanToggle };
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import { cn } from '../lib/cn.js';
4
+ const BooleanToggle = React.forwardRef(({ className, value, onChange, disabled = false, trueLabel = 'True', falseLabel = 'False', ...props }, ref) => {
5
+ const buttonClass = (selected) => cn('rounded-md border px-2.5 py-1 text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-50', selected
6
+ ? 'border-primary bg-primary text-primary-foreground'
7
+ : 'border-border bg-card text-foreground hover:bg-accent');
8
+ return (_jsxs("div", { ref: ref, className: cn('flex items-center gap-2', className), ...props, children: [_jsx("button", { type: "button", onClick: () => onChange(true), disabled: disabled, "aria-pressed": value, className: buttonClass(value), children: trueLabel }), _jsx("button", { type: "button", onClick: () => onChange(false), disabled: disabled, "aria-pressed": !value, className: buttonClass(!value), children: falseLabel })] }));
9
+ });
10
+ BooleanToggle.displayName = 'BooleanToggle';
11
+ export { BooleanToggle };
@@ -0,0 +1,11 @@
1
+ import * as React from 'react';
2
+ import { type VariantProps } from 'class-variance-authority';
3
+ declare const buttonVariants: (props?: ({
4
+ variant?: "link" | "destructive" | "default" | "outline" | "secondary" | "ghost" | null | undefined;
5
+ size?: "default" | "icon" | "sm" | "lg" | null | undefined;
6
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
8
+ asChild?: boolean;
9
+ }
10
+ declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
11
+ export { Button, buttonVariants };
@@ -0,0 +1,34 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import { Slot } from '@radix-ui/react-slot';
4
+ import { cva } from 'class-variance-authority';
5
+ import { cn } from '../lib/cn.js';
6
+ const buttonVariants = cva('inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', {
7
+ variants: {
8
+ variant: {
9
+ default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
10
+ destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
11
+ outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
12
+ secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
13
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
14
+ link: 'text-primary underline-offset-4 hover:underline',
15
+ },
16
+ size: {
17
+ default: 'h-9 px-4 py-2',
18
+ sm: 'h-8 rounded-md px-3 text-xs',
19
+ lg: 'h-10 rounded-md px-8',
20
+ icon: 'h-9 w-9',
21
+ },
22
+ },
23
+ defaultVariants: {
24
+ variant: 'default',
25
+ size: 'default',
26
+ },
27
+ });
28
+ const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
29
+ const Comp = asChild ? Slot : 'button';
30
+ return (_jsx(Comp, { className: cn(buttonVariants({ variant, size, className })), ref: ref, ...props }));
31
+ });
32
+ Button.displayName = 'Button';
33
+ // eslint-disable-next-line react-refresh/only-export-components
34
+ export { Button, buttonVariants };
@@ -0,0 +1,4 @@
1
+ import * as React from 'react';
2
+ import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
3
+ declare const Checkbox: React.ForwardRefExoticComponent<Omit<CheckboxPrimitive.CheckboxProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
4
+ export { Checkbox };
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
4
+ import { Check } from 'lucide-react';
5
+ import { cn } from '../lib/cn.js';
6
+ const Checkbox = React.forwardRef(({ className, ...props }, ref) => (_jsx(CheckboxPrimitive.Root, { ref: ref, className: cn('peer h-4 w-4 shrink-0 rounded-sm border border-border focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground', className), ...props, children: _jsx(CheckboxPrimitive.Indicator, { className: cn('flex items-center justify-center text-current'), children: _jsx(Check, { className: "h-3 w-3" }) }) })));
7
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName;
8
+ export { Checkbox };
@@ -0,0 +1,29 @@
1
+ import * as React from 'react';
2
+ export interface ChoiceOption {
3
+ value: string;
4
+ label: string;
5
+ color?: string;
6
+ }
7
+ export type ChoiceControlMode = 'single' | 'multiple';
8
+ export type ChoiceControlProfile = 'plain' | 'pill' | 'pill-cell' | 'menu';
9
+ export interface ChoiceControlProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
10
+ mode: ChoiceControlMode;
11
+ value: string[];
12
+ onValueChange: (next: string[]) => void;
13
+ options: ReadonlyArray<ChoiceOption>;
14
+ profile?: ChoiceControlProfile;
15
+ creatable?: boolean;
16
+ emptyOptionLabel?: string;
17
+ disabled?: boolean;
18
+ readOnly?: boolean;
19
+ invalid?: boolean;
20
+ openOnMount?: boolean;
21
+ /** Lets a menu live inside another dismissible surface without blocking
22
+ * pointer events to that surface. Defaults to true for inline cell editors. */
23
+ nonModal?: boolean;
24
+ /** Ends an edit when the menu is dismissed, independently of saving a choice. */
25
+ onDismiss?: () => void;
26
+ onSelectionBlur?: () => void;
27
+ onOptionColorChange?: (optionValue: string, color: string) => void;
28
+ }
29
+ export declare const ChoiceControl: React.ForwardRefExoticComponent<ChoiceControlProps & React.RefAttributes<HTMLDivElement>>;