@adecore/ui 0.24.2 → 0.25.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.
- package/dist/Button.d.ts +3 -1
- package/dist/Button.js +7 -2
- package/dist/EmptyState.d.ts +1 -3
- package/dist/EmptyState.js +2 -2
- package/dist/IconButton.d.ts +1 -3
- package/dist/IconButton.js +2 -2
- package/dist/PanelEmpty.d.ts +1 -3
- package/dist/PanelEmpty.js +2 -2
- package/dist/format/datetime.d.ts +5 -4
- package/dist/format/datetime.js +11 -5
- package/dist/format/index.d.ts +3 -3
- package/dist/format/index.js +3 -3
- package/dist/index.d.ts +7 -9
- package/dist/index.js +6 -8
- package/dist/workspace/model.d.ts +0 -2
- package/dist/workspace/model.js +0 -69
- package/package.json +1 -1
- package/src/Button.tsx +13 -3
- package/src/EmptyState.tsx +2 -4
- package/src/IconButton.tsx +1 -4
- package/src/PanelEmpty.tsx +2 -4
- package/src/format/datetime.ts +15 -5
- package/src/format/index.ts +2 -2
- package/src/index.ts +5 -10
- package/src/workspace/model.ts +0 -70
package/dist/Button.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { ComponentProps } from 'react';
|
|
2
2
|
export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger' | 'danger-outline' | 'positive' | 'inverse';
|
|
3
3
|
export type ButtonSize = 'xs' | 'sm' | 'md';
|
|
4
|
+
export type ButtonShape = 'default' | 'pill';
|
|
4
5
|
export interface ButtonProps extends ComponentProps<'button'> {
|
|
5
6
|
variant?: ButtonVariant;
|
|
6
7
|
size?: ButtonSize;
|
|
8
|
+
shape?: ButtonShape;
|
|
7
9
|
href?: string;
|
|
8
10
|
}
|
|
9
|
-
export declare function Button({ variant, size, className, href, type, ref, ...rest }: ButtonProps): import("react").JSX.Element;
|
|
11
|
+
export declare function Button({ variant, size, shape, className, href, type, ref, ...rest }: ButtonProps): import("react").JSX.Element;
|
package/dist/Button.js
CHANGED
|
@@ -18,9 +18,14 @@ const SIZE = {
|
|
|
18
18
|
sm: 'h-7 px-2.5',
|
|
19
19
|
md: 'h-8 px-3'
|
|
20
20
|
};
|
|
21
|
+
/* A pill is one size of its own, for a control that sits in a round row, so it takes the place of `size`. */
|
|
22
|
+
const SHAPE = {
|
|
23
|
+
default: 'rounded-md text-xs',
|
|
24
|
+
pill: 'h-9 rounded-full px-3.5 text-sm'
|
|
25
|
+
};
|
|
21
26
|
/* Every button with a word in it. A button that is only an icon is an `IconButton`, which is a square, not a label. */
|
|
22
|
-
export function Button({ variant = 'ghost', size = 'md', className, href, type = 'button', ref, ...rest }) {
|
|
23
|
-
const classes = clsx('inline-flex shrink-0 items-center justify-center gap-1.5
|
|
27
|
+
export function Button({ variant = 'ghost', size = 'md', shape = 'default', className, href, type = 'button', ref, ...rest }) {
|
|
28
|
+
const classes = clsx('inline-flex shrink-0 items-center justify-center gap-1.5 font-medium disabled:opacity-50', SHAPE[shape], VARIANT[variant], shape === 'default' && SIZE[size], className);
|
|
24
29
|
if (href) {
|
|
25
30
|
return (_jsx("a", { ref: ref, className: classes, href: href, target: "_blank", rel: "noreferrer", ...rest }));
|
|
26
31
|
}
|
package/dist/EmptyState.d.ts
CHANGED
|
@@ -5,12 +5,10 @@ export interface EmptyStateProps {
|
|
|
5
5
|
size?: number;
|
|
6
6
|
}>;
|
|
7
7
|
busy?: boolean;
|
|
8
|
-
/** @deprecated Turns a Lucide icon; use `busy`, which draws a `Spinner` in its place. */
|
|
9
|
-
spin?: boolean;
|
|
10
8
|
title?: ReactNode;
|
|
11
9
|
children: ReactNode;
|
|
12
10
|
action?: ReactNode;
|
|
13
11
|
className?: string;
|
|
14
12
|
ref?: Ref<HTMLDivElement>;
|
|
15
13
|
}
|
|
16
|
-
export declare function EmptyState({ icon, busy,
|
|
14
|
+
export declare function EmptyState({ icon, busy, title, children, action, className, ref }: EmptyStateProps): import("react").JSX.Element;
|
package/dist/EmptyState.js
CHANGED
|
@@ -6,6 +6,6 @@ import { Spinner } from './Spinner.js';
|
|
|
6
6
|
// One size in a panel, a card and a dialog alike, so no caller picks its own.
|
|
7
7
|
const ICON_SIZE = 20;
|
|
8
8
|
/* What a list, a canvas or a thread shows before it holds anything. */
|
|
9
|
-
export function EmptyState({ icon, busy = false,
|
|
10
|
-
return (_jsxs("div", { ref: ref, className: clsx('flex flex-col items-center justify-center gap-2 px-6 py-8 text-center', className), children: [(busy || icon) && (_jsx("span", { className: "text-text-faint", children: busy ? (_jsx(Spinner, { size: ICON_SIZE })) : isValidElement(icon) ? (cloneElement(icon, { size: ICON_SIZE })) : (icon && _jsx(Icon, { icon: icon, size: ICON_SIZE
|
|
9
|
+
export function EmptyState({ icon, busy = false, title, children, action, className, ref }) {
|
|
10
|
+
return (_jsxs("div", { ref: ref, className: clsx('flex flex-col items-center justify-center gap-2 px-6 py-8 text-center', className), children: [(busy || icon) && (_jsx("span", { className: "text-text-faint", children: busy ? (_jsx(Spinner, { size: ICON_SIZE })) : isValidElement(icon) ? (cloneElement(icon, { size: ICON_SIZE })) : (icon && _jsx(Icon, { icon: icon, size: ICON_SIZE })) })), title && _jsx("p", { className: "text-sm font-medium text-text", children: title }), _jsx("p", { className: "max-w-[280px] text-xs leading-snug text-text-muted", children: children }), action && _jsx("div", { className: "mt-2", children: action })] }));
|
|
11
11
|
}
|
package/dist/IconButton.d.ts
CHANGED
|
@@ -14,10 +14,8 @@ export interface IconButtonProps extends Omit<ComponentProps<'button'>, 'childre
|
|
|
14
14
|
size?: IconButtonSize;
|
|
15
15
|
active?: boolean;
|
|
16
16
|
busy?: boolean;
|
|
17
|
-
/** @deprecated Turns the icon; use `busy`, which draws a `Spinner` in its place. */
|
|
18
|
-
spin?: boolean;
|
|
19
17
|
iconClassName?: string;
|
|
20
18
|
children?: ReactNode;
|
|
21
19
|
render?: useRender.RenderProp;
|
|
22
20
|
}
|
|
23
|
-
export declare function IconButton({ icon, label, tooltip, kbd, tooltipSide, size, active, busy,
|
|
21
|
+
export declare function IconButton({ icon, label, tooltip, kbd, tooltipSide, size, active, busy, iconClassName, render, className, ref, children, ...props }: IconButtonProps): import("react").JSX.Element;
|
package/dist/IconButton.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Spinner } from './Spinner.js';
|
|
|
6
6
|
import { Tooltip } from './Tooltip.js';
|
|
7
7
|
import { ICON_BUTTON_CLASS, ICON_BUTTON_ICON_SIZE } from './icon-button-size.js';
|
|
8
8
|
/* Every button that is an icon and no word. A square, and the one place the icon inside it is sized. */
|
|
9
|
-
export function IconButton({ icon, label, tooltip, kbd, tooltipSide, size = 'md', active = false, busy = false,
|
|
9
|
+
export function IconButton({ icon, label, tooltip, kbd, tooltipSide, size = 'md', active = false, busy = false, iconClassName, render, className, ref, children, ...props }) {
|
|
10
10
|
const button = useRender({
|
|
11
11
|
render: render ?? _jsx("button", { type: "button" }),
|
|
12
12
|
ref,
|
|
@@ -15,7 +15,7 @@ export function IconButton({ icon, label, tooltip, kbd, tooltipSide, size = 'md'
|
|
|
15
15
|
'data-active': active ? 'true' : undefined,
|
|
16
16
|
...props,
|
|
17
17
|
className: clsx('icon-btn', ICON_BUTTON_CLASS[size], className),
|
|
18
|
-
children: (_jsxs(_Fragment, { children: [busy ? (_jsx(Spinner, { size: ICON_BUTTON_ICON_SIZE[size], className: iconClassName })) : (icon !== undefined && _jsx(Icon, { icon: icon, size: ICON_BUTTON_ICON_SIZE[size], className:
|
|
18
|
+
children: (_jsxs(_Fragment, { children: [busy ? (_jsx(Spinner, { size: ICON_BUTTON_ICON_SIZE[size], className: iconClassName })) : (icon !== undefined && _jsx(Icon, { icon: icon, size: ICON_BUTTON_ICON_SIZE[size], className: iconClassName })), children] }))
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
if (tooltip === false) {
|
package/dist/PanelEmpty.d.ts
CHANGED
|
@@ -4,8 +4,6 @@ export interface PanelEmptyProps {
|
|
|
4
4
|
header?: ReactNode;
|
|
5
5
|
icon?: LucideIcon;
|
|
6
6
|
busy?: boolean;
|
|
7
|
-
/** @deprecated Turns the icon; use `busy`, which draws a `Spinner` in its place. */
|
|
8
|
-
spin?: boolean;
|
|
9
7
|
action?: ReactNode;
|
|
10
8
|
sunken?: boolean;
|
|
11
9
|
fill?: 'grow' | 'full';
|
|
@@ -13,4 +11,4 @@ export interface PanelEmptyProps {
|
|
|
13
11
|
className?: string;
|
|
14
12
|
ref?: Ref<HTMLDivElement>;
|
|
15
13
|
}
|
|
16
|
-
export declare function PanelEmpty({ header, icon, busy,
|
|
14
|
+
export declare function PanelEmpty({ header, icon, busy, action, sunken, fill, children, className, ref }: PanelEmptyProps): import("react").JSX.Element;
|
package/dist/PanelEmpty.js
CHANGED
|
@@ -2,6 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
import { EmptyState } from './EmptyState.js';
|
|
4
4
|
/* What a panel or a card shows while it holds nothing: a sentence, one icon, at most one button. */
|
|
5
|
-
export function PanelEmpty({ header, icon, busy = false,
|
|
6
|
-
return (_jsxs("div", { ref: ref, className: clsx('grid min-h-0 place-items-center', fill === 'full' ? 'h-full' : 'grow', sunken && 'bg-surface-sunken', className), children: [header, _jsx(EmptyState, { icon: icon, busy: busy,
|
|
5
|
+
export function PanelEmpty({ header, icon, busy = false, action, sunken = false, fill = 'grow', children, className, ref }) {
|
|
6
|
+
return (_jsxs("div", { ref: ref, className: clsx('grid min-h-0 place-items-center', fill === 'full' ? 'h-full' : 'grow', sunken && 'bg-surface-sunken', className), children: [header, _jsx(EmptyState, { icon: icon, busy: busy, action: action, children: children })] }));
|
|
7
7
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
export declare const formatDateTime: (at: Date | number, options: Intl.DateTimeFormatOptions) => string;
|
|
2
|
-
export declare const formatClock: (at: Date | number) => string;
|
|
2
|
+
export declare const formatClock: (at: Date | number, timeZone?: string) => string;
|
|
3
3
|
export declare const formatWeekdayClock: (at: Date | number) => string;
|
|
4
4
|
export declare const formatHour: (at: Date | number) => string;
|
|
5
|
-
export declare const formatDay: (at: Date | number) => string;
|
|
6
|
-
export declare const formatDayWithYear: (at: Date | number) => string;
|
|
5
|
+
export declare const formatDay: (at: Date | number, timeZone?: string) => string;
|
|
6
|
+
export declare const formatDayWithYear: (at: Date | number, timeZone?: string) => string;
|
|
7
7
|
export declare const formatWeekdayDay: (at: Date | number) => string;
|
|
8
|
-
export declare const formatDayClock: (at: Date | number) => string;
|
|
8
|
+
export declare const formatDayClock: (at: Date | number, timeZone?: string) => string;
|
|
9
|
+
export declare const formatDayClockWithYear: (at: Date | number, timeZone?: string) => string;
|
|
9
10
|
export declare const formatNumericDate: (at: Date | number) => string;
|
|
10
11
|
export declare const isSameDay: (a: Date | number, b: Date | number) => boolean;
|
|
11
12
|
export declare const formatMoment: (at: Date | number, now?: Date | number) => string;
|
package/dist/format/datetime.js
CHANGED
|
@@ -26,21 +26,27 @@ const DAY = { month: 'short', day: 'numeric' };
|
|
|
26
26
|
const DAY_WITH_YEAR = { month: 'short', day: 'numeric', year: 'numeric' };
|
|
27
27
|
const WEEKDAY_DAY = { weekday: 'short', month: 'short', day: 'numeric' };
|
|
28
28
|
const DAY_CLOCK = { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' };
|
|
29
|
+
const DAY_CLOCK_WITH_YEAR = { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit' };
|
|
29
30
|
const NUMERIC_DATE = { year: 'numeric', month: 'numeric', day: 'numeric' };
|
|
30
|
-
/*
|
|
31
|
-
|
|
31
|
+
/*
|
|
32
|
+
* `08:05`, or `08:05 AM` in a region that counts to twelve. The clock functions below take the `timeZone`
|
|
33
|
+
* to write in, such as `UTC` for a moment whose parts are given and mean the same wall clock anywhere.
|
|
34
|
+
*/
|
|
35
|
+
export const formatClock = (at, timeZone) => formatDateTime(at, timeZone === undefined ? CLOCK : { ...CLOCK, timeZone });
|
|
32
36
|
/* A time far enough off that its day is part of the answer: `Sat 08:05`. */
|
|
33
37
|
export const formatWeekdayClock = (at) => formatDateTime(at, WEEKDAY_CLOCK);
|
|
34
38
|
/* An hour of the day on its own, for the foot of a chart: `08`, or `8 AM` where that is the clock. */
|
|
35
39
|
export const formatHour = (at) => formatDateTime(at, HOUR);
|
|
36
40
|
/* `19 Sep`, the short date a row falls back to once "days ago" stops meaning anything. */
|
|
37
|
-
export const formatDay = (at) => formatDateTime(at, DAY);
|
|
41
|
+
export const formatDay = (at, timeZone) => formatDateTime(at, timeZone === undefined ? DAY : { ...DAY, timeZone });
|
|
38
42
|
/* The same date with the year, for anything older than the one we are in. */
|
|
39
|
-
export const formatDayWithYear = (at) => formatDateTime(at, DAY_WITH_YEAR);
|
|
43
|
+
export const formatDayWithYear = (at, timeZone) => formatDateTime(at, timeZone === undefined ? DAY_WITH_YEAR : { ...DAY_WITH_YEAR, timeZone });
|
|
40
44
|
/* `Sat 19 Sep`. */
|
|
41
45
|
export const formatWeekdayDay = (at) => formatDateTime(at, WEEKDAY_DAY);
|
|
42
46
|
/* `19 Sep, 08:05`: a moment that is not today, to the minute. */
|
|
43
|
-
export const formatDayClock = (at) => formatDateTime(at, DAY_CLOCK);
|
|
47
|
+
export const formatDayClock = (at, timeZone) => formatDateTime(at, timeZone === undefined ? DAY_CLOCK : { ...DAY_CLOCK, timeZone });
|
|
48
|
+
/* `19 Sep 2026, 08:05`: the same moment with the year, for one that is not in the year we are in. */
|
|
49
|
+
export const formatDayClockWithYear = (at, timeZone) => formatDateTime(at, timeZone === undefined ? DAY_CLOCK_WITH_YEAR : { ...DAY_CLOCK_WITH_YEAR, timeZone });
|
|
44
50
|
/* All numbers, as a form would print it: `19-9-2026` here, `9/19/2026` in the United States. */
|
|
45
51
|
export const formatNumericDate = (at) => formatDateTime(at, NUMERIC_DATE);
|
|
46
52
|
/* Whether two instants fall on the same day of the same year, in the zone of whoever is reading. */
|
package/dist/format/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { formatAge, formatClock, formatDateTime, formatDay, formatDayClock, formatDayWithYear, formatHour, formatMoment, formatNumericDate, formatWeekdayClock, formatWeekdayDay, isSameDay } from './datetime.ts';
|
|
1
|
+
export { formatAge, formatClock, formatDateTime, formatDay, formatDayClock, formatDayClockWithYear, formatDayWithYear, formatHour, formatMoment, formatNumericDate, formatWeekdayClock, formatWeekdayDay, isSameDay } from './datetime.ts';
|
|
2
2
|
export { formatAgo, formatClockDuration, formatCountdown, formatDuration, formatElapsedShort } from './duration.ts';
|
|
3
|
-
export {
|
|
4
|
-
export { formatBytes, formatDecimal,
|
|
3
|
+
export { formatLocale, labelCollator, setFormatSource, systemLocale, useFormatLocale, type FormatSource } from './locale.ts';
|
|
4
|
+
export { formatBytes, formatDecimal, formatMoney, formatNumber, formatNumeral, formatPercent, formatRounded, formatTokens, formatUsdSignificant } from './number.ts';
|
|
5
5
|
export { FORMAT_LANGUAGE, FORMAT_REGIONS, FORMAT_REGION_CHOICES, FORMAT_SYSTEM, formatRegionFrom, regionName } from './regions.ts';
|
|
6
6
|
export { localTimeZone } from './time-zone.ts';
|
package/dist/format/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* Every number, date, time and duration a person reads, written the way the language and the region they set ask for. */
|
|
2
|
-
export { formatAge, formatClock, formatDateTime, formatDay, formatDayClock, formatDayWithYear, formatHour, formatMoment, formatNumericDate, formatWeekdayClock, formatWeekdayDay, isSameDay } from './datetime.js';
|
|
2
|
+
export { formatAge, formatClock, formatDateTime, formatDay, formatDayClock, formatDayClockWithYear, formatDayWithYear, formatHour, formatMoment, formatNumericDate, formatWeekdayClock, formatWeekdayDay, isSameDay } from './datetime.js';
|
|
3
3
|
export { formatAgo, formatClockDuration, formatCountdown, formatDuration, formatElapsedShort } from './duration.js';
|
|
4
|
-
export {
|
|
5
|
-
export { formatBytes, formatDecimal,
|
|
4
|
+
export { formatLocale, labelCollator, setFormatSource, systemLocale, useFormatLocale } from './locale.js';
|
|
5
|
+
export { formatBytes, formatDecimal, formatMoney, formatNumber, formatNumeral, formatPercent, formatRounded, formatTokens, formatUsdSignificant } from './number.js';
|
|
6
6
|
export { FORMAT_LANGUAGE, FORMAT_REGIONS, FORMAT_REGION_CHOICES, FORMAT_SYSTEM, formatRegionFrom, regionName } from './regions.js';
|
|
7
7
|
export { localTimeZone } from './time-zone.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export type { PopoverPopupProps, PopupVariant, PreviewCardPopupProps } from './p
|
|
|
14
14
|
export type { TabsCountProps, TabsListProps, TabsRootProps, TabsTabProps, TabsValue, TabsVariant } from './tabs/parts.tsx';
|
|
15
15
|
export { AccentSwatches, type AccentSwatchesProps } from './AccentSwatches.tsx';
|
|
16
16
|
export { Banner, type BannerProps, type BannerTone } from './Banner.tsx';
|
|
17
|
-
export { Button, type ButtonProps, type ButtonSize, type ButtonVariant } from './Button.tsx';
|
|
17
|
+
export { Button, type ButtonProps, type ButtonShape, type ButtonSize, type ButtonVariant } from './Button.tsx';
|
|
18
18
|
export { ButtonGroup, type ButtonGroupProps } from './ButtonGroup.tsx';
|
|
19
19
|
export { Checkbox, type CheckboxProps } from './Checkbox.tsx';
|
|
20
20
|
export { ChoiceCards, type Choice, type ChoiceCardsProps } from './ChoiceCards.tsx';
|
|
@@ -56,7 +56,7 @@ export { Switch, type SwitchProps } from './Switch.tsx';
|
|
|
56
56
|
export { TextMenu, type TextMenuProps } from './TextMenu.tsx';
|
|
57
57
|
export { Tile, type TileProps } from './Tile.tsx';
|
|
58
58
|
export { Toasts, type ToastsProps } from './Toasts.tsx';
|
|
59
|
-
export { Tooltip,
|
|
59
|
+
export { Tooltip, type TooltipProps, type TooltipSide } from './Tooltip.tsx';
|
|
60
60
|
export { UIProvider, type UIProviderProps } from './UIProvider.tsx';
|
|
61
61
|
export { Waveform, type WaveformProps } from './Waveform.tsx';
|
|
62
62
|
export { Wipe, type WipeProps } from './Wipe.tsx';
|
|
@@ -70,19 +70,17 @@ export { LoadedComponent, lazyDialog, lazyNamed, onLazyOpenError } from './lazy.
|
|
|
70
70
|
export type { LazyComponent } from './lazy.tsx';
|
|
71
71
|
export { LazyBoundary } from './LazyBoundary.tsx';
|
|
72
72
|
export type { LazyBoundaryProps } from './LazyBoundary.tsx';
|
|
73
|
-
export { UI_NAMESPACE, UI_RESOURCES
|
|
74
|
-
export { startInputModality } from './modality.ts';
|
|
73
|
+
export { UI_NAMESPACE, UI_RESOURCES } from './locales.ts';
|
|
75
74
|
export { isApplePlatform } from './platform.ts';
|
|
76
|
-
export {
|
|
75
|
+
export { prefetcher, type Loader, type WhenIdle } from './prefetch.ts';
|
|
77
76
|
export { selectAllWithin, selectionWithin } from './selection.ts';
|
|
78
77
|
export { EDIT_SHORTCUTS, KEY_SHORTCUTS, formatShortcut, isModHeld, matchesShortcut, shortcut, shortcutParts, type KeyLike, type Shortcut } from './shortcut.ts';
|
|
79
|
-
export {
|
|
78
|
+
export { UNDO_MS, createToastStore, type Toast, type ToastAction, type ToastDeadline, type ToastInput, type ToastKind, type ToastPatch, type ToastStore, type ToastStoreHook } from './toast-store.ts';
|
|
80
79
|
export { useAsyncAction, type AsyncAction } from './useAsyncAction.ts';
|
|
81
80
|
export { clampColumnSize, useColumnResize, type ColumnEdge, type ColumnResize, type ColumnResizeOptions } from './useColumnResize.ts';
|
|
82
81
|
export { useContentSize, type ContentSize } from './useContentSize.ts';
|
|
83
82
|
export { useMeasuredWidth } from './useMeasuredWidth.ts';
|
|
84
83
|
export { useNow, useTickingText } from './useNow.ts';
|
|
85
|
-
export { ZOOM_PRESETS } from './zoom.ts';
|
|
86
84
|
export { useFileTree, type FileTreeOptions } from './file-tree/use-file-tree.ts';
|
|
87
85
|
export type { FileTreeRootProps } from './file-tree/parts.tsx';
|
|
88
86
|
export type { FileTree as FileTreeModel, FileTreeVisibleRow, FileTreeDragAndDropConfig, FileTreeDropContext, FileTreeDropResult } from '@pierre/trees';
|
|
@@ -93,6 +91,6 @@ export type { WorkspaceLayout } from './workspace/context.ts';
|
|
|
93
91
|
export { DocumentTab, TabStrip, type DocumentTabProps, type TabStripProps, type TabStripItem } from './workspace/TabStrip.tsx';
|
|
94
92
|
export { SplitView, type SplitViewProps, type SplitViewInfo, type SplitViewBounds, type SplitDropTarget, type SplitDrag } from './workspace/SplitView.tsx';
|
|
95
93
|
export { DropPreview, type DropPreviewProps } from './workspace/DropPreview.tsx';
|
|
96
|
-
export {
|
|
97
|
-
export { createSplitLayout,
|
|
94
|
+
export { rectDropPreview, sameDropPreview, tabDropPreview, type DropPreviewShape, type TabDrop } from './workspace/preview.ts';
|
|
95
|
+
export { createSplitLayout, updateSplitLayout, splitPanes, type SplitAxis, type SplitSide, type SplitPane, type SplitBranch, type SplitNode, type SplitLayout, type SplitCommand } from './workspace/model.ts';
|
|
98
96
|
export { tabGapAt, type SplitRect, type SplitEdges, type SplitMinimum } from './workspace/geometry.ts';
|
package/dist/index.js
CHANGED
|
@@ -55,7 +55,7 @@ export { Switch } from './Switch.js';
|
|
|
55
55
|
export { TextMenu } from './TextMenu.js';
|
|
56
56
|
export { Tile } from './Tile.js';
|
|
57
57
|
export { Toasts } from './Toasts.js';
|
|
58
|
-
export { Tooltip
|
|
58
|
+
export { Tooltip } from './Tooltip.js';
|
|
59
59
|
export { UIProvider } from './UIProvider.js';
|
|
60
60
|
export { Waveform } from './Waveform.js';
|
|
61
61
|
export { Wipe } from './Wipe.js';
|
|
@@ -66,24 +66,22 @@ export { FILE_TREE_ICONS } from './file-icon.js';
|
|
|
66
66
|
export { cameThroughPortal, isInFloatingLayer } from './floating.js';
|
|
67
67
|
export { LoadedComponent, lazyDialog, lazyNamed, onLazyOpenError } from './lazy.js';
|
|
68
68
|
export { LazyBoundary } from './LazyBoundary.js';
|
|
69
|
-
export { UI_NAMESPACE, UI_RESOURCES
|
|
70
|
-
export { startInputModality } from './modality.js';
|
|
69
|
+
export { UI_NAMESPACE, UI_RESOURCES } from './locales.js';
|
|
71
70
|
export { isApplePlatform } from './platform.js';
|
|
72
|
-
export {
|
|
71
|
+
export { prefetcher } from './prefetch.js';
|
|
73
72
|
export { selectAllWithin, selectionWithin } from './selection.js';
|
|
74
73
|
export { EDIT_SHORTCUTS, KEY_SHORTCUTS, formatShortcut, isModHeld, matchesShortcut, shortcut, shortcutParts } from './shortcut.js';
|
|
75
|
-
export {
|
|
74
|
+
export { UNDO_MS, createToastStore } from './toast-store.js';
|
|
76
75
|
export { useAsyncAction } from './useAsyncAction.js';
|
|
77
76
|
export { clampColumnSize, useColumnResize } from './useColumnResize.js';
|
|
78
77
|
export { useContentSize } from './useContentSize.js';
|
|
79
78
|
export { useMeasuredWidth } from './useMeasuredWidth.js';
|
|
80
79
|
export { useNow, useTickingText } from './useNow.js';
|
|
81
|
-
export { ZOOM_PRESETS } from './zoom.js';
|
|
82
80
|
export { useFileTree } from './file-tree/use-file-tree.js';
|
|
83
81
|
export { Workspace } from './workspace/Workspace.js';
|
|
84
82
|
export { DocumentTab, TabStrip } from './workspace/TabStrip.js';
|
|
85
83
|
export { SplitView } from './workspace/SplitView.js';
|
|
86
84
|
export { DropPreview } from './workspace/DropPreview.js';
|
|
87
|
-
export {
|
|
88
|
-
export { createSplitLayout,
|
|
85
|
+
export { rectDropPreview, sameDropPreview, tabDropPreview } from './workspace/preview.js';
|
|
86
|
+
export { createSplitLayout, updateSplitLayout, splitPanes } from './workspace/model.js';
|
|
89
87
|
export { tabGapAt } from './workspace/geometry.js';
|
|
@@ -77,7 +77,5 @@ export type SplitCommand = {
|
|
|
77
77
|
export declare function createSplitLayout(views?: string[], paneId?: string): SplitLayout;
|
|
78
78
|
export declare function splitPanes(node: SplitNode): SplitPane[];
|
|
79
79
|
export declare function findSplitNode(node: SplitNode, id: string): SplitNode | undefined;
|
|
80
|
-
/** Repairs persisted data at the application boundary; view payloads never enter the layout. */
|
|
81
|
-
export declare function normalizeSplitLayout(value: unknown): SplitLayout;
|
|
82
80
|
/** Invalid or stale commands are no-ops. IDs for new nodes come from the caller. */
|
|
83
81
|
export declare function updateSplitLayout(layout: SplitLayout, command: SplitCommand): SplitLayout;
|
package/dist/workspace/model.js
CHANGED
|
@@ -40,75 +40,6 @@ function flattenSplits(node) {
|
|
|
40
40
|
}
|
|
41
41
|
return { ...node, children, sizes: shares(sizes, children.length) };
|
|
42
42
|
}
|
|
43
|
-
/** Repairs persisted data at the application boundary; view payloads never enter the layout. */
|
|
44
|
-
export function normalizeSplitLayout(value) {
|
|
45
|
-
const ids = new Set();
|
|
46
|
-
const views = new Set();
|
|
47
|
-
const seen = new Set();
|
|
48
|
-
let serial = 0;
|
|
49
|
-
const nextId = (candidate) => {
|
|
50
|
-
let id = typeof candidate === 'string' && candidate.length > 0 ? candidate : `pane-${serial++}`;
|
|
51
|
-
while (ids.has(id)) {
|
|
52
|
-
id = `pane-${serial++}`;
|
|
53
|
-
}
|
|
54
|
-
ids.add(id);
|
|
55
|
-
return id;
|
|
56
|
-
};
|
|
57
|
-
const read = (input, depth) => {
|
|
58
|
-
if (input === null || typeof input !== 'object' || seen.has(input) || depth > 64) {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
seen.add(input);
|
|
62
|
-
const row = input;
|
|
63
|
-
if (row.type === 'pane') {
|
|
64
|
-
const items = Array.isArray(row.views)
|
|
65
|
-
? row.views.filter((view) => {
|
|
66
|
-
if (typeof view !== 'string' || view.length === 0 || views.has(view)) {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
views.add(view);
|
|
70
|
-
return true;
|
|
71
|
-
})
|
|
72
|
-
: [];
|
|
73
|
-
return {
|
|
74
|
-
type: 'pane',
|
|
75
|
-
id: nextId(row.id),
|
|
76
|
-
views: items,
|
|
77
|
-
active: typeof row.active === 'string' && items.includes(row.active) ? row.active : (items[0] ?? null)
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
if (row.type !== 'split' || !Array.isArray(row.children)) {
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
const children = [];
|
|
84
|
-
const sizes = [];
|
|
85
|
-
row.children.forEach((child, i) => {
|
|
86
|
-
const result = read(child, depth + 1);
|
|
87
|
-
if (result) {
|
|
88
|
-
children.push(result);
|
|
89
|
-
sizes.push(Array.isArray(row.sizes) ? Number(row.sizes[i]) : 1);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
if (children.length < 2) {
|
|
93
|
-
return children[0] ?? null;
|
|
94
|
-
}
|
|
95
|
-
return {
|
|
96
|
-
type: 'split',
|
|
97
|
-
id: nextId(row.id),
|
|
98
|
-
axis: row.axis === 'vertical' ? 'vertical' : 'horizontal',
|
|
99
|
-
children,
|
|
100
|
-
sizes: shares(sizes, children.length)
|
|
101
|
-
};
|
|
102
|
-
};
|
|
103
|
-
const input = value !== null && typeof value === 'object' ? value : {};
|
|
104
|
-
const root = flattenSplits(read(input.root, 0) ?? createSplitLayout().root);
|
|
105
|
-
const panes = splitPanes(root);
|
|
106
|
-
return {
|
|
107
|
-
root,
|
|
108
|
-
focused: panes.find((pane) => pane.id === input.focused)?.id ?? panes[0].id,
|
|
109
|
-
maximized: panes.find((pane) => pane.id === input.maximized)?.id ?? null
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
43
|
function replace(node, id, transform) {
|
|
113
44
|
if (node.id === id) {
|
|
114
45
|
return transform(node);
|
package/package.json
CHANGED
package/src/Button.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import clsx from 'clsx';
|
|
|
3
3
|
|
|
4
4
|
export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger' | 'danger-outline' | 'positive' | 'inverse';
|
|
5
5
|
export type ButtonSize = 'xs' | 'sm' | 'md';
|
|
6
|
+
export type ButtonShape = 'default' | 'pill';
|
|
6
7
|
|
|
7
8
|
const VARIANT: Record<ButtonVariant, string> = {
|
|
8
9
|
// A filled button darkens under the pointer. Its color is the meaning, so no token of its own for hover.
|
|
@@ -24,19 +25,28 @@ const SIZE: Record<ButtonSize, string> = {
|
|
|
24
25
|
md: 'h-8 px-3'
|
|
25
26
|
};
|
|
26
27
|
|
|
28
|
+
/* A pill is one size of its own, for a control that sits in a round row, so it takes the place of `size`. */
|
|
29
|
+
const SHAPE: Record<ButtonShape, string> = {
|
|
30
|
+
default: 'rounded-md text-xs',
|
|
31
|
+
pill: 'h-9 rounded-full px-3.5 text-sm'
|
|
32
|
+
};
|
|
33
|
+
|
|
27
34
|
export interface ButtonProps extends ComponentProps<'button'> {
|
|
28
35
|
variant?: ButtonVariant;
|
|
29
36
|
size?: ButtonSize;
|
|
37
|
+
/* `pill` is 36 pixels tall and fully rounded, and ignores `size`. */
|
|
38
|
+
shape?: ButtonShape;
|
|
30
39
|
/* Makes it a link that looks like a button, a real anchor, so it opens the way links open. */
|
|
31
40
|
href?: string;
|
|
32
41
|
}
|
|
33
42
|
|
|
34
43
|
/* Every button with a word in it. A button that is only an icon is an `IconButton`, which is a square, not a label. */
|
|
35
|
-
export function Button({ variant = 'ghost', size = 'md', className, href, type = 'button', ref, ...rest }: ButtonProps) {
|
|
44
|
+
export function Button({ variant = 'ghost', size = 'md', shape = 'default', className, href, type = 'button', ref, ...rest }: ButtonProps) {
|
|
36
45
|
const classes = clsx(
|
|
37
|
-
'inline-flex shrink-0 items-center justify-center gap-1.5
|
|
46
|
+
'inline-flex shrink-0 items-center justify-center gap-1.5 font-medium disabled:opacity-50',
|
|
47
|
+
SHAPE[shape],
|
|
38
48
|
VARIANT[variant],
|
|
39
|
-
SIZE[size],
|
|
49
|
+
shape === 'default' && SIZE[size],
|
|
40
50
|
className
|
|
41
51
|
);
|
|
42
52
|
if (href) {
|
package/src/EmptyState.tsx
CHANGED
|
@@ -12,8 +12,6 @@ export interface EmptyStateProps {
|
|
|
12
12
|
icon?: LucideIcon | ReactElement<{ size?: number }>;
|
|
13
13
|
/* A `Spinner` in place of the icon, for a state that is still loading. */
|
|
14
14
|
busy?: boolean;
|
|
15
|
-
/** @deprecated Turns a Lucide icon; use `busy`, which draws a `Spinner` in its place. */
|
|
16
|
-
spin?: boolean;
|
|
17
15
|
/* A heading above the sentence, for a state that is an outcome rather than a list with nothing in it. */
|
|
18
16
|
title?: ReactNode;
|
|
19
17
|
/* One sentence. What is missing, and what puts something there. */
|
|
@@ -25,7 +23,7 @@ export interface EmptyStateProps {
|
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
/* What a list, a canvas or a thread shows before it holds anything. */
|
|
28
|
-
export function EmptyState({ icon, busy = false,
|
|
26
|
+
export function EmptyState({ icon, busy = false, title, children, action, className, ref }: EmptyStateProps) {
|
|
29
27
|
return (
|
|
30
28
|
<div ref={ref} className={clsx('flex flex-col items-center justify-center gap-2 px-6 py-8 text-center', className)}>
|
|
31
29
|
{(busy || icon) && (
|
|
@@ -35,7 +33,7 @@ export function EmptyState({ icon, busy = false, spin = false, title, children,
|
|
|
35
33
|
) : isValidElement(icon) ? (
|
|
36
34
|
cloneElement(icon, { size: ICON_SIZE })
|
|
37
35
|
) : (
|
|
38
|
-
icon && <Icon icon={icon} size={ICON_SIZE}
|
|
36
|
+
icon && <Icon icon={icon} size={ICON_SIZE} />
|
|
39
37
|
)}
|
|
40
38
|
</span>
|
|
41
39
|
)}
|
package/src/IconButton.tsx
CHANGED
|
@@ -24,8 +24,6 @@ export interface IconButtonProps extends Omit<ComponentProps<'button'>, 'childre
|
|
|
24
24
|
active?: boolean;
|
|
25
25
|
/* A `Spinner` in place of the icon, for a step that is running. */
|
|
26
26
|
busy?: boolean;
|
|
27
|
-
/** @deprecated Turns the icon; use `busy`, which draws a `Spinner` in its place. */
|
|
28
|
-
spin?: boolean;
|
|
29
27
|
/* Classes for the icon rather than the button: a tone of its own, a turn, a fill. The size stays the button's. */
|
|
30
28
|
iconClassName?: string;
|
|
31
29
|
/* Drawn after the icon: a count, or a word beside it together with `w-auto`. */
|
|
@@ -44,7 +42,6 @@ export function IconButton({
|
|
|
44
42
|
size = 'md',
|
|
45
43
|
active = false,
|
|
46
44
|
busy = false,
|
|
47
|
-
spin = false,
|
|
48
45
|
iconClassName,
|
|
49
46
|
render,
|
|
50
47
|
className,
|
|
@@ -65,7 +62,7 @@ export function IconButton({
|
|
|
65
62
|
{busy ? (
|
|
66
63
|
<Spinner size={ICON_BUTTON_ICON_SIZE[size]} className={iconClassName} />
|
|
67
64
|
) : (
|
|
68
|
-
icon !== undefined && <Icon icon={icon} size={ICON_BUTTON_ICON_SIZE[size]} className={
|
|
65
|
+
icon !== undefined && <Icon icon={icon} size={ICON_BUTTON_ICON_SIZE[size]} className={iconClassName} />
|
|
69
66
|
)}
|
|
70
67
|
{children}
|
|
71
68
|
</>
|
package/src/PanelEmpty.tsx
CHANGED
|
@@ -9,8 +9,6 @@ export interface PanelEmptyProps {
|
|
|
9
9
|
icon?: LucideIcon;
|
|
10
10
|
/* A `Spinner` in place of the icon, for a panel that is still loading. */
|
|
11
11
|
busy?: boolean;
|
|
12
|
-
/** @deprecated Turns the icon; use `busy`, which draws a `Spinner` in its place. */
|
|
13
|
-
spin?: boolean;
|
|
14
12
|
action?: ReactNode;
|
|
15
13
|
/* The sunken ground, for a box that has none behind it yet (a card on a canvas) rather than a panel that does. */
|
|
16
14
|
sunken?: boolean;
|
|
@@ -25,11 +23,11 @@ export interface PanelEmptyProps {
|
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
/* What a panel or a card shows while it holds nothing: a sentence, one icon, at most one button. */
|
|
28
|
-
export function PanelEmpty({ header, icon, busy = false,
|
|
26
|
+
export function PanelEmpty({ header, icon, busy = false, action, sunken = false, fill = 'grow', children, className, ref }: PanelEmptyProps) {
|
|
29
27
|
return (
|
|
30
28
|
<div ref={ref} className={clsx('grid min-h-0 place-items-center', fill === 'full' ? 'h-full' : 'grow', sunken && 'bg-surface-sunken', className)}>
|
|
31
29
|
{header}
|
|
32
|
-
<EmptyState icon={icon} busy={busy}
|
|
30
|
+
<EmptyState icon={icon} busy={busy} action={action}>
|
|
33
31
|
{children}
|
|
34
32
|
</EmptyState>
|
|
35
33
|
</div>
|
package/src/format/datetime.ts
CHANGED
|
@@ -30,10 +30,14 @@ const DAY: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric' };
|
|
|
30
30
|
const DAY_WITH_YEAR: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric', year: 'numeric' };
|
|
31
31
|
const WEEKDAY_DAY: Intl.DateTimeFormatOptions = { weekday: 'short', month: 'short', day: 'numeric' };
|
|
32
32
|
const DAY_CLOCK: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' };
|
|
33
|
+
const DAY_CLOCK_WITH_YEAR: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit' };
|
|
33
34
|
const NUMERIC_DATE: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'numeric', day: 'numeric' };
|
|
34
35
|
|
|
35
|
-
/*
|
|
36
|
-
|
|
36
|
+
/*
|
|
37
|
+
* `08:05`, or `08:05 AM` in a region that counts to twelve. The clock functions below take the `timeZone`
|
|
38
|
+
* to write in, such as `UTC` for a moment whose parts are given and mean the same wall clock anywhere.
|
|
39
|
+
*/
|
|
40
|
+
export const formatClock = (at: Date | number, timeZone?: string): string => formatDateTime(at, timeZone === undefined ? CLOCK : { ...CLOCK, timeZone });
|
|
37
41
|
|
|
38
42
|
/* A time far enough off that its day is part of the answer: `Sat 08:05`. */
|
|
39
43
|
export const formatWeekdayClock = (at: Date | number): string => formatDateTime(at, WEEKDAY_CLOCK);
|
|
@@ -42,16 +46,22 @@ export const formatWeekdayClock = (at: Date | number): string => formatDateTime(
|
|
|
42
46
|
export const formatHour = (at: Date | number): string => formatDateTime(at, HOUR);
|
|
43
47
|
|
|
44
48
|
/* `19 Sep`, the short date a row falls back to once "days ago" stops meaning anything. */
|
|
45
|
-
export const formatDay = (at: Date | number): string => formatDateTime(at, DAY);
|
|
49
|
+
export const formatDay = (at: Date | number, timeZone?: string): string => formatDateTime(at, timeZone === undefined ? DAY : { ...DAY, timeZone });
|
|
46
50
|
|
|
47
51
|
/* The same date with the year, for anything older than the one we are in. */
|
|
48
|
-
export const formatDayWithYear = (at: Date | number): string =>
|
|
52
|
+
export const formatDayWithYear = (at: Date | number, timeZone?: string): string =>
|
|
53
|
+
formatDateTime(at, timeZone === undefined ? DAY_WITH_YEAR : { ...DAY_WITH_YEAR, timeZone });
|
|
49
54
|
|
|
50
55
|
/* `Sat 19 Sep`. */
|
|
51
56
|
export const formatWeekdayDay = (at: Date | number): string => formatDateTime(at, WEEKDAY_DAY);
|
|
52
57
|
|
|
53
58
|
/* `19 Sep, 08:05`: a moment that is not today, to the minute. */
|
|
54
|
-
export const formatDayClock = (at: Date | number): string =>
|
|
59
|
+
export const formatDayClock = (at: Date | number, timeZone?: string): string =>
|
|
60
|
+
formatDateTime(at, timeZone === undefined ? DAY_CLOCK : { ...DAY_CLOCK, timeZone });
|
|
61
|
+
|
|
62
|
+
/* `19 Sep 2026, 08:05`: the same moment with the year, for one that is not in the year we are in. */
|
|
63
|
+
export const formatDayClockWithYear = (at: Date | number, timeZone?: string): string =>
|
|
64
|
+
formatDateTime(at, timeZone === undefined ? DAY_CLOCK_WITH_YEAR : { ...DAY_CLOCK_WITH_YEAR, timeZone });
|
|
55
65
|
|
|
56
66
|
/* All numbers, as a form would print it: `19-9-2026` here, `9/19/2026` in the United States. */
|
|
57
67
|
export const formatNumericDate = (at: Date | number): string => formatDateTime(at, NUMERIC_DATE);
|
package/src/format/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export {
|
|
|
6
6
|
formatDateTime,
|
|
7
7
|
formatDay,
|
|
8
8
|
formatDayClock,
|
|
9
|
+
formatDayClockWithYear,
|
|
9
10
|
formatDayWithYear,
|
|
10
11
|
formatHour,
|
|
11
12
|
formatMoment,
|
|
@@ -15,11 +16,10 @@ export {
|
|
|
15
16
|
isSameDay
|
|
16
17
|
} from './datetime.ts';
|
|
17
18
|
export { formatAgo, formatClockDuration, formatCountdown, formatDuration, formatElapsedShort } from './duration.ts';
|
|
18
|
-
export {
|
|
19
|
+
export { formatLocale, labelCollator, setFormatSource, systemLocale, useFormatLocale, type FormatSource } from './locale.ts';
|
|
19
20
|
export {
|
|
20
21
|
formatBytes,
|
|
21
22
|
formatDecimal,
|
|
22
|
-
formatFixed,
|
|
23
23
|
formatMoney,
|
|
24
24
|
formatNumber,
|
|
25
25
|
formatNumeral,
|
package/src/index.ts
CHANGED
|
@@ -29,7 +29,7 @@ export type { TabsCountProps, TabsListProps, TabsRootProps, TabsTabProps, TabsVa
|
|
|
29
29
|
|
|
30
30
|
export { AccentSwatches, type AccentSwatchesProps } from './AccentSwatches.tsx';
|
|
31
31
|
export { Banner, type BannerProps, type BannerTone } from './Banner.tsx';
|
|
32
|
-
export { Button, type ButtonProps, type ButtonSize, type ButtonVariant } from './Button.tsx';
|
|
32
|
+
export { Button, type ButtonProps, type ButtonShape, type ButtonSize, type ButtonVariant } from './Button.tsx';
|
|
33
33
|
export { ButtonGroup, type ButtonGroupProps } from './ButtonGroup.tsx';
|
|
34
34
|
export { Checkbox, type CheckboxProps } from './Checkbox.tsx';
|
|
35
35
|
export { ChoiceCards, type Choice, type ChoiceCardsProps } from './ChoiceCards.tsx';
|
|
@@ -71,7 +71,7 @@ export { Switch, type SwitchProps } from './Switch.tsx';
|
|
|
71
71
|
export { TextMenu, type TextMenuProps } from './TextMenu.tsx';
|
|
72
72
|
export { Tile, type TileProps } from './Tile.tsx';
|
|
73
73
|
export { Toasts, type ToastsProps } from './Toasts.tsx';
|
|
74
|
-
export { Tooltip,
|
|
74
|
+
export { Tooltip, type TooltipProps, type TooltipSide } from './Tooltip.tsx';
|
|
75
75
|
export { UIProvider, type UIProviderProps } from './UIProvider.tsx';
|
|
76
76
|
export { Waveform, type WaveformProps } from './Waveform.tsx';
|
|
77
77
|
export { Wipe, type WipeProps } from './Wipe.tsx';
|
|
@@ -86,17 +86,14 @@ export { LoadedComponent, lazyDialog, lazyNamed, onLazyOpenError } from './lazy.
|
|
|
86
86
|
export type { LazyComponent } from './lazy.tsx';
|
|
87
87
|
export { LazyBoundary } from './LazyBoundary.tsx';
|
|
88
88
|
export type { LazyBoundaryProps } from './LazyBoundary.tsx';
|
|
89
|
-
export { UI_NAMESPACE, UI_RESOURCES
|
|
90
|
-
export { startInputModality } from './modality.ts';
|
|
89
|
+
export { UI_NAMESPACE, UI_RESOURCES } from './locales.ts';
|
|
91
90
|
export { isApplePlatform } from './platform.ts';
|
|
92
|
-
export {
|
|
91
|
+
export { prefetcher, type Loader, type WhenIdle } from './prefetch.ts';
|
|
93
92
|
export { selectAllWithin, selectionWithin } from './selection.ts';
|
|
94
93
|
export { EDIT_SHORTCUTS, KEY_SHORTCUTS, formatShortcut, isModHeld, matchesShortcut, shortcut, shortcutParts, type KeyLike, type Shortcut } from './shortcut.ts';
|
|
95
94
|
export {
|
|
96
|
-
SUCCESS_MS,
|
|
97
95
|
UNDO_MS,
|
|
98
96
|
createToastStore,
|
|
99
|
-
elapsedOf,
|
|
100
97
|
type Toast,
|
|
101
98
|
type ToastAction,
|
|
102
99
|
type ToastDeadline,
|
|
@@ -111,7 +108,6 @@ export { clampColumnSize, useColumnResize, type ColumnEdge, type ColumnResize, t
|
|
|
111
108
|
export { useContentSize, type ContentSize } from './useContentSize.ts';
|
|
112
109
|
export { useMeasuredWidth } from './useMeasuredWidth.ts';
|
|
113
110
|
export { useNow, useTickingText } from './useNow.ts';
|
|
114
|
-
export { ZOOM_PRESETS } from './zoom.ts';
|
|
115
111
|
|
|
116
112
|
export { useFileTree, type FileTreeOptions } from './file-tree/use-file-tree.ts';
|
|
117
113
|
export type { FileTreeRootProps } from './file-tree/parts.tsx';
|
|
@@ -133,10 +129,9 @@ export type { WorkspaceLayout } from './workspace/context.ts';
|
|
|
133
129
|
export { DocumentTab, TabStrip, type DocumentTabProps, type TabStripProps, type TabStripItem } from './workspace/TabStrip.tsx';
|
|
134
130
|
export { SplitView, type SplitViewProps, type SplitViewInfo, type SplitViewBounds, type SplitDropTarget, type SplitDrag } from './workspace/SplitView.tsx';
|
|
135
131
|
export { DropPreview, type DropPreviewProps } from './workspace/DropPreview.tsx';
|
|
136
|
-
export {
|
|
132
|
+
export { rectDropPreview, sameDropPreview, tabDropPreview, type DropPreviewShape, type TabDrop } from './workspace/preview.ts';
|
|
137
133
|
export {
|
|
138
134
|
createSplitLayout,
|
|
139
|
-
normalizeSplitLayout,
|
|
140
135
|
updateSplitLayout,
|
|
141
136
|
splitPanes,
|
|
142
137
|
type SplitAxis,
|
package/src/workspace/model.ts
CHANGED
|
@@ -84,76 +84,6 @@ function flattenSplits(node: SplitNode): SplitNode {
|
|
|
84
84
|
return { ...node, children, sizes: shares(sizes, children.length) };
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
/** Repairs persisted data at the application boundary; view payloads never enter the layout. */
|
|
88
|
-
export function normalizeSplitLayout(value: unknown): SplitLayout {
|
|
89
|
-
const ids = new Set<string>();
|
|
90
|
-
const views = new Set<string>();
|
|
91
|
-
const seen = new Set<object>();
|
|
92
|
-
let serial = 0;
|
|
93
|
-
const nextId = (candidate: unknown): string => {
|
|
94
|
-
let id = typeof candidate === 'string' && candidate.length > 0 ? candidate : `pane-${serial++}`;
|
|
95
|
-
while (ids.has(id)) {
|
|
96
|
-
id = `pane-${serial++}`;
|
|
97
|
-
}
|
|
98
|
-
ids.add(id);
|
|
99
|
-
return id;
|
|
100
|
-
};
|
|
101
|
-
const read = (input: unknown, depth: number): SplitNode | null => {
|
|
102
|
-
if (input === null || typeof input !== 'object' || seen.has(input) || depth > 64) {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
seen.add(input);
|
|
106
|
-
const row = input as Record<string, unknown>;
|
|
107
|
-
if (row.type === 'pane') {
|
|
108
|
-
const items = Array.isArray(row.views)
|
|
109
|
-
? row.views.filter((view): view is string => {
|
|
110
|
-
if (typeof view !== 'string' || view.length === 0 || views.has(view)) {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
views.add(view);
|
|
114
|
-
return true;
|
|
115
|
-
})
|
|
116
|
-
: [];
|
|
117
|
-
return {
|
|
118
|
-
type: 'pane',
|
|
119
|
-
id: nextId(row.id),
|
|
120
|
-
views: items,
|
|
121
|
-
active: typeof row.active === 'string' && items.includes(row.active) ? row.active : (items[0] ?? null)
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
if (row.type !== 'split' || !Array.isArray(row.children)) {
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
const children: SplitNode[] = [];
|
|
128
|
-
const sizes: number[] = [];
|
|
129
|
-
row.children.forEach((child, i) => {
|
|
130
|
-
const result = read(child, depth + 1);
|
|
131
|
-
if (result) {
|
|
132
|
-
children.push(result);
|
|
133
|
-
sizes.push(Array.isArray(row.sizes) ? Number(row.sizes[i]) : 1);
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
if (children.length < 2) {
|
|
137
|
-
return children[0] ?? null;
|
|
138
|
-
}
|
|
139
|
-
return {
|
|
140
|
-
type: 'split',
|
|
141
|
-
id: nextId(row.id),
|
|
142
|
-
axis: row.axis === 'vertical' ? 'vertical' : 'horizontal',
|
|
143
|
-
children,
|
|
144
|
-
sizes: shares(sizes, children.length)
|
|
145
|
-
};
|
|
146
|
-
};
|
|
147
|
-
const input = value !== null && typeof value === 'object' ? (value as Record<string, unknown>) : {};
|
|
148
|
-
const root = flattenSplits(read(input.root, 0) ?? createSplitLayout().root);
|
|
149
|
-
const panes = splitPanes(root);
|
|
150
|
-
return {
|
|
151
|
-
root,
|
|
152
|
-
focused: panes.find((pane) => pane.id === input.focused)?.id ?? panes[0]!.id,
|
|
153
|
-
maximized: panes.find((pane) => pane.id === input.maximized)?.id ?? null
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
87
|
function replace(node: SplitNode, id: string, transform: (node: SplitNode) => SplitNode | null): SplitNode | null {
|
|
158
88
|
if (node.id === id) {
|
|
159
89
|
return transform(node);
|