@adecore/ui 0.31.12 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AccentSwatches.js +290 -13
- package/dist/Banner.js +90 -9
- package/dist/Button.js +114 -22
- package/dist/ButtonGroup.js +62 -8
- package/dist/Checkbox.js +73 -4
- package/dist/ChoiceCards.js +205 -11
- package/dist/CloseButton.js +87 -7
- package/dist/ColorSwatch.js +132 -15
- package/dist/ColorWheelPicker.js +305 -71
- package/dist/ColumnResizeHandle.js +43 -8
- package/dist/DisabledReason.js +27 -8
- package/dist/DockShell.js +144 -37
- package/dist/EmptyState.js +95 -6
- package/dist/ErrorBoundary.js +254 -31
- package/dist/Field.js +349 -31
- package/dist/FileIcon.js +67 -6
- package/dist/Icon.js +38 -6
- package/dist/IconButton.js +176 -20
- package/dist/IconPicker.js +344 -73
- package/dist/Input.js +233 -14
- package/dist/Kbd.js +150 -10
- package/dist/LazyBoundary.js +14 -5
- package/dist/ListRow.js +75 -15
- package/dist/Meter.js +108 -5
- package/dist/MiddleTruncate.js +126 -45
- package/dist/PanelEmpty.js +64 -5
- package/dist/PanelHeader.js +95 -13
- package/dist/Pill.js +114 -19
- package/dist/ProjectSwitcher.js +225 -12
- package/dist/PromptDialog.js +352 -37
- package/dist/SectionLabel.js +62 -8
- package/dist/SegmentBar.js +197 -36
- package/dist/Segmented.js +130 -23
- package/dist/Select.js +310 -19
- package/dist/SelectSearch.js +344 -29
- package/dist/Separator.js +35 -4
- package/dist/ShortcutHints.js +146 -76
- package/dist/Skeleton.js +31 -4
- package/dist/Slider.js +172 -10
- package/dist/SlidingColumn.js +194 -34
- package/dist/Spinner.js +100 -3
- package/dist/Stepper.js +232 -11
- package/dist/Surface.js +62 -8
- package/dist/Switch.js +60 -6
- package/dist/TextMenu.js +212 -17
- package/dist/Tile.js +149 -5
- package/dist/Toasts.js +391 -33
- package/dist/Tooltip.js +153 -16
- package/dist/UIProvider.js +62 -15
- package/dist/Waveform.js +362 -93
- package/dist/Wipe.js +259 -34
- package/dist/ZoomControls.js +356 -14
- package/dist/class-name.js +1 -1
- package/dist/clipboard.js +15 -17
- package/dist/context-menu/index.parts.js +1 -1
- package/dist/dialog/index.parts.js +1 -1
- package/dist/dialog/parts.js +378 -29
- package/dist/dialog-layer.js +51 -18
- package/dist/error-boundary.js +19 -19
- package/dist/error-message.js +1 -1
- package/dist/field-context.js +7 -5
- package/dist/file-icon.js +23 -21
- package/dist/file-tree/helpers.js +133 -119
- package/dist/file-tree/index.parts.js +3 -3
- package/dist/file-tree/parts.js +418 -127
- package/dist/file-tree/style.js +1 -1
- package/dist/file-tree/use-file-tree-shift.js +43 -30
- package/dist/file-tree/use-file-tree.js +60 -18
- package/dist/file-tree/use-slots.js +220 -192
- package/dist/floating.js +2 -2
- package/dist/format/datetime.js +88 -38
- package/dist/format/duration.js +59 -53
- package/dist/format/index.js +1 -1
- package/dist/format/locale.js +76 -56
- package/dist/format/number.js +77 -61
- package/dist/format/regions.js +4 -2
- package/dist/format/time-zone.js +1 -1
- package/dist/icon-button-size.js +12 -2
- package/dist/icon-picker.js +70 -61
- package/dist/index.js +57 -57
- package/dist/key-value-list/index.parts.js +1 -1
- package/dist/key-value-list/parts.js +256 -22
- package/dist/lazy-landings.js +10 -10
- package/dist/lazy.js +84 -71
- package/dist/locales/en.js +80 -80
- package/dist/locales/nl.js +80 -80
- package/dist/locales.js +10 -7
- package/dist/menu/index.parts.js +1 -1
- package/dist/menu/parts.js +882 -44
- package/dist/merge-refs.js +8 -9
- package/dist/middle-truncate.js +24 -25
- package/dist/modality.js +27 -19
- package/dist/placement.js +11 -20
- package/dist/platform.js +6 -6
- package/dist/popover/index.parts.js +1 -1
- package/dist/popover/parts.js +173 -11
- package/dist/popup-layer.js +38 -17
- package/dist/prefetch.js +78 -79
- package/dist/preview-card/index.parts.js +1 -1
- package/dist/project-switcher-row.js +153 -12
- package/dist/select-row.js +69 -5
- package/dist/select-shared.js +6 -6
- package/dist/selection.js +16 -16
- package/dist/seti-icons.js +413 -413
- package/dist/settings/ConfirmDialog.js +55 -8
- package/dist/settings/DetailHeader.js +72 -4
- package/dist/settings/MasterDetail.js +119 -7
- package/dist/settings/SettingsDialog.js +820 -80
- package/dist/settings/SettingsRow.js +205 -18
- package/dist/settings/SettingsSection.js +96 -6
- package/dist/settings/index.js +7 -7
- package/dist/settings/target.js +7 -2
- package/dist/shortcut-hints.js +73 -55
- package/dist/shortcut.js +140 -104
- package/dist/step-places.js +1 -1
- package/dist/tabs/index.parts.js +1 -1
- package/dist/tabs/overflow.js +18 -18
- package/dist/tabs/parts.js +780 -166
- package/dist/testing/dedupe.js +82 -75
- package/dist/testing/fake-source.js +12 -12
- package/dist/testing/index.js +1 -1
- package/dist/toast-store.js +65 -46
- package/dist/tree/index.parts.js +1 -1
- package/dist/tree/parts.js +721 -94
- package/dist/tree/shift.js +28 -22
- package/dist/tree/style.js +2 -2
- package/dist/tree/use-tree-shift.js +178 -117
- package/dist/useAsyncAction.js +23 -19
- package/dist/useColumnResize.js +36 -34
- package/dist/useContentSize.js +62 -18
- package/dist/useMeasuredWidth.js +14 -3
- package/dist/useNow.js +73 -30
- package/dist/wipe-split.js +14 -14
- package/dist/workspace/DropPreview.js +60 -7
- package/dist/workspace/SplitView.js +1025 -441
- package/dist/workspace/TabStrip.js +810 -191
- package/dist/workspace/Workspace.js +276 -26
- package/dist/workspace/context.js +8 -3
- package/dist/workspace/geometry.js +173 -150
- package/dist/workspace/model.js +313 -250
- package/dist/workspace/preview.js +61 -61
- package/dist/zoom.js +2 -2
- package/package.json +1 -1
- package/src/ErrorBoundary.tsx +1 -1
- package/src/Field.tsx +4 -3
- package/src/IconPicker.tsx +3 -3
- package/src/LazyBoundary.tsx +2 -0
- package/src/MiddleTruncate.tsx +2 -2
- package/src/SegmentBar.tsx +2 -2
- package/src/Toasts.tsx +3 -1
- package/src/Tooltip.tsx +7 -10
- package/src/Waveform.tsx +3 -3
- package/src/file-tree/parts.tsx +6 -9
- package/src/file-tree/use-file-tree-shift.tsx +6 -9
- package/src/lazy.tsx +4 -0
- package/src/locales/en.ts +4 -4
- package/src/locales/nl.ts +5 -5
- package/src/settings/SettingsDialog.tsx +1 -1
- package/src/tree/parts.tsx +5 -8
- package/src/useContentSize.ts +3 -3
- package/src/workspace/SplitView.tsx +2 -2
- package/src/workspace/TabStrip.tsx +2 -3
- package/src/workspace/Workspace.tsx +2 -5
|
@@ -6,43 +6,50 @@ export const BODY_RADIUS = 4;
|
|
|
6
6
|
/* The stroke is centered on the path, so the path runs half of it inside the box to keep the outer edge on it. */
|
|
7
7
|
export const DROP_STROKE = 2;
|
|
8
8
|
function coordinate(value) {
|
|
9
|
-
|
|
9
|
+
return String(Math.round(value * 100) / 100 + 0);
|
|
10
10
|
}
|
|
11
11
|
/*
|
|
12
12
|
* The preview for a split or a trade: the rectangle as a tab with no height, parked just inside its
|
|
13
13
|
* top left corner so the corner keeps its radius. It is where a tab target grows out of the rectangle.
|
|
14
14
|
*/
|
|
15
|
-
export function rectDropPreview({
|
|
16
|
-
|
|
15
|
+
export function rectDropPreview({
|
|
16
|
+
x,
|
|
17
|
+
y,
|
|
18
|
+
width,
|
|
19
|
+
height
|
|
20
|
+
}) {
|
|
21
|
+
return {
|
|
22
|
+
x,
|
|
23
|
+
y,
|
|
24
|
+
width,
|
|
25
|
+
height,
|
|
26
|
+
barHeight: 0,
|
|
27
|
+
tabLeft: x + BODY_RADIUS,
|
|
28
|
+
tabWidth: 0,
|
|
29
|
+
tabTop: y,
|
|
30
|
+
radius: TAB_RADIUS
|
|
31
|
+
};
|
|
17
32
|
}
|
|
18
33
|
/* The preview for a tab: the tab and the body of the cell it lands in, `origin` being the cell's top left in the grid. */
|
|
19
34
|
export function tabDropPreview(drop, origin) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
return {
|
|
36
|
+
x: origin.x,
|
|
37
|
+
y: origin.y,
|
|
38
|
+
width: drop.width,
|
|
39
|
+
height: drop.height,
|
|
40
|
+
barHeight: drop.barHeight,
|
|
41
|
+
tabLeft: origin.x + drop.left,
|
|
42
|
+
tabWidth: drop.tabWidth,
|
|
43
|
+
tabTop: origin.y + TAB_TOP_INSET,
|
|
44
|
+
radius: TAB_RADIUS
|
|
45
|
+
};
|
|
31
46
|
}
|
|
32
47
|
/* Two shapes that draw the same outline, so a drag that stays in one place can skip a render. */
|
|
33
48
|
export function sameDropPreview(left, right) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
left.y === right.y &&
|
|
39
|
-
left.width === right.width &&
|
|
40
|
-
left.height === right.height &&
|
|
41
|
-
left.barHeight === right.barHeight &&
|
|
42
|
-
left.tabLeft === right.tabLeft &&
|
|
43
|
-
left.tabWidth === right.tabWidth &&
|
|
44
|
-
left.tabTop === right.tabTop &&
|
|
45
|
-
left.radius === right.radius);
|
|
49
|
+
if (left === null || right === null) {
|
|
50
|
+
return left === right;
|
|
51
|
+
}
|
|
52
|
+
return left.x === right.x && left.y === right.y && left.width === right.width && left.height === right.height && left.barHeight === right.barHeight && left.tabLeft === right.tabLeft && left.tabWidth === right.tabWidth && left.tabTop === right.tabTop && left.radius === right.radius;
|
|
46
53
|
}
|
|
47
54
|
/*
|
|
48
55
|
* The outline of a tab grown into the body under it, as one closed path so no line runs between the
|
|
@@ -50,38 +57,31 @@ export function sameDropPreview(left, right) {
|
|
|
50
57
|
* the browser can move one outline into another with a transition on `d`. `inset` is how far the
|
|
51
58
|
* path runs inside the box, which is half the stroke, so the outer edge of the line is on the box.
|
|
52
59
|
*/
|
|
53
|
-
export function dropPreviewPath({
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
`H${coordinate(x + left + cornerBottom)}`,
|
|
82
|
-
arc(cornerBottom, left, bottom - cornerBottom),
|
|
83
|
-
`V${coordinate(y + top + cornerLeft)}`,
|
|
84
|
-
arc(cornerLeft, left + cornerLeft, top),
|
|
85
|
-
'Z'
|
|
86
|
-
].join(' ');
|
|
87
|
-
}
|
|
60
|
+
export function dropPreviewPath({
|
|
61
|
+
x,
|
|
62
|
+
y,
|
|
63
|
+
width,
|
|
64
|
+
height,
|
|
65
|
+
barHeight,
|
|
66
|
+
tabLeft,
|
|
67
|
+
tabWidth,
|
|
68
|
+
tabTop,
|
|
69
|
+
radius
|
|
70
|
+
}, inset = DROP_STROKE / 2) {
|
|
71
|
+
const left = inset;
|
|
72
|
+
const right = width - inset;
|
|
73
|
+
const bottom = height - inset;
|
|
74
|
+
const top = barHeight + inset;
|
|
75
|
+
const tabStart = Math.max(left, Math.min(tabLeft - x + inset, right));
|
|
76
|
+
const tabEnd = Math.max(tabStart, Math.min(tabLeft - x + tabWidth - inset, right));
|
|
77
|
+
const tabY = Math.min(tabTop - y + inset, top);
|
|
78
|
+
const tabCorner = Math.max(0, Math.min(radius - inset, (tabEnd - tabStart) / 2, top - tabY));
|
|
79
|
+
const bodyCorner = Math.max(0, BODY_RADIUS - inset);
|
|
80
|
+
// A tab flush with a side of the box continues that side, so the body has no corner there.
|
|
81
|
+
const cornerLeft = Math.min(bodyCorner, tabStart - left);
|
|
82
|
+
const cornerRight = Math.min(bodyCorner, right - tabEnd);
|
|
83
|
+
const cornerBottom = Math.max(0, Math.min(bodyCorner, (right - left) / 2, (bottom - top) / 2));
|
|
84
|
+
const at = (px, py) => `${coordinate(x + px)},${coordinate(y + py)}`;
|
|
85
|
+
const arc = (corner, px, py) => `A${coordinate(corner)},${coordinate(corner)} 0 0 1 ${at(px, py)}`;
|
|
86
|
+
return [`M${at(left + cornerLeft, top)}`, `H${coordinate(x + tabStart)}`, `V${coordinate(y + tabY + tabCorner)}`, arc(tabCorner, tabStart + tabCorner, tabY), `H${coordinate(x + tabEnd - tabCorner)}`, arc(tabCorner, tabEnd, tabY + tabCorner), `V${coordinate(y + top)}`, `H${coordinate(x + right - cornerRight)}`, arc(cornerRight, right, top + cornerRight), `V${coordinate(y + bottom - cornerBottom)}`, arc(cornerBottom, right - cornerBottom, bottom), `H${coordinate(x + left + cornerBottom)}`, arc(cornerBottom, left, bottom - cornerBottom), `V${coordinate(y + top + cornerLeft)}`, arc(cornerLeft, left + cornerLeft, top), 'Z'].join(' ');
|
|
87
|
+
}
|
package/dist/zoom.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* The percentages a zoom menu offers when the app names none. */
|
|
2
2
|
export const ZOOM_PRESETS = [25, 50, 75, 100, 150, 200];
|
|
3
3
|
/* Compared on the rounded percent the readout shows, so 0.999 still ticks 100%. */
|
|
4
|
-
export const activeZoomPreset = (zoom, presets) => presets.find(
|
|
4
|
+
export const activeZoomPreset = (zoom, presets) => presets.find(preset => preset === Math.round(zoom * 100)) ?? null;
|
|
5
5
|
/* Ten percent a click, on the readout's own rounded percent, so + and - come back where they were. */
|
|
6
|
-
export const steppedZoom = (zoom, by) => Math.round(zoom * 100 + by) / 100;
|
|
6
|
+
export const steppedZoom = (zoom, by) => Math.round(zoom * 100 + by) / 100;
|
package/package.json
CHANGED
package/src/ErrorBoundary.tsx
CHANGED
|
@@ -10,7 +10,7 @@ import { Icon } from './Icon.tsx';
|
|
|
10
10
|
import { IconButton } from './IconButton.tsx';
|
|
11
11
|
|
|
12
12
|
export interface ErrorBoundaryProps {
|
|
13
|
-
/* What failed, as the first line of the message: "
|
|
13
|
+
/* What failed, as the first line of the message: "Could not show this view". */
|
|
14
14
|
label: string;
|
|
15
15
|
/* What the children draw from; the boundary tries again when one of them changes. */
|
|
16
16
|
resetKeys?: ResetKeys;
|
package/src/Field.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useId,
|
|
1
|
+
import { useId, type ReactNode, type Ref } from 'react';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
import { useRender } from '@base-ui/react/use-render';
|
|
4
4
|
import { FieldContext } from './field-context.ts';
|
|
@@ -84,8 +84,9 @@ export function Field({ label, hint, error, group = false, orientation = 'vertic
|
|
|
84
84
|
const hasLabel = label !== undefined;
|
|
85
85
|
const hasHint = isShown(hint);
|
|
86
86
|
const hasError = isShown(error);
|
|
87
|
-
const
|
|
88
|
-
const
|
|
87
|
+
const lines = [hasHint ? `${id}-hint` : null, hasError ? `${id}-error` : null].filter((part) => part !== null);
|
|
88
|
+
const describedBy = lines.length > 0 ? lines.join(' ') : undefined;
|
|
89
|
+
const control = group ? null : { id: `${id}-control`, describedBy, invalid: hasError };
|
|
89
90
|
return (
|
|
90
91
|
<div
|
|
91
92
|
ref={ref}
|
package/src/IconPicker.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useId, useLayoutEffect,
|
|
1
|
+
import { useId, useLayoutEffect, useRef, useState, type KeyboardEvent, type Ref } from 'react';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
import { Ban, Search, X } from 'lucide-react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
@@ -74,8 +74,8 @@ export function IconPicker({
|
|
|
74
74
|
const [focused, setFocused] = useState<string | null>(null);
|
|
75
75
|
const headed = isGrouped(icons);
|
|
76
76
|
const framed = searchable || rows !== undefined;
|
|
77
|
-
const sections =
|
|
78
|
-
const shown =
|
|
77
|
+
const sections = sectionsOf(icons);
|
|
78
|
+
const shown = filterSections(sections, query, keywords);
|
|
79
79
|
const tabStop = tabStopOf(
|
|
80
80
|
shown.flatMap((section) => section.icons.map(([name]) => name)),
|
|
81
81
|
focused,
|
package/src/LazyBoundary.tsx
CHANGED
|
@@ -12,6 +12,8 @@ export interface LazyBoundaryProps {
|
|
|
12
12
|
* lands, so a chunk that takes 20 ms shows after 20 ms.
|
|
13
13
|
*/
|
|
14
14
|
export function LazyBoundary({ fallback = null, children }: LazyBoundaryProps) {
|
|
15
|
+
// Compiled, the same boundary would come back and the landing would wait for React's own retry after all.
|
|
16
|
+
'use no memo';
|
|
15
17
|
useSyncExternalStore(subscribeLandings, readLandings, readLandings);
|
|
16
18
|
return <Suspense fallback={fallback}>{children}</Suspense>;
|
|
17
19
|
}
|
package/src/MiddleTruncate.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useLayoutEffect,
|
|
1
|
+
import { useLayoutEffect, useState, type Ref } from 'react';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
import { fitMiddle, type MiddleCut } from './middle-truncate.ts';
|
|
4
4
|
import { mergeRefs } from './merge-refs.ts';
|
|
@@ -42,7 +42,7 @@ function sameCut(first: MiddleCut | null, second: MiddleCut | null): boolean {
|
|
|
42
42
|
export function MiddleTruncate({ text, tooltip = true, className, ref }: MiddleTruncateProps) {
|
|
43
43
|
const [element, setElement] = useState<HTMLSpanElement | null>(null);
|
|
44
44
|
const [cut, setCut] = useState<MiddleCut | null>(null);
|
|
45
|
-
const rootRef =
|
|
45
|
+
const rootRef = mergeRefs(setElement, ref);
|
|
46
46
|
|
|
47
47
|
useLayoutEffect(() => {
|
|
48
48
|
if (element === null) {
|
package/src/SegmentBar.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect,
|
|
1
|
+
import { useEffect, useRef, useState, type Ref } from 'react';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
import { mergeRefs } from './merge-refs.ts';
|
|
4
4
|
import { Tooltip } from './Tooltip.tsx';
|
|
@@ -37,7 +37,7 @@ export interface SegmentBarProps {
|
|
|
37
37
|
place; the gap between two parts that touch comes out of the one before. */
|
|
38
38
|
export function SegmentBar({ parts, range, size = 'md', onSelect, label, className, ref }: SegmentBarProps) {
|
|
39
39
|
const [measure, width] = useMeasuredWidth();
|
|
40
|
-
const rootRef =
|
|
40
|
+
const rootRef = mergeRefs(ref, measure);
|
|
41
41
|
const labels = useRef<(HTMLSpanElement | null)[]>([]);
|
|
42
42
|
const [cut, setCut] = useState<readonly boolean[]>([]);
|
|
43
43
|
|
package/src/Toasts.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import { useState, type ReactNode, type Ref } from 'react';
|
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { CircleAlert, CircleCheck, Trash, X } from 'lucide-react';
|
|
5
|
+
import { useStore } from 'zustand';
|
|
5
6
|
import { Button } from './Button.tsx';
|
|
6
7
|
import { Icon } from './Icon.tsx';
|
|
7
8
|
import { IconButton } from './IconButton.tsx';
|
|
@@ -133,7 +134,8 @@ export interface ToastsProps<T extends Toast> {
|
|
|
133
134
|
* a deletion after eight, and a failure waits to be read.
|
|
134
135
|
*/
|
|
135
136
|
export function Toasts<T extends Toast>({ store, footer, className, ref }: ToastsProps<T>) {
|
|
136
|
-
|
|
137
|
+
// Through `useStore`: the compiler takes a call of the prop itself for a plain function and would skip it on a later draw.
|
|
138
|
+
const toasts = useStore(store, (state) => state.toasts);
|
|
137
139
|
// Stays mounted with nothing in it: a screen reader skips what a live region says the moment it is inserted.
|
|
138
140
|
return (
|
|
139
141
|
<div
|
package/src/Tooltip.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ComponentProps, type ReactElement, type ReactNode } from 'react';
|
|
2
2
|
import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip';
|
|
3
3
|
import { Kbd } from './Kbd.tsx';
|
|
4
4
|
import type { Shortcut } from './shortcut.ts';
|
|
@@ -37,15 +37,12 @@ export interface TooltipProps {
|
|
|
37
37
|
/* A hint on hover and on keyboard focus. Every hint in an app is one of these, never a `title`. */
|
|
38
38
|
export function Tooltip({ label, open, onOpenChange, anchor, kbd, side = 'top', sideOffset = 6, name = false, disabled = false, children }: TooltipProps) {
|
|
39
39
|
// Held Cmd (Ctrl off macOS) prints this shortcut under the trigger (`ShortcutHints`).
|
|
40
|
-
const hintRef =
|
|
41
|
-
(element
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
},
|
|
47
|
-
[kbd]
|
|
48
|
-
);
|
|
40
|
+
const hintRef = (element: HTMLElement | null) => {
|
|
41
|
+
if (element === null || kbd === undefined || typeof kbd === 'string') {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
return registerShortcutHint(element, kbd);
|
|
45
|
+
};
|
|
49
46
|
|
|
50
47
|
return (
|
|
51
48
|
<BaseTooltip.Root disabled={disabled} open={open} onOpenChange={onOpenChange} disableHoverablePopup={children === undefined}>
|
package/src/Waveform.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useRef, type KeyboardEvent, type PointerEvent, type Ref } from 'react';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { formatClockDuration } from './format/duration.ts';
|
|
@@ -80,12 +80,12 @@ export function Waveform({
|
|
|
80
80
|
}: WaveformProps) {
|
|
81
81
|
const { t } = useTranslation('ui');
|
|
82
82
|
const [measure, width] = useMeasuredWidth();
|
|
83
|
-
const rootRef =
|
|
83
|
+
const rootRef = mergeRefs(ref, measure);
|
|
84
84
|
const dragged = useRef(0);
|
|
85
85
|
const [from, to] = range ?? [0, duration];
|
|
86
86
|
const span = to - from;
|
|
87
87
|
const count = Math.floor(width / BAR_PITCH);
|
|
88
|
-
const bars =
|
|
88
|
+
const bars = barsOf(levels, duration, from, to, count);
|
|
89
89
|
|
|
90
90
|
const positionOf = (time: number): number => (span > 0 ? (time - from) / span : 0);
|
|
91
91
|
const inView = (time: number): boolean => positionOf(time) >= 0 && positionOf(time) <= 1;
|
package/src/file-tree/parts.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useLayoutEffect, useRef, useState, type ReactNode } from 'react';
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
3
|
import { useRender } from '@base-ui/react/use-render';
|
|
4
4
|
import { FileTree as PierreFileTree, type FileTreeProps as PierreProps } from '@pierre/trees/react';
|
|
@@ -55,14 +55,11 @@ export function FileTreeRoot({
|
|
|
55
55
|
const { t } = useTranslation('ui');
|
|
56
56
|
const [frame, setFrame] = useState<HTMLDivElement | null>(null);
|
|
57
57
|
const { attach, bar } = useFileTreeShift(model, resetKey);
|
|
58
|
-
const frameRef =
|
|
59
|
-
(node
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
},
|
|
64
|
-
[ref, attach]
|
|
65
|
-
);
|
|
58
|
+
const frameRef = (node: Element | null) => {
|
|
59
|
+
setFrame(node as HTMLDivElement | null);
|
|
60
|
+
attach(node as HTMLElement | null);
|
|
61
|
+
mergeRefs<Element>(ref)(node);
|
|
62
|
+
};
|
|
66
63
|
const slots = useFileTreeSlots(model, frame, label ?? t('tree.label'));
|
|
67
64
|
const latestLoads = useRef({ onLoadChildren, onExpandedPathsChange });
|
|
68
65
|
useLayoutEffect(() => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ReactElement } from 'react';
|
|
2
2
|
import type { FileTree } from '@pierre/trees';
|
|
3
3
|
import { useTreeShift, type TreeShiftSource } from '../tree/use-tree-shift.tsx';
|
|
4
4
|
import { shiftNeed } from '../tree/shift.ts';
|
|
@@ -40,13 +40,10 @@ function needOf(row: HTMLElement, shift: number, gap: number, range: Range): num
|
|
|
40
40
|
|
|
41
41
|
/* The file tree draws its rows in a shadow root, and only the name slides: its decorations sit over the row. */
|
|
42
42
|
export function useFileTreeShift(model: FileTree, resetKey?: string): { attach(node: HTMLElement | null): void; bar: ReactElement } {
|
|
43
|
-
const source =
|
|
44
|
-
()
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}),
|
|
49
|
-
[model]
|
|
50
|
-
);
|
|
43
|
+
const source: TreeShiftSource = {
|
|
44
|
+
root: () => model.getFileTreeContainer()?.shadowRoot ?? null,
|
|
45
|
+
rows: (root) => [...root.querySelectorAll<HTMLElement>(ROWS)],
|
|
46
|
+
need: needOf
|
|
47
|
+
};
|
|
51
48
|
return useTreeShift(source, resetKey);
|
|
52
49
|
}
|
package/src/lazy.tsx
CHANGED
|
@@ -79,6 +79,8 @@ export function lazyNamed<Module extends Record<Name, ComponentType<any>>, Name
|
|
|
79
79
|
const Lazy = lazy(() => open(loaded));
|
|
80
80
|
// Once `loaded` has it, `Lazy` never committed anything, so switching over remounts nothing.
|
|
81
81
|
const LazyNamed = (props: ComponentProps<Module[Name]>) => {
|
|
82
|
+
// `loaded` fills in place; compiled, the element drawn before the module landed would stay.
|
|
83
|
+
'use no memo';
|
|
82
84
|
const Component: ComponentType<any> = loaded.current ?? Lazy;
|
|
83
85
|
return <Component {...props} />;
|
|
84
86
|
};
|
|
@@ -114,6 +116,8 @@ export function lazyDialog<Module extends Record<Name, ComponentType>, Name exte
|
|
|
114
116
|
const subscribe = (listener: () => void) => loaded.subscribe(listener);
|
|
115
117
|
const isLoaded = () => loaded.current !== null;
|
|
116
118
|
const LazyDialog = () => {
|
|
119
|
+
// As in `lazyNamed`: what `loaded` holds is read again on every render.
|
|
120
|
+
'use no memo';
|
|
117
121
|
const opened = useOpenedOnce(useStore(isOpen));
|
|
118
122
|
useSyncExternalStore(subscribe, isLoaded);
|
|
119
123
|
const Loaded: ComponentType | null = loaded.current;
|
package/src/locales/en.ts
CHANGED
|
@@ -17,11 +17,11 @@ export default {
|
|
|
17
17
|
section: 'Settings section',
|
|
18
18
|
sections: 'Settings sections',
|
|
19
19
|
close: 'Close settings',
|
|
20
|
-
failed: '
|
|
20
|
+
failed: 'Could not show this settings page',
|
|
21
21
|
search: {
|
|
22
22
|
placeholder: 'Search settings',
|
|
23
23
|
clear: 'Clear search',
|
|
24
|
-
none: '
|
|
24
|
+
none: 'No results for “{{query}}”',
|
|
25
25
|
results: 'Search results',
|
|
26
26
|
pane: 'Section'
|
|
27
27
|
}
|
|
@@ -36,12 +36,12 @@ export default {
|
|
|
36
36
|
},
|
|
37
37
|
select: {
|
|
38
38
|
search: 'Search',
|
|
39
|
-
noMatch: 'No
|
|
39
|
+
noMatch: 'No match for “{{query}}”'
|
|
40
40
|
},
|
|
41
41
|
iconPicker: {
|
|
42
42
|
label: 'Icon',
|
|
43
43
|
none: 'No icon',
|
|
44
|
-
search: '
|
|
44
|
+
search: 'Search icons',
|
|
45
45
|
clear: 'Clear search',
|
|
46
46
|
noMatch: 'No icon matches “{{query}}”'
|
|
47
47
|
},
|
package/src/locales/nl.ts
CHANGED
|
@@ -17,11 +17,11 @@ export default {
|
|
|
17
17
|
section: 'Onderdeel van de instellingen',
|
|
18
18
|
sections: 'Onderdelen van de instellingen',
|
|
19
19
|
close: 'Instellingen sluiten',
|
|
20
|
-
failed: '
|
|
20
|
+
failed: 'Kon deze instellingenpagina niet tonen',
|
|
21
21
|
search: {
|
|
22
22
|
placeholder: 'Instellingen zoeken',
|
|
23
23
|
clear: 'Zoekopdracht wissen',
|
|
24
|
-
none: '
|
|
24
|
+
none: 'Geen resultaten voor “{{query}}”',
|
|
25
25
|
results: 'Zoekresultaten',
|
|
26
26
|
pane: 'Onderdeel'
|
|
27
27
|
}
|
|
@@ -36,12 +36,12 @@ export default {
|
|
|
36
36
|
},
|
|
37
37
|
select: {
|
|
38
38
|
search: 'Zoeken',
|
|
39
|
-
noMatch: 'Geen
|
|
39
|
+
noMatch: 'Geen resultaat voor “{{query}}”'
|
|
40
40
|
},
|
|
41
41
|
iconPicker: {
|
|
42
42
|
label: 'Icoon',
|
|
43
43
|
none: 'Geen icoon',
|
|
44
|
-
search: '
|
|
44
|
+
search: 'Iconen zoeken',
|
|
45
45
|
clear: 'Zoekopdracht wissen',
|
|
46
46
|
noMatch: 'Geen icoon past bij “{{query}}”'
|
|
47
47
|
},
|
|
@@ -70,7 +70,7 @@ export default {
|
|
|
70
70
|
collapse: 'Invouwen'
|
|
71
71
|
},
|
|
72
72
|
workspace: {
|
|
73
|
-
tabs: 'Open
|
|
73
|
+
tabs: 'Open weergaven',
|
|
74
74
|
unsaved: 'Niet-opgeslagen wijzigingen',
|
|
75
75
|
closeTab: 'Sluit {{name}}',
|
|
76
76
|
resize: 'Panelen vergroten of verkleinen',
|
|
@@ -239,7 +239,7 @@ export function SettingsDialog({
|
|
|
239
239
|
useEffect(() => {
|
|
240
240
|
latestShown.current = onTargetShown;
|
|
241
241
|
});
|
|
242
|
-
const targetValue =
|
|
242
|
+
const targetValue = { target, shown: () => latestShown.current?.() };
|
|
243
243
|
|
|
244
244
|
const labelOf = (id: string): string => sections.find((entry) => entry.id === id)?.label ?? id;
|
|
245
245
|
const pick = (result: SettingsSearchResult): void => onNavigate({ section: result.section, target: result.id });
|
package/src/tree/parts.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type MouseEvent } from 'react';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
import { useRender } from '@base-ui/react/use-render';
|
|
4
4
|
import { ChevronRight } from 'lucide-react';
|
|
@@ -25,13 +25,10 @@ export type TreeRootProps = useRender.ComponentProps<'div'> & {
|
|
|
25
25
|
export function TreeRoot({ overflow = 'truncate', render, className, ref, children, ...props }: TreeRootProps) {
|
|
26
26
|
const { attach, bar } = useTreeShift(TREE_ROWS);
|
|
27
27
|
const scrolls = overflow === 'scroll';
|
|
28
|
-
const rootRef =
|
|
29
|
-
(node:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
},
|
|
33
|
-
[scrolls, ref, attach]
|
|
34
|
-
);
|
|
28
|
+
const rootRef = (node: HTMLDivElement | null) => {
|
|
29
|
+
attach(scrolls ? node : null);
|
|
30
|
+
mergeRefs<HTMLDivElement>(ref)(node);
|
|
31
|
+
};
|
|
35
32
|
return useRender({
|
|
36
33
|
render,
|
|
37
34
|
ref: rootRef,
|
package/src/useContentSize.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
export interface ContentSize {
|
|
4
4
|
width: number;
|
|
@@ -10,7 +10,7 @@ export function useContentSize(): [(node: HTMLElement | null) => void, ContentSi
|
|
|
10
10
|
const [size, setSize] = useState<ContentSize>({ width: 0, height: 0 });
|
|
11
11
|
const observer = useRef<ResizeObserver | null>(null);
|
|
12
12
|
useEffect(() => () => observer.current?.disconnect(), []);
|
|
13
|
-
const measure =
|
|
13
|
+
const measure = (node: HTMLElement | null): void => {
|
|
14
14
|
observer.current?.disconnect();
|
|
15
15
|
if (node === null) {
|
|
16
16
|
return;
|
|
@@ -22,6 +22,6 @@ export function useContentSize(): [(node: HTMLElement | null) => void, ContentSi
|
|
|
22
22
|
update(node.clientWidth, node.clientHeight);
|
|
23
23
|
observer.current = new ResizeObserver(([entry]) => update(entry!.contentRect.width, entry!.contentRect.height));
|
|
24
24
|
observer.current.observe(node);
|
|
25
|
-
}
|
|
25
|
+
};
|
|
26
26
|
return [measure, size];
|
|
27
27
|
}
|
|
@@ -287,13 +287,13 @@ export function SplitView({
|
|
|
287
287
|
useLayoutEffect(() => {
|
|
288
288
|
latest.current = { value, onValueChange };
|
|
289
289
|
});
|
|
290
|
-
const dispatch =
|
|
290
|
+
const dispatch = (command: SplitCommand): void => {
|
|
291
291
|
const state = latest.current;
|
|
292
292
|
const next = updateSplitLayout(state.value, command);
|
|
293
293
|
if (next !== state.value) {
|
|
294
294
|
state.onValueChange(next, command);
|
|
295
295
|
}
|
|
296
|
-
}
|
|
296
|
+
};
|
|
297
297
|
const measure = useCallback((): void => {
|
|
298
298
|
const element = root.current;
|
|
299
299
|
if (!element) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createContext,
|
|
3
3
|
useContext,
|
|
4
|
-
useCallback,
|
|
5
4
|
useEffect,
|
|
6
5
|
useLayoutEffect,
|
|
7
6
|
useRef,
|
|
@@ -109,7 +108,7 @@ export function TabStrip({
|
|
|
109
108
|
};
|
|
110
109
|
const [edges, setEdges] = useState({ start: false, end: false });
|
|
111
110
|
const [insertion, setInsertion] = useState(0);
|
|
112
|
-
const measure =
|
|
111
|
+
const measure = (): void => {
|
|
113
112
|
const element = strip.current;
|
|
114
113
|
if (!element) {
|
|
115
114
|
return;
|
|
@@ -123,7 +122,7 @@ export function TabStrip({
|
|
|
123
122
|
const last = tabs.at(-1);
|
|
124
123
|
setInsertion(after?.offsetLeft ?? (last ? last.offsetLeft + last.offsetWidth : 0));
|
|
125
124
|
}
|
|
126
|
-
}
|
|
125
|
+
};
|
|
127
126
|
// Stays set after the active tab changes, as its width can settle frames later; a person's own scroll ends it.
|
|
128
127
|
const revealing = useRef(false);
|
|
129
128
|
const reveal = (): void => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
import { useRender } from '@base-ui/react/use-render';
|
|
4
4
|
import { WorkspaceContext, type WorkspaceLayout } from './context.ts';
|
|
@@ -70,9 +70,6 @@ export function Workspace({
|
|
|
70
70
|
)
|
|
71
71
|
}
|
|
72
72
|
});
|
|
73
|
-
const context =
|
|
74
|
-
() => ({ layout, edges: { top: !hasToolbar, right: !rightOpen, bottom: true, left: !leftOpen } }),
|
|
75
|
-
[layout, hasToolbar, rightOpen, leftOpen]
|
|
76
|
-
);
|
|
73
|
+
const context = { layout, edges: { top: !hasToolbar, right: !rightOpen, bottom: true, left: !leftOpen } };
|
|
77
74
|
return <WorkspaceContext value={context}>{element}</WorkspaceContext>;
|
|
78
75
|
}
|