@inkra/react 0.1.0 → 0.3.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/README.md +186 -1
- package/dist/PencilIcon.js +14 -16
- package/dist/PencilLine.js +18 -12
- package/dist/PencilText.js +192 -59
- package/dist/alert-dialog/index.d.ts +64 -0
- package/dist/alert-dialog/index.js +111 -0
- package/dist/audio-player/index.d.ts +60 -0
- package/dist/audio-player/index.js +101 -0
- package/dist/carousel/index.d.ts +51 -0
- package/dist/carousel/index.js +290 -0
- package/dist/choice-grid/index.d.ts +38 -0
- package/dist/choice-grid/index.js +85 -0
- package/dist/color-picker/color.d.ts +15 -0
- package/dist/color-picker/color.js +55 -0
- package/dist/color-picker/index.d.ts +31 -0
- package/dist/color-picker/index.js +69 -0
- package/dist/command/index.d.ts +45 -0
- package/dist/command/index.js +39 -0
- package/dist/context-menu/index.d.ts +76 -0
- package/dist/context-menu/index.js +241 -0
- package/dist/data-table/index.d.ts +47 -0
- package/dist/data-table/index.js +91 -0
- package/dist/date-picker/index.d.ts +50 -0
- package/dist/date-picker/index.js +180 -0
- package/dist/display/ProgressRing.d.ts +6 -0
- package/dist/display/ProgressRing.js +146 -0
- package/dist/display/index.d.ts +27 -0
- package/dist/display/index.js +18 -2
- package/dist/dockable-panel/index.d.ts +41 -0
- package/dist/dockable-panel/index.js +253 -0
- package/dist/dockable-panel/model.d.ts +21 -0
- package/dist/dockable-panel/model.js +61 -0
- package/dist/drawer/index.d.ts +63 -0
- package/dist/drawer/index.js +88 -0
- package/dist/dropdown-menu/index.d.ts +1 -1
- package/dist/dropdown-menu/index.js +3 -2
- package/dist/editable/index.d.ts +53 -0
- package/dist/editable/index.js +162 -0
- package/dist/empty/index.d.ts +21 -0
- package/dist/empty/index.js +23 -0
- package/dist/flip/index.d.ts +49 -0
- package/dist/flip/index.js +138 -0
- package/dist/form/EditablePencilText.js +188 -143
- package/dist/form/Input.js +1 -1
- package/dist/form/Textarea.d.ts +12 -0
- package/dist/form/Textarea.js +83 -4
- package/dist/form/index.d.ts +1 -1
- package/dist/heatmap/index.d.ts +59 -0
- package/dist/heatmap/index.js +248 -0
- package/dist/hotkey/index.d.ts +37 -0
- package/dist/hotkey/index.js +130 -0
- package/dist/hotkey/model.d.ts +27 -0
- package/dist/hotkey/model.js +97 -0
- package/dist/hover-card/index.d.ts +33 -0
- package/dist/hover-card/index.js +141 -0
- package/dist/image-cropper/index.d.ts +82 -0
- package/dist/image-cropper/index.js +261 -0
- package/dist/image-cropper/model.d.ts +42 -0
- package/dist/image-cropper/model.js +94 -0
- package/dist/image-viewer/index.d.ts +79 -0
- package/dist/image-viewer/index.js +220 -0
- package/dist/image-viewer/model.d.ts +9 -0
- package/dist/image-viewer/model.js +18 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +47 -0
- package/dist/input-number/index.d.ts +6 -0
- package/dist/input-number/index.js +126 -15
- package/dist/input-number/model.d.ts +13 -0
- package/dist/input-number/model.js +50 -0
- package/dist/internal/cache.d.ts +1 -1
- package/dist/internal/cache.js +1 -1
- package/dist/internal/renderTokens.d.ts +10 -0
- package/dist/internal/renderTokens.js +13 -0
- package/dist/internal/surfaceRenderer.d.ts +14 -6
- package/dist/internal/surfaceRenderer.js +15 -12
- package/dist/internal/textRenderer.d.ts +1 -0
- package/dist/internal/textRenderer.js +33 -7
- package/dist/internal/usePencilSurface.js +16 -8
- package/dist/internal/useSelectionDrawing.js +15 -11
- package/dist/journey-map/MapScale.js +3 -1
- package/dist/journey-map/index.js +3 -1
- package/dist/keypad/index.d.ts +23 -0
- package/dist/keypad/index.js +63 -0
- package/dist/keypad/model.d.ts +20 -0
- package/dist/keypad/model.js +33 -0
- package/dist/layout/index.d.ts +63 -0
- package/dist/layout/index.js +62 -0
- package/dist/log/index.d.ts +45 -0
- package/dist/log/index.js +159 -0
- package/dist/log/model.d.ts +16 -0
- package/dist/log/model.js +13 -0
- package/dist/node-graph/index.d.ts +123 -0
- package/dist/node-graph/index.js +304 -0
- package/dist/number-ticker/index.d.ts +31 -0
- package/dist/number-ticker/index.js +135 -0
- package/dist/number-ticker/model.d.ts +41 -0
- package/dist/number-ticker/model.js +163 -0
- package/dist/number-ticker/precision.d.ts +7 -0
- package/dist/number-ticker/precision.js +134 -0
- package/dist/overflow-list/index.d.ts +39 -0
- package/dist/overflow-list/index.js +230 -0
- package/dist/overflow-list/layout.d.ts +19 -0
- package/dist/overflow-list/layout.js +44 -0
- package/dist/overlay/Overlay.d.ts +1 -1
- package/dist/overlay/Overlay.js +4 -2
- package/dist/overlay/Popover.d.ts +1 -1
- package/dist/overlay/Popover.js +3 -2
- package/dist/overlay/themeBridge.d.ts +12 -1
- package/dist/overlay/themeBridge.js +27 -1
- package/dist/pagination/index.d.ts +23 -0
- package/dist/pagination/index.js +41 -0
- package/dist/pagination/model.d.ts +4 -0
- package/dist/pagination/model.js +42 -0
- package/dist/pan-zoom/index.d.ts +71 -0
- package/dist/pan-zoom/index.js +175 -0
- package/dist/radar/index.d.ts +56 -0
- package/dist/radar/index.js +220 -0
- package/dist/range-slider/index.d.ts +16 -0
- package/dist/range-slider/index.js +22 -0
- package/dist/rating/index.d.ts +28 -0
- package/dist/rating/index.js +127 -0
- package/dist/resizable/index.d.ts +32 -0
- package/dist/resizable/index.js +50 -0
- package/dist/scroll-area/index.d.ts +36 -0
- package/dist/scroll-area/index.js +44 -0
- package/dist/segmented-progress/SegmentedProgress.d.ts +17 -0
- package/dist/segmented-progress/SegmentedProgress.js +42 -0
- package/dist/slider/index.js +1 -1
- package/dist/slot/index.d.ts +24 -0
- package/dist/slot/index.js +29 -0
- package/dist/sortable/index.d.ts +50 -0
- package/dist/sortable/index.js +283 -0
- package/dist/sortable/model.d.ts +30 -0
- package/dist/sortable/model.js +64 -0
- package/dist/sparkline/index.d.ts +19 -0
- package/dist/sparkline/index.js +240 -0
- package/dist/sparkline/model.d.ts +16 -0
- package/dist/sparkline/model.js +28 -0
- package/dist/steps/index.d.ts +16 -0
- package/dist/steps/index.js +37 -0
- package/dist/styles.css +4957 -269
- package/dist/tag-input/index.d.ts +69 -0
- package/dist/tag-input/index.js +325 -0
- package/dist/tag-input/model.d.ts +14 -0
- package/dist/tag-input/model.js +38 -0
- package/dist/time-picker/index.d.ts +15 -0
- package/dist/time-picker/index.js +168 -0
- package/dist/timeline/index.d.ts +14 -0
- package/dist/timeline/index.js +16 -0
- package/dist/toast/index.d.ts +27 -0
- package/dist/toast/index.js +36 -0
- package/dist/toggle-group/index.d.ts +17 -0
- package/dist/toggle-group/index.js +26 -0
- package/dist/tokens.css +10 -9
- package/dist/tooltip/Tooltip.d.ts +1 -1
- package/dist/tooltip/Tooltip.js +3 -2
- package/dist/transfer/index.d.ts +64 -0
- package/dist/transfer/index.js +174 -0
- package/dist/transfer/model.d.ts +12 -0
- package/dist/transfer/model.js +33 -0
- package/dist/tree/index.d.ts +39 -0
- package/dist/tree/index.js +36 -0
- package/dist/tree-select/index.d.ts +59 -0
- package/dist/tree-select/index.js +142 -0
- package/dist/tree-select/model.d.ts +31 -0
- package/dist/tree-select/model.js +135 -0
- package/dist/typewriter/index.d.ts +21 -0
- package/dist/typewriter/index.js +101 -0
- package/dist/typewriter/model.d.ts +7 -0
- package/dist/typewriter/model.js +45 -0
- package/dist/upload/index.d.ts +59 -0
- package/dist/upload/index.js +230 -0
- package/dist/virtual-list/index.d.ts +44 -0
- package/dist/virtual-list/index.js +129 -0
- package/dist/virtual-list/model.d.ts +7 -0
- package/dist/virtual-list/model.js +27 -0
- package/package.json +23 -3
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import type { KeypadAction, KeypadKey } from './model.js';
|
|
3
|
+
export interface KeypadPressDetails {
|
|
4
|
+
action: KeypadAction;
|
|
5
|
+
key: KeypadKey;
|
|
6
|
+
previousValue: string;
|
|
7
|
+
nextValue: string;
|
|
8
|
+
}
|
|
9
|
+
export interface KeypadProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange' | 'onKeyPress'> {
|
|
10
|
+
layout: readonly (readonly KeypadKey[])[];
|
|
11
|
+
value?: string;
|
|
12
|
+
defaultValue?: string;
|
|
13
|
+
onValueChange?: (value: string, details: KeypadPressDetails) => void;
|
|
14
|
+
onKeyPress?: (details: KeypadPressDetails) => void;
|
|
15
|
+
onConfirm?: (value: string, details: KeypadPressDetails) => void;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
maxLength?: number;
|
|
18
|
+
label?: string;
|
|
19
|
+
emptyContent?: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
export declare const Keypad: import("react").ForwardRefExoticComponent<KeypadProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
22
|
+
export { applyKeypadKey, getKeypadAction, getKeypadColumnCount } from './model.js';
|
|
23
|
+
export type { KeypadAction, KeypadKey, KeypadKeyVariant, KeypadValueResult } from './model.js';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useId, useRef, useState } from 'react';
|
|
3
|
+
import { PencilSurface } from '../PencilSurface.js';
|
|
4
|
+
import { PencilText } from '../PencilText.js';
|
|
5
|
+
import { applyKeypadKey, getKeypadAction, getKeypadColumnCount } from './model.js';
|
|
6
|
+
function keyLabel(key) {
|
|
7
|
+
if (key.ariaLabel)
|
|
8
|
+
return key.ariaLabel;
|
|
9
|
+
if (typeof key.label === 'string' || typeof key.label === 'number')
|
|
10
|
+
return String(key.label);
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
function keyVariant(key) {
|
|
14
|
+
if (key.variant)
|
|
15
|
+
return key.variant;
|
|
16
|
+
const action = getKeypadAction(key);
|
|
17
|
+
if (action === 'confirm')
|
|
18
|
+
return 'confirm';
|
|
19
|
+
if (action === 'delete')
|
|
20
|
+
return 'secondary';
|
|
21
|
+
return 'default';
|
|
22
|
+
}
|
|
23
|
+
export const Keypad = forwardRef(function Keypad({ 'aria-label': ariaLabel, className, defaultValue = '', disabled = false, emptyContent = null, label = 'Keypad', layout, maxLength, onConfirm, onKeyPress, onValueChange, value, ...props }, ref) {
|
|
24
|
+
const id = useId();
|
|
25
|
+
const controlled = value !== undefined;
|
|
26
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
|
|
27
|
+
const currentValue = controlled ? value : uncontrolledValue;
|
|
28
|
+
const valueRef = useRef(currentValue);
|
|
29
|
+
valueRef.current = currentValue;
|
|
30
|
+
const columns = getKeypadColumnCount(layout);
|
|
31
|
+
const classes = ['inkra-keypad', className].filter(Boolean).join(' ');
|
|
32
|
+
const style = { '--inkra-keypad-columns': columns, ...props.style };
|
|
33
|
+
const press = (key) => {
|
|
34
|
+
const previousValue = valueRef.current;
|
|
35
|
+
const result = applyKeypadKey(previousValue, key, maxLength);
|
|
36
|
+
const details = {
|
|
37
|
+
action: getKeypadAction(key),
|
|
38
|
+
key,
|
|
39
|
+
previousValue,
|
|
40
|
+
nextValue: result.nextValue,
|
|
41
|
+
};
|
|
42
|
+
if (result.changed) {
|
|
43
|
+
if (!controlled) {
|
|
44
|
+
valueRef.current = result.nextValue;
|
|
45
|
+
setUncontrolledValue(result.nextValue);
|
|
46
|
+
}
|
|
47
|
+
onValueChange?.(result.nextValue, details);
|
|
48
|
+
}
|
|
49
|
+
onKeyPress?.(details);
|
|
50
|
+
if (details.action === 'confirm')
|
|
51
|
+
onConfirm?.(previousValue, details);
|
|
52
|
+
};
|
|
53
|
+
return (_jsx("div", { ...props, "aria-disabled": disabled || undefined, "aria-label": ariaLabel ?? label, className: classes, "data-disabled": disabled || undefined, ref: ref, role: "group", style: style, children: layout.length === 0 ? emptyContent : layout.map((row, rowIndex) => (_jsx("div", { className: "inkra-keypad__row", children: row.map(key => {
|
|
54
|
+
const action = getKeypadAction(key);
|
|
55
|
+
const atLimit = action === 'input' && maxLength !== undefined && !applyKeypadKey(currentValue, key, maxLength).changed;
|
|
56
|
+
const keyDisabled = disabled || key.disabled === true || atLimit;
|
|
57
|
+
const accessibleName = keyLabel(key);
|
|
58
|
+
return (_jsxs("button", { "aria-label": accessibleName, className: "inkra-keypad__key", "data-action": action, "data-key-id": key.id, "data-variant": keyVariant(key), disabled: keyDisabled, onClick: () => press(key), style: { '--inkra-keypad-span': Math.max(1, Math.floor(key.span ?? 1)) }, type: "button", children: [_jsx(PencilSurface, { as: "span", className: "inkra-keypad__key-surface", radius: 6, seed: `inkra-keypad-${id}-${key.id}`, variant: keyVariant(key) === 'confirm' ? 'action' : keyVariant(key) === 'danger' ? 'danger' : keyVariant(key) === 'secondary' ? 'quiet' : 'outline', children: _jsx("span", { className: "inkra-keypad__key-label", children: typeof key.label === 'string' || typeof key.label === 'number'
|
|
59
|
+
? _jsx(PencilText, { seed: `inkra-keypad-${id}-${key.id}-label`, children: String(key.label) })
|
|
60
|
+
: key.label }) }), _jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-keypad__focus", seed: `inkra-keypad-${id}-${key.id}-focus`, variant: "outline" })] }, key.id));
|
|
61
|
+
}) }, `row-${rowIndex}`))) }));
|
|
62
|
+
});
|
|
63
|
+
export { applyKeypadKey, getKeypadAction, getKeypadColumnCount } from './model.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export type KeypadAction = 'input' | 'delete' | 'confirm' | 'custom';
|
|
3
|
+
export type KeypadKeyVariant = 'default' | 'secondary' | 'confirm' | 'danger';
|
|
4
|
+
export interface KeypadKey {
|
|
5
|
+
id: string;
|
|
6
|
+
label: ReactNode;
|
|
7
|
+
action?: KeypadAction;
|
|
8
|
+
value?: string;
|
|
9
|
+
ariaLabel?: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
span?: number;
|
|
12
|
+
variant?: KeypadKeyVariant;
|
|
13
|
+
}
|
|
14
|
+
export interface KeypadValueResult {
|
|
15
|
+
nextValue: string;
|
|
16
|
+
changed: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function getKeypadAction(key: KeypadKey): KeypadAction;
|
|
19
|
+
export declare function applyKeypadKey(value: string, key: KeypadKey, maxLength?: number): KeypadValueResult;
|
|
20
|
+
export declare function getKeypadColumnCount(layout: readonly (readonly KeypadKey[])[]): number;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
function removeLastGrapheme(value) {
|
|
2
|
+
if (value.length === 0)
|
|
3
|
+
return value;
|
|
4
|
+
const Segmenter = Intl.Segmenter;
|
|
5
|
+
if (Segmenter) {
|
|
6
|
+
const segments = Array.from(new Segmenter(undefined, { granularity: 'grapheme' }).segment(value));
|
|
7
|
+
const last = segments[segments.length - 1];
|
|
8
|
+
return last ? value.slice(0, last.index) : '';
|
|
9
|
+
}
|
|
10
|
+
return Array.from(value).slice(0, -1).join('');
|
|
11
|
+
}
|
|
12
|
+
export function getKeypadAction(key) {
|
|
13
|
+
return key.action ?? 'input';
|
|
14
|
+
}
|
|
15
|
+
export function applyKeypadKey(value, key, maxLength) {
|
|
16
|
+
const action = getKeypadAction(key);
|
|
17
|
+
if (action === 'delete') {
|
|
18
|
+
const nextValue = removeLastGrapheme(value);
|
|
19
|
+
return { nextValue, changed: nextValue !== value };
|
|
20
|
+
}
|
|
21
|
+
if (action !== 'input')
|
|
22
|
+
return { nextValue: value, changed: false };
|
|
23
|
+
const addition = key.value ?? (typeof key.label === 'string' || typeof key.label === 'number' ? String(key.label) : '');
|
|
24
|
+
if (addition.length === 0)
|
|
25
|
+
return { nextValue: value, changed: false };
|
|
26
|
+
if (maxLength !== undefined && value.length + addition.length > maxLength) {
|
|
27
|
+
return { nextValue: value, changed: false };
|
|
28
|
+
}
|
|
29
|
+
return { nextValue: value + addition, changed: true };
|
|
30
|
+
}
|
|
31
|
+
export function getKeypadColumnCount(layout) {
|
|
32
|
+
return Math.max(1, ...layout.map(row => row.reduce((total, key) => total + Math.max(1, Math.floor(key.span ?? 1)), 0)));
|
|
33
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { CSSProperties, HTMLAttributes } from 'react';
|
|
2
|
+
export type LayoutSpace = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12 | 16;
|
|
3
|
+
export type LayoutElement = 'div' | 'section' | 'article' | 'nav' | 'header' | 'footer' | 'main' | 'aside' | 'ul' | 'ol' | 'li';
|
|
4
|
+
export type LayoutAlign = 'start' | 'center' | 'end' | 'stretch' | 'baseline';
|
|
5
|
+
export type LayoutJustify = 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';
|
|
6
|
+
interface LayoutElementProps extends HTMLAttributes<HTMLElement> {
|
|
7
|
+
as?: LayoutElement;
|
|
8
|
+
}
|
|
9
|
+
export interface FlexProps extends LayoutElementProps {
|
|
10
|
+
direction?: 'row' | 'column' | 'row-reverse' | 'column-reverse';
|
|
11
|
+
gap?: LayoutSpace;
|
|
12
|
+
align?: LayoutAlign;
|
|
13
|
+
justify?: LayoutJustify;
|
|
14
|
+
wrap?: boolean;
|
|
15
|
+
inline?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare const Flex: import("react").ForwardRefExoticComponent<FlexProps & import("react").RefAttributes<HTMLElement>>;
|
|
18
|
+
export type StackProps = FlexProps;
|
|
19
|
+
export declare const Stack: import("react").ForwardRefExoticComponent<FlexProps & import("react").RefAttributes<HTMLElement>>;
|
|
20
|
+
export interface GridColumns {
|
|
21
|
+
base?: number;
|
|
22
|
+
sm?: number;
|
|
23
|
+
md?: number;
|
|
24
|
+
lg?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface GridProps extends LayoutElementProps {
|
|
27
|
+
columns?: number | GridColumns;
|
|
28
|
+
gap?: LayoutSpace;
|
|
29
|
+
rowGap?: LayoutSpace;
|
|
30
|
+
columnGap?: LayoutSpace;
|
|
31
|
+
align?: LayoutAlign;
|
|
32
|
+
}
|
|
33
|
+
export declare const Grid: import("react").ForwardRefExoticComponent<GridProps & import("react").RefAttributes<HTMLElement>>;
|
|
34
|
+
export interface ContainerProps extends LayoutElementProps {
|
|
35
|
+
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
36
|
+
padding?: LayoutSpace;
|
|
37
|
+
centered?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export declare const Container: import("react").ForwardRefExoticComponent<ContainerProps & import("react").RefAttributes<HTMLElement>>;
|
|
40
|
+
export interface LayoutProps extends LayoutElementProps {
|
|
41
|
+
gap?: LayoutSpace;
|
|
42
|
+
collapseAt?: 'sm' | 'md' | 'never';
|
|
43
|
+
}
|
|
44
|
+
export interface LayoutBodyProps extends LayoutElementProps {
|
|
45
|
+
gap?: LayoutSpace;
|
|
46
|
+
}
|
|
47
|
+
export interface LayoutRegionProps extends LayoutElementProps {
|
|
48
|
+
padding?: LayoutSpace;
|
|
49
|
+
divided?: boolean;
|
|
50
|
+
}
|
|
51
|
+
export interface LayoutSidebarProps extends LayoutRegionProps {
|
|
52
|
+
width?: CSSProperties['width'];
|
|
53
|
+
}
|
|
54
|
+
export type LayoutContentProps = Omit<LayoutRegionProps, 'divided'>;
|
|
55
|
+
export declare const Layout: Readonly<{
|
|
56
|
+
Root: import("react").ForwardRefExoticComponent<LayoutProps & import("react").RefAttributes<HTMLElement>>;
|
|
57
|
+
Header: import("react").ForwardRefExoticComponent<LayoutRegionProps & import("react").RefAttributes<HTMLElement>>;
|
|
58
|
+
Body: import("react").ForwardRefExoticComponent<LayoutBodyProps & import("react").RefAttributes<HTMLElement>>;
|
|
59
|
+
Sidebar: import("react").ForwardRefExoticComponent<LayoutSidebarProps & import("react").RefAttributes<HTMLElement>>;
|
|
60
|
+
Content: import("react").ForwardRefExoticComponent<LayoutContentProps & import("react").RefAttributes<HTMLElement>>;
|
|
61
|
+
Footer: import("react").ForwardRefExoticComponent<LayoutRegionProps & import("react").RefAttributes<HTMLElement>>;
|
|
62
|
+
}>;
|
|
63
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { createElement, forwardRef } from 'react';
|
|
3
|
+
import { PencilLine } from '../PencilLine.js';
|
|
4
|
+
const Box = forwardRef(function Box({ as = 'div', ...props }, ref) {
|
|
5
|
+
return createElement(as, { ...props, ref });
|
|
6
|
+
});
|
|
7
|
+
const classes = (...values) => values.filter(Boolean).join(' ');
|
|
8
|
+
const space = (value) => `var(--inkra-space-${value}, ${value * 4}px)`;
|
|
9
|
+
const justifyValues = { start: 'flex-start', center: 'center', end: 'flex-end', between: 'space-between', around: 'space-around', evenly: 'space-evenly' };
|
|
10
|
+
const alignValues = { start: 'flex-start', center: 'center', end: 'flex-end', stretch: 'stretch', baseline: 'baseline' };
|
|
11
|
+
export const Flex = forwardRef(function Flex({ as: Element = 'div', direction = 'row', gap = 3, align = 'stretch', justify = 'start', wrap = false, inline = false, className, style, ...props }, ref) {
|
|
12
|
+
return _jsx(Box, { as: Element, ...props, ref: ref, className: classes('inkra-flex', className), style: { display: inline ? 'inline-flex' : 'flex', flexDirection: direction,
|
|
13
|
+
gap: space(gap), alignItems: alignValues[align], justifyContent: justifyValues[justify],
|
|
14
|
+
flexWrap: wrap ? 'wrap' : 'nowrap', ...style } });
|
|
15
|
+
});
|
|
16
|
+
export const Stack = forwardRef(function Stack({ direction = 'column', className, ...props }, ref) {
|
|
17
|
+
return _jsx(Flex, { ...props, ref: ref, direction: direction, className: classes('inkra-stack', className) });
|
|
18
|
+
});
|
|
19
|
+
const columnCount = (value, fallback) => value === undefined || !Number.isFinite(value) ? fallback : Math.min(12, Math.max(1, Math.trunc(value)));
|
|
20
|
+
export const Grid = forwardRef(function Grid({ as: Element = 'div', columns = 1, gap = 3, rowGap = gap, columnGap = gap, align = 'stretch', className, style, ...props }, ref) {
|
|
21
|
+
const responsive = typeof columns === 'number' ? { base: columns } : columns;
|
|
22
|
+
const base = columnCount(responsive.base, 1);
|
|
23
|
+
const sm = columnCount(responsive.sm, base);
|
|
24
|
+
const md = columnCount(responsive.md, sm);
|
|
25
|
+
const lg = columnCount(responsive.lg, md);
|
|
26
|
+
const resolvedStyle = {
|
|
27
|
+
'--inkra-grid-base': base, '--inkra-grid-sm': sm, '--inkra-grid-md': md, '--inkra-grid-lg': lg,
|
|
28
|
+
rowGap: space(rowGap), columnGap: space(columnGap), alignItems: alignValues[align], ...style,
|
|
29
|
+
};
|
|
30
|
+
return _jsx(Box, { as: Element, ...props, ref: ref, className: classes('inkra-grid', className), style: resolvedStyle });
|
|
31
|
+
});
|
|
32
|
+
const containerWidths = { sm: '640px', md: '768px', lg: '1024px', xl: '1280px', full: 'none' };
|
|
33
|
+
export const Container = forwardRef(function Container({ as: Element = 'div', size = 'lg', padding = 5, centered = true, className, style, ...props }, ref) {
|
|
34
|
+
return _jsx(Box, { as: Element, ...props, ref: ref, className: classes('inkra-container', className), style: { maxWidth: containerWidths[size], paddingInline: space(padding),
|
|
35
|
+
marginInline: centered ? 'auto' : 0, ...style } });
|
|
36
|
+
});
|
|
37
|
+
const LayoutRoot = forwardRef(function LayoutRoot({ as: Element = 'div', gap = 0, collapseAt = 'sm', className, style, ...props }, ref) {
|
|
38
|
+
return _jsx(Box, { as: Element, ...props, ref: ref, className: classes('inkra-layout', className), "data-collapse-at": collapseAt, style: { gap: space(gap), ...style } });
|
|
39
|
+
});
|
|
40
|
+
const LayoutBody = forwardRef(function LayoutBody({ as: Element = 'div', gap = 0, className, style, ...props }, ref) {
|
|
41
|
+
return _jsx(Box, { as: Element, ...props, ref: ref, className: classes('inkra-layout__body', className), style: { gap: space(gap), ...style } });
|
|
42
|
+
});
|
|
43
|
+
const LayoutHeader = forwardRef(function LayoutHeader({ as: Element = 'header', padding = 5, divided = false, className, style, children, ...props }, ref) {
|
|
44
|
+
return _jsxs(Box, { as: Element, ...props, ref: ref, className: classes('inkra-layout__header', className), style: { padding: space(padding), ...style }, children: [children, divided && _jsx(PencilLine, { className: "inkra-layout__divider" })] });
|
|
45
|
+
});
|
|
46
|
+
const LayoutFooter = forwardRef(function LayoutFooter({ as: Element = 'footer', padding = 5, divided = false, className, style, children, ...props }, ref) {
|
|
47
|
+
return _jsxs(Box, { as: Element, ...props, ref: ref, className: classes('inkra-layout__footer', className), style: { padding: space(padding), ...style }, children: [divided && _jsx(PencilLine, { className: "inkra-layout__divider" }), children] });
|
|
48
|
+
});
|
|
49
|
+
const LayoutSidebar = forwardRef(function LayoutSidebar({ as: Element = 'aside', width = 240, padding = 5, divided = false, className, style, children, ...props }, ref) {
|
|
50
|
+
const resolvedStyle = {
|
|
51
|
+
'--inkra-layout-sidebar-width': typeof width === 'number' ? `${width}px` : width,
|
|
52
|
+
padding: space(padding), ...style,
|
|
53
|
+
};
|
|
54
|
+
return _jsxs(Box, { as: Element, ...props, ref: ref, className: classes('inkra-layout__sidebar', className), style: resolvedStyle, children: [children, divided && _jsxs(_Fragment, { children: [_jsx(PencilLine, { className: "inkra-layout__sidebar-divider", orientation: "vertical" }), _jsx(PencilLine, { className: "inkra-layout__sidebar-divider-mobile" })] })] });
|
|
55
|
+
});
|
|
56
|
+
const LayoutContent = forwardRef(function LayoutContent({ as: Element = 'div', padding = 5, className, style, ...props }, ref) {
|
|
57
|
+
return _jsx(Box, { as: Element, ...props, ref: ref, className: classes('inkra-layout__content', className), style: { padding: space(padding), ...style } });
|
|
58
|
+
});
|
|
59
|
+
export const Layout = Object.freeze({
|
|
60
|
+
Root: LayoutRoot, Header: LayoutHeader, Body: LayoutBody,
|
|
61
|
+
Sidebar: LayoutSidebar, Content: LayoutContent, Footer: LayoutFooter,
|
|
62
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
export type LogLevelTone = 'neutral' | 'info' | 'success' | 'warning' | 'danger';
|
|
3
|
+
export interface LogLevelDefinition {
|
|
4
|
+
value: string;
|
|
5
|
+
label: ReactNode;
|
|
6
|
+
tone?: LogLevelTone;
|
|
7
|
+
}
|
|
8
|
+
export interface LogEntry {
|
|
9
|
+
/** Stable and unique within this Log. Changing an id replaces that record. */
|
|
10
|
+
id: string;
|
|
11
|
+
/** Entries render in the exact order supplied by the items array. */
|
|
12
|
+
level?: string;
|
|
13
|
+
time?: ReactNode;
|
|
14
|
+
dateTime?: string;
|
|
15
|
+
icon?: ReactNode;
|
|
16
|
+
content: ReactNode;
|
|
17
|
+
}
|
|
18
|
+
export interface LogLabels {
|
|
19
|
+
filters: string;
|
|
20
|
+
allLevels: string;
|
|
21
|
+
pause: string;
|
|
22
|
+
resume: string;
|
|
23
|
+
latest: string;
|
|
24
|
+
empty: string;
|
|
25
|
+
noMatches: string;
|
|
26
|
+
list: string;
|
|
27
|
+
}
|
|
28
|
+
export interface LogProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
29
|
+
items: readonly LogEntry[];
|
|
30
|
+
levels?: readonly LogLevelDefinition[];
|
|
31
|
+
filteredLevels?: readonly string[];
|
|
32
|
+
defaultFilteredLevels?: readonly string[];
|
|
33
|
+
onFilteredLevelsChange?: (levels: readonly string[]) => void;
|
|
34
|
+
paused?: boolean;
|
|
35
|
+
defaultPaused?: boolean;
|
|
36
|
+
onPausedChange?: (paused: boolean) => void;
|
|
37
|
+
showLevelFilter?: boolean;
|
|
38
|
+
showFollowControl?: boolean;
|
|
39
|
+
nearBottomThreshold?: number;
|
|
40
|
+
empty?: ReactNode;
|
|
41
|
+
noMatches?: ReactNode;
|
|
42
|
+
labels?: Partial<LogLabels>;
|
|
43
|
+
viewportProps?: Omit<HTMLAttributes<HTMLDivElement>, 'children'>;
|
|
44
|
+
}
|
|
45
|
+
export declare const Log: import("react").ForwardRefExoticComponent<LogProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useCallback, useMemo, useRef, useState, } from 'react';
|
|
3
|
+
import { Button } from '../Button.js';
|
|
4
|
+
import { PencilIcon } from '../PencilIcon.js';
|
|
5
|
+
import { PencilLine } from '../PencilLine.js';
|
|
6
|
+
import { PencilSurface } from '../PencilSurface.js';
|
|
7
|
+
import { PencilText } from '../PencilText.js';
|
|
8
|
+
import { useBrowserLayoutEffect } from '../internal/useBrowserLayoutEffect.js';
|
|
9
|
+
import { isNearLogBottom, shouldFollowLogUpdate } from './model.js';
|
|
10
|
+
const defaultLabels = {
|
|
11
|
+
filters: 'Filter log levels',
|
|
12
|
+
allLevels: 'All',
|
|
13
|
+
pause: 'Pause automatic follow',
|
|
14
|
+
resume: 'Resume automatic follow',
|
|
15
|
+
latest: 'Go to latest record',
|
|
16
|
+
empty: 'No records yet',
|
|
17
|
+
noMatches: 'No records match the selected levels',
|
|
18
|
+
list: 'Log records',
|
|
19
|
+
};
|
|
20
|
+
const defaultTones = {
|
|
21
|
+
debug: 'neutral',
|
|
22
|
+
info: 'info',
|
|
23
|
+
success: 'success',
|
|
24
|
+
warning: 'warning',
|
|
25
|
+
error: 'danger',
|
|
26
|
+
};
|
|
27
|
+
const pausePaths = [
|
|
28
|
+
{ d: 'M8 5v14' },
|
|
29
|
+
{ d: 'M16 5v14' },
|
|
30
|
+
];
|
|
31
|
+
const followPaths = [
|
|
32
|
+
{ d: 'M12 5v14' },
|
|
33
|
+
{ d: 'm6 13 6 6 6-6' },
|
|
34
|
+
];
|
|
35
|
+
function classes(...values) {
|
|
36
|
+
return values.filter(Boolean).join(' ');
|
|
37
|
+
}
|
|
38
|
+
function renderText(value, className) {
|
|
39
|
+
return typeof value === 'string' ? _jsx(PencilText, { className: className, children: value }) : value;
|
|
40
|
+
}
|
|
41
|
+
function normalizedThreshold(value) {
|
|
42
|
+
return Number.isFinite(value) ? Math.max(0, value) : 32;
|
|
43
|
+
}
|
|
44
|
+
export const Log = forwardRef(function Log({ 'aria-label': ariaLabel, className, defaultFilteredLevels, defaultPaused = false, empty, filteredLevels, items, labels: labelOverrides, levels, nearBottomThreshold, noMatches, onFilteredLevelsChange, onPausedChange, paused, showFollowControl = true, showLevelFilter = true, viewportProps, ...props }, ref) {
|
|
45
|
+
const labels = { ...defaultLabels, ...labelOverrides };
|
|
46
|
+
const resolvedLevels = useMemo(() => {
|
|
47
|
+
if (levels)
|
|
48
|
+
return levels;
|
|
49
|
+
const seen = new Set();
|
|
50
|
+
return items.flatMap((item) => {
|
|
51
|
+
if (!item.level || seen.has(item.level))
|
|
52
|
+
return [];
|
|
53
|
+
seen.add(item.level);
|
|
54
|
+
return [{ value: item.level, label: item.level, tone: defaultTones[item.level] ?? 'neutral' }];
|
|
55
|
+
});
|
|
56
|
+
}, [items, levels]);
|
|
57
|
+
const availableLevels = useMemo(() => new Set(resolvedLevels.map(level => level.value)), [resolvedLevels]);
|
|
58
|
+
const [uncontrolledLevels, setUncontrolledLevels] = useState(defaultFilteredLevels);
|
|
59
|
+
const selectedLevels = (filteredLevels ?? uncontrolledLevels)?.filter(level => availableLevels.has(level));
|
|
60
|
+
const allLevelsSelected = selectedLevels == null
|
|
61
|
+
|| (selectedLevels.length === resolvedLevels.length && resolvedLevels.every(level => selectedLevels.includes(level.value)));
|
|
62
|
+
const [uncontrolledPaused, setUncontrolledPaused] = useState(defaultPaused);
|
|
63
|
+
const resolvedPaused = paused ?? uncontrolledPaused;
|
|
64
|
+
const [nearBottom, setNearBottom] = useState(true);
|
|
65
|
+
const viewportRef = useRef(null);
|
|
66
|
+
const previousItemIdsRef = useRef(null);
|
|
67
|
+
const previousVisibleIdsRef = useRef(null);
|
|
68
|
+
const previousPausedRef = useRef(resolvedPaused);
|
|
69
|
+
const nearBottomRef = useRef(true);
|
|
70
|
+
const threshold = normalizedThreshold(nearBottomThreshold);
|
|
71
|
+
const visibleItems = selectedLevels == null
|
|
72
|
+
? items
|
|
73
|
+
: items.filter(item => item.level != null && selectedLevels.includes(item.level));
|
|
74
|
+
const itemIds = items.map(item => item.id);
|
|
75
|
+
const visibleIds = visibleItems.map(item => item.id);
|
|
76
|
+
const scrollToLatest = useCallback((behavior = 'smooth') => {
|
|
77
|
+
const viewport = viewportRef.current;
|
|
78
|
+
if (!viewport)
|
|
79
|
+
return;
|
|
80
|
+
viewport.scrollTo({ top: viewport.scrollHeight, behavior });
|
|
81
|
+
nearBottomRef.current = true;
|
|
82
|
+
setNearBottom(true);
|
|
83
|
+
}, []);
|
|
84
|
+
useBrowserLayoutEffect(() => {
|
|
85
|
+
const previousItemIds = previousItemIdsRef.current;
|
|
86
|
+
const previousVisibleIds = previousVisibleIdsRef.current;
|
|
87
|
+
const initial = previousItemIds == null;
|
|
88
|
+
const resumed = previousPausedRef.current && !resolvedPaused;
|
|
89
|
+
if (resumed || shouldFollowLogUpdate({
|
|
90
|
+
initial,
|
|
91
|
+
nearBottom: nearBottomRef.current,
|
|
92
|
+
paused: resolvedPaused,
|
|
93
|
+
previousItemIds: previousItemIds ?? [],
|
|
94
|
+
nextItemIds: itemIds,
|
|
95
|
+
previousVisibleIds: previousVisibleIds ?? [],
|
|
96
|
+
nextVisibleIds: visibleIds,
|
|
97
|
+
}))
|
|
98
|
+
scrollToLatest('auto');
|
|
99
|
+
previousItemIdsRef.current = itemIds;
|
|
100
|
+
previousVisibleIdsRef.current = visibleIds;
|
|
101
|
+
previousPausedRef.current = resolvedPaused;
|
|
102
|
+
}, [itemIds.join('\u0000'), resolvedPaused, scrollToLatest, visibleIds.join('\u0000')]);
|
|
103
|
+
function changePaused(nextPaused) {
|
|
104
|
+
if (paused === undefined)
|
|
105
|
+
setUncontrolledPaused(nextPaused);
|
|
106
|
+
onPausedChange?.(nextPaused);
|
|
107
|
+
}
|
|
108
|
+
function changeLevels(nextLevels) {
|
|
109
|
+
if (filteredLevels === undefined)
|
|
110
|
+
setUncontrolledLevels(nextLevels);
|
|
111
|
+
onFilteredLevelsChange?.(nextLevels ?? resolvedLevels.map(level => level.value));
|
|
112
|
+
}
|
|
113
|
+
function handleScroll(event) {
|
|
114
|
+
viewportProps?.onScroll?.(event);
|
|
115
|
+
const viewport = event.currentTarget;
|
|
116
|
+
const nextNearBottom = isNearLogBottom(viewport, threshold);
|
|
117
|
+
nearBottomRef.current = nextNearBottom;
|
|
118
|
+
setNearBottom(current => current === nextNearBottom ? current : nextNearBottom);
|
|
119
|
+
}
|
|
120
|
+
function handleViewportKeyDown(event) {
|
|
121
|
+
viewportProps?.onKeyDown?.(event);
|
|
122
|
+
if (event.defaultPrevented || event.target !== event.currentTarget)
|
|
123
|
+
return;
|
|
124
|
+
if (event.key === 'Home') {
|
|
125
|
+
event.preventDefault();
|
|
126
|
+
event.currentTarget.scrollTo({ top: 0 });
|
|
127
|
+
}
|
|
128
|
+
else if (event.key === 'End') {
|
|
129
|
+
event.preventDefault();
|
|
130
|
+
scrollToLatest();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const hasFilter = !allLevelsSelected;
|
|
134
|
+
const isEmpty = items.length === 0;
|
|
135
|
+
const viewportLabel = viewportProps?.['aria-label'] ?? ariaLabel ?? labels.list;
|
|
136
|
+
const viewportClassName = classes('inkra-log__viewport', viewportProps?.className);
|
|
137
|
+
const viewportTabIndex = viewportProps?.tabIndex ?? 0;
|
|
138
|
+
const levelMap = new Map(resolvedLevels.map(level => [level.value, level]));
|
|
139
|
+
return (_jsxs(PencilSurface, { ...props, as: "div", className: classes('inkra-log', className), "data-paused": resolvedPaused || undefined, "data-toolbar": ((showLevelFilter && resolvedLevels.length > 0) || showFollowControl) || undefined, ref: ref, variant: "paper", children: [(showLevelFilter && resolvedLevels.length > 0) || showFollowControl ? _jsxs("div", { className: "inkra-log__toolbar", children: [showLevelFilter && resolvedLevels.length > 0 ? _jsxs("div", { "aria-label": labels.filters, className: "inkra-log__filters", role: "group", children: [_jsx(Button, { "aria-pressed": !hasFilter, size: "sm", variant: !hasFilter ? 'secondary' : 'ghost', onClick: () => changeLevels(undefined), children: labels.allLevels }), resolvedLevels.map(level => {
|
|
140
|
+
const selected = allLevelsSelected || selectedLevels?.includes(level.value) === true;
|
|
141
|
+
return _jsx(Button, { "aria-pressed": selected, "data-level": level.value, "data-tone": level.tone ?? defaultTones[level.value] ?? 'neutral', size: "sm", variant: selected ? 'secondary' : 'ghost', onClick: () => {
|
|
142
|
+
const active = selectedLevels ?? resolvedLevels.map(option => option.value);
|
|
143
|
+
changeLevels(selected
|
|
144
|
+
? active.filter(value => value !== level.value)
|
|
145
|
+
: [...active, level.value]);
|
|
146
|
+
}, children: level.label }, level.value);
|
|
147
|
+
})] }) : null, showFollowControl ? _jsx(Button, { "aria-pressed": resolvedPaused, className: "inkra-log__follow-control", size: "sm", startIcon: _jsx(PencilIcon, { paths: resolvedPaused ? followPaths : pausePaths, size: 18, sourceSize: 24 }), variant: "outline", onClick: () => changePaused(!resolvedPaused), children: resolvedPaused ? labels.resume : labels.pause }) : null, _jsx(PencilLine, { className: "inkra-log__toolbar-divider", seed: "inkra-log-toolbar-divider" })] }) : null, _jsx("div", { ...viewportProps, "aria-label": viewportLabel, className: viewportClassName, onKeyDown: handleViewportKeyDown, onScroll: handleScroll, ref: viewportRef, role: "region", tabIndex: viewportTabIndex, children: visibleItems.length > 0 ? _jsx("ol", { className: "inkra-log__list", children: visibleItems.map((item, index) => {
|
|
148
|
+
const definition = item.level ? levelMap.get(item.level) : undefined;
|
|
149
|
+
const tone = definition?.tone ?? (item.level ? defaultTones[item.level] : undefined) ?? 'neutral';
|
|
150
|
+
return _jsxs("li", { className: "inkra-log__item", "data-has-icon": item.icon != null || undefined, "data-level": item.level, "data-tone": tone, children: [index > 0 ? _jsx(PencilLine, { className: "inkra-log__item-divider", seed: `inkra-log-item-${item.id}-divider` }) : null, item.icon == null ? null : _jsx("span", { "aria-hidden": "true", className: "inkra-log__icon", children: item.icon }), _jsxs("div", { className: "inkra-log__record", children: [item.time == null && item.level == null ? null : _jsxs("div", { className: "inkra-log__meta", children: [item.time == null ? null : item.dateTime
|
|
151
|
+
? _jsx("time", { dateTime: item.dateTime, children: renderText(item.time) })
|
|
152
|
+
: _jsx("span", { children: renderText(item.time) }), item.level == null ? null : _jsx("span", { className: "inkra-log__level", children: renderText(definition?.label ?? item.level) })] }), _jsx("div", { className: "inkra-log__content", children: renderText(item.content) })] })] }, item.id);
|
|
153
|
+
}) }) : _jsx("div", { className: "inkra-log__empty", children: renderText(isEmpty ? (empty ?? labels.empty) : (noMatches ?? labels.noMatches)) }) }), (!nearBottom || resolvedPaused) && visibleItems.length > 0 ? _jsx("div", { className: "inkra-log__latest", children: _jsx(Button, { size: "sm", startIcon: _jsx(PencilIcon, { paths: followPaths, size: 18, sourceSize: 24 }), variant: "secondary", onClick: () => {
|
|
154
|
+
if (resolvedPaused)
|
|
155
|
+
changePaused(false);
|
|
156
|
+
else
|
|
157
|
+
scrollToLatest();
|
|
158
|
+
}, children: resolvedPaused ? labels.resume : labels.latest }) }) : null] }));
|
|
159
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface LogScrollMetrics {
|
|
2
|
+
scrollHeight: number;
|
|
3
|
+
scrollTop: number;
|
|
4
|
+
clientHeight: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function isNearLogBottom(metrics: LogScrollMetrics, threshold?: number): boolean;
|
|
7
|
+
export declare function isLogAppend(previous: readonly string[], next: readonly string[]): boolean;
|
|
8
|
+
export declare function shouldFollowLogUpdate({ initial, nearBottom, paused, previousItemIds, nextItemIds, previousVisibleIds, nextVisibleIds, }: {
|
|
9
|
+
initial: boolean;
|
|
10
|
+
nearBottom: boolean;
|
|
11
|
+
paused: boolean;
|
|
12
|
+
previousItemIds: readonly string[];
|
|
13
|
+
nextItemIds: readonly string[];
|
|
14
|
+
previousVisibleIds: readonly string[];
|
|
15
|
+
nextVisibleIds: readonly string[];
|
|
16
|
+
}): boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function isNearLogBottom(metrics, threshold = 32) {
|
|
2
|
+
const safeThreshold = Number.isFinite(threshold) ? Math.max(0, threshold) : 32;
|
|
3
|
+
return metrics.scrollHeight - metrics.scrollTop - metrics.clientHeight <= safeThreshold;
|
|
4
|
+
}
|
|
5
|
+
export function isLogAppend(previous, next) {
|
|
6
|
+
return previous.length < next.length && previous.every((id, index) => id === next[index]);
|
|
7
|
+
}
|
|
8
|
+
export function shouldFollowLogUpdate({ initial, nearBottom, paused, previousItemIds, nextItemIds, previousVisibleIds, nextVisibleIds, }) {
|
|
9
|
+
if (paused || !nearBottom)
|
|
10
|
+
return false;
|
|
11
|
+
return initial
|
|
12
|
+
|| (isLogAppend(previousItemIds, nextItemIds) && isLogAppend(previousVisibleIds, nextVisibleIds));
|
|
13
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { Connection, Edge, EdgeChange, FitViewOptions, Node, NodeChange, OnConnect, Viewport } from '@xyflow/react';
|
|
2
|
+
import type { HTMLAttributes, ReactElement, ReactNode, RefAttributes } from 'react';
|
|
3
|
+
export type NodeGraphPortPosition = 'top' | 'right' | 'bottom' | 'left';
|
|
4
|
+
export interface NodeGraphPortDefinition {
|
|
5
|
+
id: string;
|
|
6
|
+
type: 'source' | 'target';
|
|
7
|
+
position?: NodeGraphPortPosition;
|
|
8
|
+
label: string;
|
|
9
|
+
connectable?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface NodeGraphNodeData extends Record<string, unknown> {
|
|
12
|
+
label?: ReactNode;
|
|
13
|
+
description?: ReactNode;
|
|
14
|
+
content?: ReactNode;
|
|
15
|
+
ports?: NodeGraphPortDefinition[];
|
|
16
|
+
}
|
|
17
|
+
export interface NodeGraphEdgeData extends Record<string, unknown> {
|
|
18
|
+
tone?: 'neutral' | 'accent';
|
|
19
|
+
}
|
|
20
|
+
export type NodeGraphNode<Data extends Record<string, unknown> = NodeGraphNodeData> = Node<Data>;
|
|
21
|
+
export type NodeGraphEdge<Data extends Record<string, unknown> = NodeGraphEdgeData> = Edge<Data>;
|
|
22
|
+
export type NodeGraphNodeChange<NodeType extends NodeGraphNode = NodeGraphNode> = NodeChange<NodeType>;
|
|
23
|
+
export type NodeGraphEdgeChange<EdgeType extends NodeGraphEdge = NodeGraphEdge> = EdgeChange<EdgeType>;
|
|
24
|
+
export type NodeGraphConnection = Connection;
|
|
25
|
+
export type NodeGraphViewport = Viewport;
|
|
26
|
+
export interface NodeGraphRenderNodeProps<Data extends Record<string, unknown> = NodeGraphNodeData> {
|
|
27
|
+
id: string;
|
|
28
|
+
data: Data;
|
|
29
|
+
selected: boolean;
|
|
30
|
+
dragging: boolean;
|
|
31
|
+
readOnly: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface NodeGraphRenderEdgeProps<EdgeType extends NodeGraphEdge = NodeGraphEdge> {
|
|
34
|
+
id: string;
|
|
35
|
+
edge: EdgeType;
|
|
36
|
+
selected: boolean;
|
|
37
|
+
path: string;
|
|
38
|
+
labelX: number;
|
|
39
|
+
labelY: number;
|
|
40
|
+
}
|
|
41
|
+
export interface NodeGraphRef {
|
|
42
|
+
fitView: (options?: FitViewOptions) => Promise<boolean>;
|
|
43
|
+
zoomIn: () => Promise<boolean>;
|
|
44
|
+
zoomOut: () => Promise<boolean>;
|
|
45
|
+
getViewport: () => NodeGraphViewport;
|
|
46
|
+
}
|
|
47
|
+
export interface NodeGraphControlLabels {
|
|
48
|
+
zoomIn: string;
|
|
49
|
+
zoomOut: string;
|
|
50
|
+
fitView: string;
|
|
51
|
+
}
|
|
52
|
+
export interface NodeGraphRootProps<NodeType extends NodeGraphNode = NodeGraphNode, EdgeType extends NodeGraphEdge = NodeGraphEdge> extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onError'> {
|
|
53
|
+
nodes: NodeType[];
|
|
54
|
+
edges: EdgeType[];
|
|
55
|
+
onNodesChange?: (changes: NodeGraphNodeChange<NodeType>[]) => void;
|
|
56
|
+
onEdgesChange?: (changes: NodeGraphEdgeChange<EdgeType>[]) => void;
|
|
57
|
+
onConnect?: OnConnect;
|
|
58
|
+
isValidConnection?: (connection: NodeGraphConnection | EdgeType) => boolean;
|
|
59
|
+
renderNode?: (props: NodeGraphRenderNodeProps<NodeType['data']>) => ReactNode;
|
|
60
|
+
renderEdge?: (props: NodeGraphRenderEdgeProps<EdgeType>) => ReactNode;
|
|
61
|
+
renderEdgeLabel?: (edge: EdgeType) => ReactNode;
|
|
62
|
+
readOnly?: boolean;
|
|
63
|
+
emptyState?: ReactNode;
|
|
64
|
+
controls?: boolean;
|
|
65
|
+
controlLabels?: Partial<NodeGraphControlLabels>;
|
|
66
|
+
fitView?: boolean;
|
|
67
|
+
fitViewOptions?: FitViewOptions<NodeType>;
|
|
68
|
+
defaultViewport?: NodeGraphViewport;
|
|
69
|
+
viewport?: NodeGraphViewport;
|
|
70
|
+
onViewportChange?: (viewport: NodeGraphViewport) => void;
|
|
71
|
+
minZoom?: number;
|
|
72
|
+
maxZoom?: number;
|
|
73
|
+
panOnDrag?: boolean;
|
|
74
|
+
zoomOnScroll?: boolean;
|
|
75
|
+
zoomOnPinch?: boolean;
|
|
76
|
+
connectOnClick?: boolean;
|
|
77
|
+
onlyRenderVisibleElements?: boolean;
|
|
78
|
+
}
|
|
79
|
+
export interface NodeGraphPortProps extends Omit<HTMLAttributes<HTMLDivElement>, 'id' | 'children'> {
|
|
80
|
+
id: string;
|
|
81
|
+
type: 'source' | 'target';
|
|
82
|
+
position?: NodeGraphPortPosition;
|
|
83
|
+
label: string;
|
|
84
|
+
connectable?: boolean;
|
|
85
|
+
}
|
|
86
|
+
export interface NodeGraphNodeProps extends HTMLAttributes<HTMLDivElement> {
|
|
87
|
+
selected?: boolean;
|
|
88
|
+
dragging?: boolean;
|
|
89
|
+
readOnly?: boolean;
|
|
90
|
+
label?: ReactNode;
|
|
91
|
+
description?: ReactNode;
|
|
92
|
+
ports?: NodeGraphPortDefinition[];
|
|
93
|
+
children?: ReactNode;
|
|
94
|
+
seed?: string;
|
|
95
|
+
}
|
|
96
|
+
export interface NodeGraphEdgeProps {
|
|
97
|
+
id: string;
|
|
98
|
+
path: string;
|
|
99
|
+
labelX?: number;
|
|
100
|
+
labelY?: number;
|
|
101
|
+
selected?: boolean;
|
|
102
|
+
interactionWidth?: number;
|
|
103
|
+
label?: ReactNode;
|
|
104
|
+
className?: string;
|
|
105
|
+
}
|
|
106
|
+
export interface NodeGraphControlsProps extends HTMLAttributes<HTMLDivElement> {
|
|
107
|
+
fitViewOptions?: FitViewOptions;
|
|
108
|
+
zoomInLabel?: string;
|
|
109
|
+
zoomOutLabel?: string;
|
|
110
|
+
fitViewLabel?: string;
|
|
111
|
+
}
|
|
112
|
+
declare function NodeGraphControls({ className, fitViewOptions, zoomInLabel, zoomOutLabel, fitViewLabel, ...props }: NodeGraphControlsProps): import("react").JSX.Element;
|
|
113
|
+
export declare function applyNodeGraphNodeChanges<NodeType extends NodeGraphNode>(changes: NodeGraphNodeChange<NodeType>[], nodes: NodeType[]): NodeType[];
|
|
114
|
+
export declare function applyNodeGraphEdgeChanges<EdgeType extends NodeGraphEdge>(changes: NodeGraphEdgeChange<EdgeType>[], edges: EdgeType[]): EdgeType[];
|
|
115
|
+
export declare function addNodeGraphEdge<EdgeType extends NodeGraphEdge>(connection: NodeGraphConnection | EdgeType, edges: EdgeType[]): EdgeType[];
|
|
116
|
+
export declare const NodeGraph: {
|
|
117
|
+
Root: <NodeType extends NodeGraphNode = NodeGraphNode<NodeGraphNodeData>, EdgeType extends NodeGraphEdge = NodeGraphEdge<NodeGraphEdgeData>>(props: NodeGraphRootProps<NodeType, EdgeType> & RefAttributes<NodeGraphRef>) => ReactElement | null;
|
|
118
|
+
Node: import("react").ForwardRefExoticComponent<NodeGraphNodeProps & RefAttributes<HTMLDivElement>>;
|
|
119
|
+
Port: import("react").ForwardRefExoticComponent<NodeGraphPortProps & RefAttributes<HTMLDivElement>>;
|
|
120
|
+
Edge: import("react").NamedExoticComponent<NodeGraphEdgeProps>;
|
|
121
|
+
Controls: typeof NodeGraphControls;
|
|
122
|
+
};
|
|
123
|
+
export {};
|