@santi020k/lumen-react 3.0.1 → 4.0.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 +666 -13
- package/dist/ai-surfaces.d.ts +49 -0
- package/dist/ai-surfaces.d.ts.map +1 -0
- package/dist/ai-surfaces.js +88 -0
- package/dist/amount-field.d.ts +11 -0
- package/dist/amount-field.d.ts.map +1 -0
- package/dist/amount-field.js +37 -0
- package/dist/attachments.d.ts +17 -0
- package/dist/attachments.d.ts.map +1 -0
- package/dist/attachments.js +23 -0
- package/dist/bullet-chart.d.ts +14 -0
- package/dist/bullet-chart.d.ts.map +1 -0
- package/dist/bullet-chart.js +22 -0
- package/dist/change-summary.d.ts +14 -0
- package/dist/change-summary.d.ts.map +1 -0
- package/dist/change-summary.js +4 -0
- package/dist/chart-inspection.d.ts +10 -0
- package/dist/chart-inspection.d.ts.map +1 -0
- package/dist/chart-inspection.js +11 -0
- package/dist/chart-interaction.d.ts +17 -0
- package/dist/chart-interaction.d.ts.map +1 -0
- package/dist/chart-interaction.js +50 -0
- package/dist/chart-label.d.ts +3 -0
- package/dist/chart-label.d.ts.map +1 -0
- package/dist/chart-label.js +1 -0
- package/dist/chart-recipes.d.ts +8 -0
- package/dist/chart-recipes.d.ts.map +1 -1
- package/dist/chart-recipes.js +35 -0
- package/dist/combobox.d.ts +9 -0
- package/dist/combobox.d.ts.map +1 -0
- package/dist/combobox.js +137 -0
- package/dist/comparison-chart.d.ts +13 -0
- package/dist/comparison-chart.d.ts.map +1 -0
- package/dist/comparison-chart.js +18 -0
- package/dist/components.d.ts +106 -70
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +4 -1949
- package/dist/dashboard.d.ts +20 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +19 -0
- package/dist/data-table-range-model.d.ts +8 -0
- package/dist/data-table-range-model.d.ts.map +1 -0
- package/dist/data-table-range-model.js +31 -0
- package/dist/data-table-range.d.ts +17 -0
- package/dist/data-table-range.d.ts.map +1 -0
- package/dist/data-table-range.js +8 -0
- package/dist/data-table-saved-views.d.ts +30 -0
- package/dist/data-table-saved-views.d.ts.map +1 -0
- package/dist/data-table-saved-views.js +49 -0
- package/dist/data-table-selection-model.d.ts +23 -0
- package/dist/data-table-selection-model.d.ts.map +1 -0
- package/dist/data-table-selection-model.js +36 -0
- package/dist/data-table-selection.d.ts +11 -0
- package/dist/data-table-selection.d.ts.map +1 -0
- package/dist/data-table-selection.js +10 -0
- package/dist/data-table-state.d.ts +6 -0
- package/dist/data-table-state.d.ts.map +1 -0
- package/dist/data-table-state.js +38 -0
- package/dist/data-table-view.d.ts +88 -0
- package/dist/data-table-view.d.ts.map +1 -0
- package/dist/data-table-view.js +1 -0
- package/dist/data-table.d.ts +60 -0
- package/dist/data-table.d.ts.map +1 -0
- package/dist/data-table.js +1 -0
- package/dist/date-range-calendar.d.ts +29 -0
- package/dist/date-range-calendar.d.ts.map +1 -0
- package/dist/date-range-calendar.js +69 -0
- package/dist/date-range-input.d.ts +25 -0
- package/dist/date-range-input.d.ts.map +1 -0
- package/dist/date-range-input.js +195 -0
- package/dist/device-frame.d.ts +13 -0
- package/dist/device-frame.d.ts.map +1 -0
- package/dist/device-frame.js +23 -0
- package/dist/dialog-lifecycle.d.ts +3 -0
- package/dist/dialog-lifecycle.d.ts.map +1 -0
- package/dist/dialog-lifecycle.js +70 -0
- package/dist/expanded-charts.d.ts +26 -0
- package/dist/expanded-charts.d.ts.map +1 -0
- package/dist/expanded-charts.js +46 -0
- package/dist/floating-panel.d.ts +11 -0
- package/dist/floating-panel.d.ts.map +1 -0
- package/dist/floating-panel.js +1 -0
- package/dist/form-workflow.d.ts +77 -0
- package/dist/form-workflow.d.ts.map +1 -0
- package/dist/form-workflow.js +337 -0
- package/dist/hooks.d.ts +24 -21
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +1 -2468
- package/dist/image-comparison.d.ts +20 -0
- package/dist/image-comparison.d.ts.map +1 -0
- package/dist/image-comparison.js +55 -0
- package/dist/index.d.ts +25 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -0
- package/dist/interval-charts.d.ts +21 -0
- package/dist/interval-charts.d.ts.map +1 -0
- package/dist/interval-charts.js +23 -0
- package/dist/media-selection.d.ts +18 -0
- package/dist/media-selection.d.ts.map +1 -0
- package/dist/media-selection.js +12 -0
- package/dist/media-viewport.d.ts +16 -0
- package/dist/media-viewport.d.ts.map +1 -0
- package/dist/media-viewport.js +37 -0
- package/dist/motion.d.ts +597 -0
- package/dist/motion.d.ts.map +1 -0
- package/dist/motion.js +22 -0
- package/dist/review-workflow.d.ts +32 -0
- package/dist/review-workflow.d.ts.map +1 -0
- package/dist/review-workflow.js +76 -0
- package/dist/rich-text-editor.d.ts +21 -0
- package/dist/rich-text-editor.d.ts.map +1 -0
- package/dist/rich-text-editor.js +143 -0
- package/dist/select-form.d.ts +3 -0
- package/dist/select-form.d.ts.map +1 -0
- package/dist/select-form.js +1 -0
- package/dist/server-components.d.ts +24 -4
- package/dist/server-components.d.ts.map +1 -1
- package/dist/server-components.js +11 -3
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -0
- package/dist/toast-context.d.ts +4 -0
- package/dist/toast-context.d.ts.map +1 -0
- package/dist/toast-context.js +10 -0
- package/dist/toast-provider.d.ts +3 -0
- package/dist/toast-provider.d.ts.map +1 -0
- package/dist/toast-provider.js +3 -0
- package/dist/use-copy-feedback.d.ts +17 -0
- package/dist/use-copy-feedback.d.ts.map +1 -0
- package/dist/use-copy-feedback.js +54 -0
- package/dist/virtual-list-data.d.ts +13 -0
- package/dist/virtual-list-data.d.ts.map +1 -0
- package/dist/virtual-list-data.js +87 -0
- package/dist/virtual-list.d.ts +14 -0
- package/dist/virtual-list.d.ts.map +1 -0
- package/dist/virtual-list.js +24 -0
- package/dist/visual-interactions.d.ts +16 -0
- package/dist/visual-interactions.d.ts.map +1 -0
- package/dist/visual-interactions.js +30 -0
- package/dist/world-map.d.ts +26 -0
- package/dist/world-map.d.ts.map +1 -0
- package/dist/world-map.js +81 -0
- package/package.json +99 -11
- package/styles/critical.css +634 -61
- package/styles/data-table-view.css +36 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { isLumenDateRangeValid as isCalendarRangeValid, parseLumenDate as parseCalendarDate } from '@santi020k/lumen-core';
|
|
5
|
+
import { Button, Icon } from './components.js';
|
|
6
|
+
import { useCalendar } from './hooks.js';
|
|
7
|
+
const RangeMonth = ({ part, value, onValueChange, locale, min, max, label, labels, disabled, readOnly, formatDate }) => {
|
|
8
|
+
const { rootRef, ...calendar } = useCalendar({
|
|
9
|
+
value: value[part],
|
|
10
|
+
locale,
|
|
11
|
+
min,
|
|
12
|
+
max,
|
|
13
|
+
labels,
|
|
14
|
+
disabled,
|
|
15
|
+
readOnly,
|
|
16
|
+
onValueChange: next => {
|
|
17
|
+
const range = part === 'start' ?
|
|
18
|
+
{ start: next, end: next > value.end ? next : value.end } :
|
|
19
|
+
{ start: next < value.start ? next : value.start, end: next };
|
|
20
|
+
onValueChange(isCalendarRangeValid(range, min, max) ? range : { start: next, end: next });
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
const selectedRef = useRef(value[part]);
|
|
24
|
+
const focusedDate = calendar.weeks.flat().find(day => day.tabIndex === 0)?.date;
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (selectedRef.current !== value[part]) {
|
|
27
|
+
selectedRef.current = value[part];
|
|
28
|
+
calendar.focusDate(value[part]);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const root = rootRef.current;
|
|
33
|
+
const activeElement = root?.ownerDocument.activeElement;
|
|
34
|
+
if (root?.contains(activeElement ?? null) && activeElement?.getAttribute('role') === 'gridcell') {
|
|
35
|
+
root.querySelector('[role="gridcell"][tabindex="0"]')?.focus();
|
|
36
|
+
}
|
|
37
|
+
}, [focusedDate, rootRef]);
|
|
38
|
+
return (_jsxs("section", { className: "ui-range-calendar__month", "aria-label": label, children: [_jsxs("div", { className: "ui-range-calendar__endpoint", children: [_jsx("span", { children: label }), _jsx("strong", { children: parseCalendarDate(value[part]) ? formatDate(value[part]) : value[part] })] }), _jsxs("div", { ...calendar.rootProps, children: [_jsxs("div", { className: "ui-calendar__header", children: [_jsx("button", { ...calendar.previousProps, type: "button", children: _jsx(Icon, { name: "chevron-left", size: "sm" }) }), _jsx("strong", { ...calendar.labelProps, children: calendar.label }), _jsx("button", { ...calendar.nextProps, type: "button", children: _jsx(Icon, { name: "chevron-right", size: "sm" }) })] }), _jsxs("table", { ...calendar.gridProps, "aria-multiselectable": "true", children: [_jsx("thead", { children: _jsx("tr", { role: "row", children: calendar.weekdays.map(day => _jsx("th", { role: "columnheader", scope: "col", children: day }, day)) }) }), _jsx("tbody", { children: calendar.weeks.map(week => (_jsx("tr", { role: "row", children: week.map(day => {
|
|
39
|
+
const inRange = isCalendarRangeValid(value, min, max) &&
|
|
40
|
+
day.date >= value.start && day.date <= value.end;
|
|
41
|
+
const endpoint = inRange && (day.date === value.start || day.date === value.end);
|
|
42
|
+
return (_jsx("td", { ...calendar.getDayProps(day), "aria-selected": inRange, "data-selected": endpoint || undefined, "data-in-range": inRange || undefined, children: day.day }, day.date));
|
|
43
|
+
}) }, week[0]?.date))) })] })] })] }));
|
|
44
|
+
};
|
|
45
|
+
const emptyPresets = [];
|
|
46
|
+
const isPresetRecord = (value) => (typeof value === 'object' && value !== null && !Array.isArray(value));
|
|
47
|
+
const isPreset = (value) => (isPresetRecord(value) && typeof value.label === 'string' && isPresetRecord(value.value) &&
|
|
48
|
+
typeof value.value.start === 'string' && typeof value.value.end === 'string');
|
|
49
|
+
const validPresets = (value) => {
|
|
50
|
+
if (!Array.isArray(value))
|
|
51
|
+
return emptyPresets;
|
|
52
|
+
const presets = Array.from(value);
|
|
53
|
+
return presets.every(isPreset) ? presets : emptyPresets;
|
|
54
|
+
};
|
|
55
|
+
const isoDate = (value) => value;
|
|
56
|
+
/** Inline range editor. Consumers own draft state and Apply/Cancel actions. */
|
|
57
|
+
export const DateRangeCalendar = ({ value, onValueChange, locale, min, max, labels, presets: rawPresets = emptyPresets, disabled = false, readOnly = false, formatDate = isoDate, className, ...props }) => {
|
|
58
|
+
const presets = validPresets(rawPresets);
|
|
59
|
+
const [selectedPreset, setSelectedPreset] = useState();
|
|
60
|
+
const matchesRange = (preset) => (preset.value.start === value.start && preset.value.end === value.end);
|
|
61
|
+
const activePreset = presets.find(preset => preset.label === selectedPreset && matchesRange(preset)) ??
|
|
62
|
+
presets.find(matchesRange);
|
|
63
|
+
return (_jsxs("div", { ...props, "aria-disabled": disabled || undefined, className: ['ui-range-calendar', className].filter(Boolean).join(' '), children: [presets.length > 0 && (_jsx("nav", { className: "ui-range-calendar__presets", "aria-label": labels.presets, children: presets.map(preset => (_jsx(Button, { type: "button", variant: "ghost", disabled: disabled || readOnly || !isCalendarRangeValid(preset.value, min, max), "aria-pressed": preset === activePreset, onClick: () => {
|
|
64
|
+
if (disabled || readOnly || !isCalendarRangeValid(preset.value, min, max))
|
|
65
|
+
return;
|
|
66
|
+
setSelectedPreset(preset.label);
|
|
67
|
+
onValueChange({ ...preset.value });
|
|
68
|
+
}, children: preset.label }, preset.label))) })), _jsx("div", { className: "ui-range-calendar__months", children: ['start', 'end'].map(part => (_jsx(RangeMonth, { part: part, value: value, onValueChange: onValueChange, locale: locale, min: min, max: max, label: labels[part], labels: labels, disabled: disabled, readOnly: readOnly, formatDate: formatDate }, part))) })] }));
|
|
69
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { CalendarRange, DateRangeCalendarProps } from './date-range-calendar.js';
|
|
3
|
+
export interface DateRangeInputProps extends Pick<DateRangeCalendarProps, 'value' | 'onValueChange' | 'locale' | 'min' | 'max' | 'presets' | 'formatDate' | 'disabled' | 'readOnly'> {
|
|
4
|
+
/** Accessible trigger and non-modal dialog name. */
|
|
5
|
+
label: string;
|
|
6
|
+
labels: DateRangeCalendarProps['labels'] & {
|
|
7
|
+
apply: string;
|
|
8
|
+
cancel: string;
|
|
9
|
+
invalidRange?: string;
|
|
10
|
+
};
|
|
11
|
+
className?: string;
|
|
12
|
+
form?: string;
|
|
13
|
+
/** Optional native form entries containing the applied ISO endpoints. */
|
|
14
|
+
name?: {
|
|
15
|
+
start: string;
|
|
16
|
+
end: string;
|
|
17
|
+
};
|
|
18
|
+
/** Return a localized message to prevent applying an invalid draft. */
|
|
19
|
+
validate?: (value: CalendarRange) => string | undefined;
|
|
20
|
+
/** Optional localized summary below the calendars. */
|
|
21
|
+
renderSummary?: (value: CalendarRange) => ReactNode;
|
|
22
|
+
}
|
|
23
|
+
/** Input-attached range editor. Only Apply publishes the draft to onValueChange. */
|
|
24
|
+
export declare const DateRangeInput: ({ value, onValueChange, label, labels, disabled, readOnly, className, name, form, validate, renderSummary, formatDate, ...calendarProps }: DateRangeInputProps) => import("react").JSX.Element;
|
|
25
|
+
//# sourceMappingURL=date-range-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-range-input.d.ts","sourceRoot":"","sources":["../src/date-range-input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,OAAO,KAAK,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAA;AAqMrF,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,sBAAsB,EAAE,OAAO,GAAG,eAAe,GAAG,QAAQ,GAAG,KAAK,GAAG,KAAK,GAAG,SAAS,GAAG,YAAY,GAAG,UAAU,GAAG,UAAU,CAAC;IAClL,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,sBAAsB,CAAC,QAAQ,CAAC,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACnG,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IACrC,uEAAuE;IACvE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,MAAM,GAAG,SAAS,CAAA;IACvD,sDAAsD;IACtD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,SAAS,CAAA;CACpD;AA6CD,oFAAoF;AACpF,eAAO,MAAM,cAAc,GAAI,2IAG5B,mBAAmB,gCAyGrB,CAAA"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from 'react';
|
|
4
|
+
import { isLumenDateRangeValid as isCalendarRangeValid, parseLumenDate as parseCalendarDate, resolveLumenDateLabels as resolveDateControlLabels } from '@santi020k/lumen-core';
|
|
5
|
+
import { Button, Icon } from './components.js';
|
|
6
|
+
import { DateRangeCalendar } from './date-range-calendar.js';
|
|
7
|
+
import { usePopover } from './hooks.js';
|
|
8
|
+
const viewportBounds = (view) => {
|
|
9
|
+
const viewport = view.visualViewport;
|
|
10
|
+
const left = (viewport?.offsetLeft ?? 0) + 8;
|
|
11
|
+
const top = (viewport?.offsetTop ?? 0) + 8;
|
|
12
|
+
return { left,
|
|
13
|
+
top,
|
|
14
|
+
right: left + (viewport?.width ?? view.innerWidth) - 16,
|
|
15
|
+
bottom: top + (viewport?.height ?? view.innerHeight) - 16 };
|
|
16
|
+
};
|
|
17
|
+
const positionPanel = (panel, control, view) => {
|
|
18
|
+
const { left, top, right, bottom } = viewportBounds(view);
|
|
19
|
+
const anchor = control.getBoundingClientRect();
|
|
20
|
+
panel.style.width = `${Math.max(0, Math.min(816, right - left))}px`;
|
|
21
|
+
const body = panel.querySelector('.ui-range-input__body');
|
|
22
|
+
const footer = panel.querySelector('.ui-range-input__actions');
|
|
23
|
+
const preferredHeight = (body?.scrollHeight ?? panel.scrollHeight) + (footer?.offsetHeight ?? 0) + 2;
|
|
24
|
+
const below = Math.max(0, bottom - anchor.bottom - 8);
|
|
25
|
+
const above = Math.max(0, anchor.top - top - 8);
|
|
26
|
+
const placeBelow = below >= preferredHeight || below >= above;
|
|
27
|
+
const available = placeBelow ? below : above;
|
|
28
|
+
panel.style.maxHeight = `${Math.min(bottom - top, Math.max(120, available))}px`;
|
|
29
|
+
const bounds = panel.getBoundingClientRect();
|
|
30
|
+
const y = placeBelow ? anchor.bottom + 8 : anchor.top - bounds.height - 8;
|
|
31
|
+
panel.style.left = `${Math.max(left, Math.min(anchor.left, right - bounds.width))}px`;
|
|
32
|
+
panel.style.top = `${Math.max(top, Math.min(y, bottom - bounds.height))}px`;
|
|
33
|
+
};
|
|
34
|
+
const showPanel = (panel) => {
|
|
35
|
+
if (typeof panel.showPopover === 'function')
|
|
36
|
+
panel.showPopover();
|
|
37
|
+
};
|
|
38
|
+
const hidePanel = (panel) => {
|
|
39
|
+
if (typeof panel.hidePopover === 'function' && panel.matches(':popover-open'))
|
|
40
|
+
panel.hidePopover();
|
|
41
|
+
};
|
|
42
|
+
const isoDate = (date) => date;
|
|
43
|
+
const resolveRangeInputForm = (root, formId) => {
|
|
44
|
+
if (!root)
|
|
45
|
+
return null;
|
|
46
|
+
if (formId !== undefined) {
|
|
47
|
+
const owner = root.ownerDocument.getElementById(formId);
|
|
48
|
+
const FormType = root.ownerDocument.defaultView?.HTMLFormElement;
|
|
49
|
+
return FormType && owner instanceof FormType ? owner : null;
|
|
50
|
+
}
|
|
51
|
+
return root.closest('form');
|
|
52
|
+
};
|
|
53
|
+
const observePanelPosition = (panel, control, view, position) => {
|
|
54
|
+
const owner = panel.ownerDocument;
|
|
55
|
+
const NodeType = owner.defaultView?.Node;
|
|
56
|
+
const viewport = view.visualViewport;
|
|
57
|
+
const onScroll = (event) => {
|
|
58
|
+
if (NodeType && event.target instanceof NodeType && panel.contains(event.target))
|
|
59
|
+
return;
|
|
60
|
+
position();
|
|
61
|
+
};
|
|
62
|
+
const observer = new ResizeObserver(position);
|
|
63
|
+
observer.observe(control);
|
|
64
|
+
observer.observe(panel);
|
|
65
|
+
owner.addEventListener('scroll', onScroll, true);
|
|
66
|
+
view.addEventListener('resize', position);
|
|
67
|
+
viewport?.addEventListener('resize', position);
|
|
68
|
+
viewport?.addEventListener('scroll', position);
|
|
69
|
+
return () => {
|
|
70
|
+
observer.disconnect();
|
|
71
|
+
owner.removeEventListener('scroll', onScroll, true);
|
|
72
|
+
view.removeEventListener('resize', position);
|
|
73
|
+
viewport?.removeEventListener('resize', position);
|
|
74
|
+
viewport?.removeEventListener('scroll', position);
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
const scrollSmallViewport = (control, view) => {
|
|
78
|
+
if ((view.visualViewport?.width ?? view.innerWidth) <= 640) {
|
|
79
|
+
control.scrollIntoView({ block: 'start', behavior: 'instant' });
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const RangePopover = ({ open, onOpenChange, label, trigger, children, disabled, readOnly }) => {
|
|
83
|
+
const { panelRef, triggerRef, rootRef, rootProps, panelProps, triggerProps } = usePopover({ open, onOpenChange, positioning: 'none' });
|
|
84
|
+
useLayoutEffect(() => {
|
|
85
|
+
if (!open)
|
|
86
|
+
return;
|
|
87
|
+
const panel = panelRef.current;
|
|
88
|
+
const control = triggerRef.current;
|
|
89
|
+
const view = panel?.ownerDocument.defaultView;
|
|
90
|
+
if (!panel || !control || !view)
|
|
91
|
+
return;
|
|
92
|
+
scrollSmallViewport(control, view);
|
|
93
|
+
showPanel(panel);
|
|
94
|
+
const position = () => {
|
|
95
|
+
positionPanel(panel, control, view);
|
|
96
|
+
};
|
|
97
|
+
position();
|
|
98
|
+
const initialFocus = panel.querySelector('[role="gridcell"][tabindex="0"]') ??
|
|
99
|
+
panel.querySelector('button:not(:disabled)');
|
|
100
|
+
initialFocus?.focus({ preventScroll: true });
|
|
101
|
+
const owner = panel.ownerDocument;
|
|
102
|
+
const NodeType = owner.defaultView?.Node;
|
|
103
|
+
const closeOnFocusOutside = (event) => {
|
|
104
|
+
if (NodeType && event.target instanceof NodeType && !rootRef.current?.contains(event.target))
|
|
105
|
+
onOpenChange(false);
|
|
106
|
+
};
|
|
107
|
+
owner.addEventListener('focusin', closeOnFocusOutside);
|
|
108
|
+
const stopObserving = observePanelPosition(panel, control, view, position);
|
|
109
|
+
return () => {
|
|
110
|
+
stopObserving();
|
|
111
|
+
owner.removeEventListener('focusin', closeOnFocusOutside);
|
|
112
|
+
hidePanel(panel);
|
|
113
|
+
};
|
|
114
|
+
}, [open, onOpenChange, panelRef, rootRef, triggerRef]);
|
|
115
|
+
return (_jsxs("div", { ...rootProps, className: "ui-range-input", children: [_jsx(Button, { ...triggerProps, disabled: disabled, "aria-disabled": readOnly || undefined, variant: "outline", "aria-haspopup": "dialog", "aria-label": label, children: trigger }), _jsx("div", { ...panelProps, popover: "manual", role: "dialog", "aria-label": label, className: "ui-range-input__panel", onKeyDown: event => {
|
|
116
|
+
if (event.key === 'Escape') {
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
event.stopPropagation();
|
|
119
|
+
onOpenChange(false);
|
|
120
|
+
triggerRef.current?.focus({ preventScroll: true });
|
|
121
|
+
}
|
|
122
|
+
}, onClick: event => {
|
|
123
|
+
const ElementType = event.currentTarget.ownerDocument.defaultView?.Element;
|
|
124
|
+
if (ElementType && event.target instanceof ElementType && event.target.closest('[data-range-close]')) {
|
|
125
|
+
triggerRef.current?.focus({ preventScroll: true });
|
|
126
|
+
}
|
|
127
|
+
}, children: open && children })] }));
|
|
128
|
+
};
|
|
129
|
+
const useRangeDraft = (value, disabled, readOnly) => {
|
|
130
|
+
const { start, end } = value;
|
|
131
|
+
const revision = JSON.stringify([start, end, disabled, readOnly]);
|
|
132
|
+
const [state, setState] = useState({ revision, open: false, draft: value });
|
|
133
|
+
// External values and availability invalidate an editing session before it can commit.
|
|
134
|
+
if (state.revision !== revision) {
|
|
135
|
+
setState({ revision, open: false, draft: value });
|
|
136
|
+
}
|
|
137
|
+
const changeOpen = useCallback((next) => {
|
|
138
|
+
if (next && (disabled || readOnly))
|
|
139
|
+
return;
|
|
140
|
+
setState(current => ({ revision, open: next, draft: next ? { start, end } : current.draft }));
|
|
141
|
+
}, [disabled, readOnly, start, end, revision]);
|
|
142
|
+
const setDraft = (draft) => {
|
|
143
|
+
setState(current => ({ ...current, draft }));
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
open: state.revision === revision && state.open && !disabled && !readOnly,
|
|
147
|
+
draft: state.draft,
|
|
148
|
+
changeOpen,
|
|
149
|
+
setDraft
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
const rangeError = (draft, props) => {
|
|
153
|
+
if (!isCalendarRangeValid(draft, props.min, props.max)) {
|
|
154
|
+
return props.labels.invalidRange ?? resolveDateControlLabels(props.locale || 'en').invalidRange;
|
|
155
|
+
}
|
|
156
|
+
return props.validate?.(draft);
|
|
157
|
+
};
|
|
158
|
+
const displayRangeDate = (date, formatDate) => (parseCalendarDate(date) ? formatDate(date) : date);
|
|
159
|
+
/** Input-attached range editor. Only Apply publishes the draft to onValueChange. */
|
|
160
|
+
export const DateRangeInput = ({ value, onValueChange, label, labels, disabled = false, readOnly = false, className, name, form, validate, renderSummary, formatDate = isoDate, ...calendarProps }) => {
|
|
161
|
+
const { open, draft, changeOpen, setDraft } = useRangeDraft(value, disabled, readOnly);
|
|
162
|
+
const wrapperRef = useRef(null);
|
|
163
|
+
const messageId = useId();
|
|
164
|
+
const validDraft = isCalendarRangeValid(draft, calendarProps.min, calendarProps.max);
|
|
165
|
+
const error = rangeError(draft, { ...calendarProps, labels, validate });
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
const owner = wrapperRef.current?.ownerDocument;
|
|
168
|
+
let active = true;
|
|
169
|
+
let resetTimer;
|
|
170
|
+
const reset = (event) => {
|
|
171
|
+
if (event.target !== resolveRangeInputForm(wrapperRef.current, form))
|
|
172
|
+
return;
|
|
173
|
+
globalThis.clearTimeout(resetTimer);
|
|
174
|
+
resetTimer = globalThis.setTimeout(() => {
|
|
175
|
+
if (!active || event.defaultPrevented || !wrapperRef.current?.isConnected)
|
|
176
|
+
return;
|
|
177
|
+
changeOpen(false);
|
|
178
|
+
});
|
|
179
|
+
};
|
|
180
|
+
owner?.addEventListener('reset', reset);
|
|
181
|
+
return () => {
|
|
182
|
+
active = false;
|
|
183
|
+
globalThis.clearTimeout(resetTimer);
|
|
184
|
+
owner?.removeEventListener('reset', reset);
|
|
185
|
+
};
|
|
186
|
+
}, [form, changeOpen]);
|
|
187
|
+
return (_jsxs("div", { className: className, ref: wrapperRef, children: [name && (_jsxs(_Fragment, { children: [_jsx("input", { type: "hidden", name: name.start, form: form, value: value.start, disabled: disabled }), _jsx("input", { type: "hidden", name: name.end, form: form, value: value.end, disabled: disabled })] })), _jsxs(RangePopover, { open: open, onOpenChange: changeOpen, label: label, disabled: disabled, readOnly: readOnly, trigger: (_jsxs(_Fragment, { children: [_jsxs("span", { className: "ui-range-input__value", children: [_jsx(Icon, { name: "calendar", size: "sm" }), _jsxs("span", { children: [displayRangeDate(value.start, formatDate), ' ', "\u2013", ' ', displayRangeDate(value.end, formatDate)] })] }), _jsx(Icon, { name: "chevron-down", size: "sm" })] })), children: [_jsxs("div", { className: "ui-range-input__body", children: [_jsx(DateRangeCalendar, { ...calendarProps, value: draft, onValueChange: setDraft, labels: labels, formatDate: formatDate, disabled: disabled, readOnly: readOnly }), _jsx("div", { id: messageId, className: "ui-range-input__summary", "aria-live": "polite", "data-invalid": Boolean(error) || undefined, children: error ?? (validDraft ? renderSummary?.(draft) : undefined) })] }), _jsxs("div", { className: "ui-range-input__actions", children: [_jsx(Button, { variant: "outline", "data-range-close": true, onClick: () => {
|
|
188
|
+
changeOpen(false);
|
|
189
|
+
}, children: labels.cancel }), _jsx(Button, { disabled: Boolean(error), "aria-describedby": error ? messageId : undefined, "data-range-close": true, onClick: () => {
|
|
190
|
+
if (disabled || readOnly || !validDraft || error)
|
|
191
|
+
return;
|
|
192
|
+
onValueChange({ ...draft });
|
|
193
|
+
changeOpen(false);
|
|
194
|
+
}, children: labels.apply })] })] })] }));
|
|
195
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ComponentPropsWithoutRef } from 'react';
|
|
2
|
+
import { type LumenDeviceFrameColor, type LumenDeviceFrameDevice, type LumenDeviceFrameOrientation, type LumenDeviceFrameTone } from '@santi020k/lumen-core';
|
|
3
|
+
export interface DeviceFrameProps extends ComponentPropsWithoutRef<'div'> {
|
|
4
|
+
device?: LumenDeviceFrameDevice;
|
|
5
|
+
orientation?: LumenDeviceFrameOrientation;
|
|
6
|
+
tone?: LumenDeviceFrameTone;
|
|
7
|
+
color?: LumenDeviceFrameColor;
|
|
8
|
+
screenWidth?: number;
|
|
9
|
+
screenHeight?: number;
|
|
10
|
+
scroll?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const DeviceFrame: ({ device, orientation, tone, color, screenWidth, screenHeight, scroll, className, children, style: consumerStyle, ...props }: DeviceFrameProps) => import("react").JSX.Element;
|
|
13
|
+
//# sourceMappingURL=device-frame.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-frame.d.ts","sourceRoot":"","sources":["../src/device-frame.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,wBAAwB,EAAyC,MAAM,OAAO,CAAA;AAE5F,OAAO,EACa,KAAK,qBAAqB,EAAE,KAAK,sBAAsB,EAAE,KAAK,2BAA2B,EAC3G,KAAK,oBAAoB,EAC1B,MAAM,uBAAuB,CAAA;AAE9B,MAAM,WAAW,gBAAiB,SAAQ,wBAAwB,CAAC,KAAK,CAAC;IACvE,MAAM,CAAC,EAAE,sBAAsB,CAAA;IAC/B,WAAW,CAAC,EAAE,2BAA2B,CAAA;IACzC,IAAI,CAAC,EAAE,oBAAoB,CAAA;IAC3B,KAAK,CAAC,EAAE,qBAAqB,CAAA;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,eAAO,MAAM,WAAW,GAAI,8HAC4C,gBAAgB,gCAuCvF,CAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
|
+
import { composeClassName, observeLumenDeviceFrame, resolveLumenDeviceFrame, resolveLumenDeviceFrameColor } from '@santi020k/lumen-core';
|
|
5
|
+
export const DeviceFrame = ({ device = 'laptop', orientation, tone = 'dark', color, screenWidth, screenHeight, scroll = true, className, children, style: consumerStyle, ...props }) => {
|
|
6
|
+
const screenRef = useRef(null);
|
|
7
|
+
const size = resolveLumenDeviceFrame(device, orientation, screenWidth, screenHeight);
|
|
8
|
+
const resolvedColor = resolveLumenDeviceFrameColor(color);
|
|
9
|
+
const frameStyle = {
|
|
10
|
+
...consumerStyle,
|
|
11
|
+
...(resolvedColor ? { '--ui-device-color': resolvedColor } : {})
|
|
12
|
+
};
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (screenRef.current)
|
|
15
|
+
return observeLumenDeviceFrame(screenRef.current, size.width);
|
|
16
|
+
}, [size.width]);
|
|
17
|
+
const style = {
|
|
18
|
+
'--ui-device-frame-height': `${size.height}px`,
|
|
19
|
+
'--ui-device-frame-width': `${size.width}px`,
|
|
20
|
+
aspectRatio: `${size.width} / ${size.height}`
|
|
21
|
+
};
|
|
22
|
+
return (_jsxs("div", { className: composeClassName('ui-device-frame', className), "data-device": device, "data-tone": tone, "data-orientation": orientation, style: frameStyle, ...props, children: [_jsxs("div", { className: "ui-device-frame__shell", children: [_jsxs("span", { className: "ui-device-frame__hardware", "aria-hidden": "true", children: [_jsx("span", { className: "ui-device-frame__action" }), _jsx("span", { className: "ui-device-frame__volume-up" }), _jsx("span", { className: "ui-device-frame__volume-down" }), _jsx("span", { className: "ui-device-frame__power" }), _jsx("span", { className: "ui-device-frame__rail-left" }), _jsx("span", { className: "ui-device-frame__rail-right" })] }), _jsxs("div", { className: "ui-device-frame__glass", children: [_jsx("span", { "aria-hidden": "true", className: "ui-device-frame__camera" }), _jsx("div", { className: "ui-device-frame__screen", "data-scroll": String(scroll), ref: screenRef, style: style, children: children })] })] }), _jsx("span", { "aria-hidden": "true", className: "ui-device-frame__base" })] }));
|
|
23
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialog-lifecycle.d.ts","sourceRoot":"","sources":["../src/dialog-lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAiCtC,eAAO,MAAM,kBAAkB,GAC7B,MAAM,OAAO,EACb,WAAW,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,EAC9C,YAAY,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,kBA4D1C,CAAA"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
const openDialogs = [];
|
|
3
|
+
const removeDialog = (dialog) => {
|
|
4
|
+
const index = openDialogs.lastIndexOf(dialog);
|
|
5
|
+
if (index !== -1)
|
|
6
|
+
openDialogs.splice(index, 1);
|
|
7
|
+
};
|
|
8
|
+
const getActiveElement = (dialog) => {
|
|
9
|
+
const { activeElement, defaultView } = dialog.ownerDocument;
|
|
10
|
+
return defaultView && activeElement instanceof defaultView.HTMLElement ? activeElement : null;
|
|
11
|
+
};
|
|
12
|
+
const restoreDialogFocus = (dialog, target) => {
|
|
13
|
+
if (!target?.isConnected)
|
|
14
|
+
return;
|
|
15
|
+
const topDialog = [...openDialogs].reverse().find(candidate => (candidate.ownerDocument === dialog.ownerDocument && candidate.isConnected && candidate.open));
|
|
16
|
+
if (topDialog && !topDialog.contains(target))
|
|
17
|
+
return;
|
|
18
|
+
const active = getActiveElement(dialog);
|
|
19
|
+
if (active && active !== dialog.ownerDocument.body && active !== target && !dialog.contains(active))
|
|
20
|
+
return;
|
|
21
|
+
target.focus({ preventScroll: true });
|
|
22
|
+
};
|
|
23
|
+
export const useDialogLifecycle = (open, dialogRef, triggerRef) => {
|
|
24
|
+
const returnFocusRef = useRef(null);
|
|
25
|
+
const generationRef = useRef(0);
|
|
26
|
+
const programmaticClosesRef = useRef(0);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const dialog = dialogRef.current;
|
|
29
|
+
if (!open || !dialog?.isConnected)
|
|
30
|
+
return;
|
|
31
|
+
generationRef.current += 1;
|
|
32
|
+
returnFocusRef.current ??= triggerRef.current ?? getActiveElement(dialog);
|
|
33
|
+
removeDialog(dialog);
|
|
34
|
+
openDialogs.push(dialog);
|
|
35
|
+
if (!dialog.open) {
|
|
36
|
+
if (typeof dialog.showModal === 'function') {
|
|
37
|
+
dialog.showModal();
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
dialog.setAttribute('open', '');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return () => {
|
|
44
|
+
const generation = ++generationRef.current;
|
|
45
|
+
const target = returnFocusRef.current;
|
|
46
|
+
removeDialog(dialog);
|
|
47
|
+
if (dialog.open) {
|
|
48
|
+
if (typeof dialog.close === 'function') {
|
|
49
|
+
programmaticClosesRef.current += 1;
|
|
50
|
+
dialog.close();
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
dialog.removeAttribute('open');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
queueMicrotask(() => {
|
|
57
|
+
if (generationRef.current !== generation)
|
|
58
|
+
return;
|
|
59
|
+
returnFocusRef.current = null;
|
|
60
|
+
restoreDialogFocus(dialog, target);
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
}, [dialogRef, open, triggerRef]);
|
|
64
|
+
return () => {
|
|
65
|
+
if (programmaticClosesRef.current === 0)
|
|
66
|
+
return false;
|
|
67
|
+
programmaticClosesRef.current -= 1;
|
|
68
|
+
return true;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type LumenBoxPlotDatum, type LumenBoxPlotOptions, type LumenBoxPlotStatisticLabels, type LumenCalendarHeatmapDatum, type LumenCalendarHeatmapOptions, type LumenChartLabels, type LumenFunnelDatum } from '@santi020k/lumen-core';
|
|
2
|
+
import { type ChartProps } from './components.js';
|
|
3
|
+
interface DataChartProps extends Omit<ChartProps, 'children'> {
|
|
4
|
+
formatValue?: (value: number) => string;
|
|
5
|
+
labels?: Partial<LumenChartLabels>;
|
|
6
|
+
showTable?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface CalendarHeatmapProps extends DataChartProps, LumenCalendarHeatmapOptions {
|
|
9
|
+
data?: readonly LumenCalendarHeatmapDatum[];
|
|
10
|
+
formatDate?: (date: string) => string;
|
|
11
|
+
/** Seven labels indexed Sunday (0) through Saturday (6). */
|
|
12
|
+
weekdayLabels?: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
export interface FunnelChartProps extends DataChartProps {
|
|
15
|
+
data?: readonly LumenFunnelDatum[];
|
|
16
|
+
}
|
|
17
|
+
export type BoxPlotStatisticLabels = LumenBoxPlotStatisticLabels;
|
|
18
|
+
export interface BoxPlotProps extends DataChartProps, LumenBoxPlotOptions {
|
|
19
|
+
data?: readonly LumenBoxPlotDatum[];
|
|
20
|
+
statisticLabels?: Partial<BoxPlotStatisticLabels>;
|
|
21
|
+
}
|
|
22
|
+
export declare const CalendarHeatmap: ({ data, startDate, endDate, weekStartsOn, domain, formatValue, formatDate, weekdayLabels, labels, showTable, summary, className, ...props }: CalendarHeatmapProps) => import("react").JSX.Element;
|
|
23
|
+
export declare const FunnelChart: ({ data, formatValue, labels, showTable, summary, className, ...props }: FunnelChartProps) => import("react").JSX.Element;
|
|
24
|
+
export declare const BoxPlot: ({ data, domain, statisticLabels, formatValue: suppliedFormatValue, labels, showTable, summary, className, ...props }: BoxPlotProps) => import("react").JSX.Element;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=expanded-charts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expanded-charts.d.ts","sourceRoot":"","sources":["../src/expanded-charts.tsx"],"names":[],"mappings":"AAIA,OAAO,EAA2I,KAAK,iBAAiB,EAAE,KAAK,mBAAmB,EAAE,KAAK,2BAA2B,EAAE,KAAK,yBAAyB,EAAE,KAAK,2BAA2B,EAAE,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAA2B,MAAM,uBAAuB,CAAA;AAE5Y,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAExD,UAAU,cAAe,SAAQ,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC;IAC3D,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAClC,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,MAAM,WAAW,oBAAqB,SAAQ,cAAc,EAAE,2BAA2B;IACvF,IAAI,CAAC,EAAE,SAAS,yBAAyB,EAAE,CAAA;IAC3C,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACrC,4DAA4D;IAC5D,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAClC;AACD,MAAM,WAAW,gBAAiB,SAAQ,cAAc;IAAG,IAAI,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;CAAE;AAC/F,MAAM,MAAM,sBAAsB,GAAG,2BAA2B,CAAA;AAChE,MAAM,WAAW,YAAa,SAAQ,cAAc,EAAE,mBAAmB;IACvE,IAAI,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAA;IACnC,eAAe,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAA;CAClD;AA+BD,eAAO,MAAM,eAAe,GAAI,6IAc7B,oBAAoB,gCAsHtB,CAAA;AAED,eAAO,MAAM,WAAW,GAAI,wEAQzB,gBAAgB,gCA8ClB,CAAA;AAED,eAAO,MAAM,OAAO,GAAI,sHAUrB,YAAY,gCAwFd,CAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { composeClassName, createLumenBoxPlotGeometry, createLumenCalendarHeatmapGeometry, createLumenFunnelGeometry, getLumenChartToneClassName, resolveLumenChartLabels } from '@santi020k/lumen-core';
|
|
4
|
+
import { Chart } from './components.js';
|
|
5
|
+
const defaultStatistics = { min: 'Lower whisker', q1: 'First quartile', median: 'Median', q3: 'Third quartile', max: 'Upper whisker', outliers: 'Outliers' };
|
|
6
|
+
const defaultWeekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
7
|
+
const weekdayLabel = (labels, day, weekStartsOn = 0) => (labels ?? defaultWeekdays)[(day + weekStartsOn) % 7] ?? defaultWeekdays[(day + weekStartsOn) % 7];
|
|
8
|
+
const chartNumberFormatter = (formatter) => formatter ?? String;
|
|
9
|
+
const calendarDateFormatter = (formatter) => formatter ?? String;
|
|
10
|
+
const emptyCalendarData = [];
|
|
11
|
+
const emptyFunnelData = [];
|
|
12
|
+
const emptyBoxData = [];
|
|
13
|
+
const DataTable = ({ text, headings, children }) => (_jsxs("details", { className: "ui-chart__data", children: [_jsx("summary", { children: text.viewData }), _jsx("div", { role: "group", tabIndex: 0, "aria-label": text.chartData, children: _jsxs("table", { children: [_jsx("thead", { children: _jsx("tr", { children: headings.map(heading => _jsx("th", { scope: "col", children: heading.label }, heading.id)) }) }), _jsx("tbody", { children: children })] }) })] }));
|
|
14
|
+
export const CalendarHeatmap = ({ data = emptyCalendarData, startDate, endDate, weekStartsOn, domain, formatValue = String, formatDate, weekdayLabels, labels, showTable = true, summary, className, ...props }) => {
|
|
15
|
+
const dateFormatter = calendarDateFormatter(formatDate);
|
|
16
|
+
const text = resolveLumenChartLabels(labels);
|
|
17
|
+
const model = createLumenCalendarHeatmapGeometry(data, { startDate, endDate, weekStartsOn, domain });
|
|
18
|
+
const format = (value) => value === null ? text.notAvailable : formatValue(value);
|
|
19
|
+
const available = model.cells.filter(cell => cell.value !== null).length;
|
|
20
|
+
return (_jsx(Chart, { ...props, className: composeClassName('ui-calendar-heatmap', className), summary: summary ?? (model.valid ? text.formatHeatmapSummary(available) : text.invalidData), children: !model.valid ?
|
|
21
|
+
_jsx("p", { className: "ui-chart__empty", role: "status", children: text.invalidData }) :
|
|
22
|
+
(_jsxs(_Fragment, { children: [available === 0 && _jsx("p", { className: "ui-chart__empty", role: "status", children: text.empty }), _jsxs("p", { className: "ui-calendar-heatmap__range", children: [dateFormatter(startDate), ' ', "\u2013", ' ', dateFormatter(endDate)] }), _jsx("div", { className: "ui-calendar-heatmap__plot", role: "group", tabIndex: 0, "aria-label": text.chartData, children: _jsxs("svg", { "aria-hidden": "true", width: model.weekCount * 18 + 36, height: "146", viewBox: `0 0 ${model.weekCount * 18 + 36} 146`, children: [Array.from({ length: 7 }, (_, day) => (_jsx("text", { x: "0", y: day * 18 + 16, className: "ui-calendar-heatmap__weekday", children: weekdayLabel(weekdayLabels, day, weekStartsOn) }, day))), model.cells.map(cell => (_jsxs("g", { children: [_jsx("rect", { className: "\n ui-calendar-heatmap__cell ui-chart-tone--series-1\n ", "data-missing": cell.value === null ? 'true' : undefined, x: cell.week * 18 + 36, y: cell.day * 18 + 4, width: "14", height: "14", rx: "3", style: { fillOpacity: cell.ratio === null ? 1 : 0.15 + cell.ratio * 0.85 }, children: _jsx("title", { children: `${dateFormatter(cell.date)}: ${format(cell.value)}` }) }), cell.value === null && (_jsx("text", { className: "ui-calendar-heatmap__missing", x: cell.week * 18 + 43, y: cell.day * 18 + 15, textAnchor: "middle", children: "\u00D7" }))] }, cell.date))), model.cells.filter(cell => cell.day === 0 && cell.week % 4 === 0).map(cell => (_jsx("text", { className: "ui-calendar-heatmap__weekday", x: cell.week * 18 + 36, y: "142", children: cell.date.slice(5) }, cell.date)))] }) }), _jsxs("div", { className: "ui-calendar-heatmap__legend", children: [_jsxs("span", { children: [text.low, ":", ' ', formatValue(model.domain.min)] }), _jsxs("span", { children: [text.high, ":", ' ', formatValue(model.domain.max)] }), _jsx("span", { className: "ui-calendar-heatmap__scale", "aria-hidden": "true" }), _jsxs("span", { children: ["\u00D7", text.notAvailable] })] }), !showTable && (_jsx("ul", { className: "ui-sr-only", children: model.cells.map(cell => (_jsxs("li", { children: [dateFormatter(cell.date), ":", ' ', format(cell.value)] }, cell.date))) })), showTable && (_jsx(DataTable, { text: text, headings: [{ id: 'category', label: text.category }, { id: 'value', label: text.value }], children: model.cells.map(cell => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: dateFormatter(cell.date) }), _jsx("td", { children: format(cell.value) })] }, cell.date))) }))] })) }));
|
|
23
|
+
};
|
|
24
|
+
export const FunnelChart = ({ data = emptyFunnelData, formatValue = String, labels, showTable = true, summary, className, ...props }) => {
|
|
25
|
+
const text = resolveLumenChartLabels(labels);
|
|
26
|
+
const model = createLumenFunnelGeometry(data);
|
|
27
|
+
const format = (value) => value === null ? text.notAvailable : formatValue(value);
|
|
28
|
+
return (_jsx(Chart, { ...props, className: composeClassName('ui-funnel-chart', className), summary: summary ?? (model.valid ? model.rows.map(row => `${row.label}: ${format(row.value)}`).join('. ') : text.invalidData), children: !model.valid || model.rows.length === 0 ?
|
|
29
|
+
_jsx("p", { className: "ui-chart__empty", role: "status", children: model.valid ? text.empty : text.invalidData }) :
|
|
30
|
+
(_jsxs(_Fragment, { children: [_jsx("ol", { className: "ui-funnel-chart__rows", children: model.rows.map((row, index) => (_jsxs("li", { className: getLumenChartToneClassName(row.tone, index), children: [_jsxs("div", { className: "ui-funnel-chart__label", children: [_jsx("span", { children: row.label }), _jsx("strong", { children: format(row.value) })] }), _jsx("div", { className: "ui-funnel-chart__track", "aria-hidden": "true", children: row.ratio !== null && (_jsx("span", { className: "ui-funnel-chart__bar", style: { width: `${row.ratio * 100}%` } })) })] }, row.id))) }), showTable && (_jsx(DataTable, { text: text, headings: [{ id: 'category', label: text.category }, { id: 'value', label: text.value }], children: model.rows.map(row => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: row.label }), _jsx("td", { children: format(row.value) })] }, row.id))) }))] })) }));
|
|
31
|
+
};
|
|
32
|
+
export const BoxPlot = ({ data = emptyBoxData, domain, statisticLabels, formatValue: suppliedFormatValue, labels, showTable = true, summary, className, ...props }) => {
|
|
33
|
+
const formatValue = chartNumberFormatter(suppliedFormatValue);
|
|
34
|
+
const text = resolveLumenChartLabels(labels);
|
|
35
|
+
const stats = { ...defaultStatistics, ...statisticLabels };
|
|
36
|
+
const model = createLumenBoxPlotGeometry(data, { domain });
|
|
37
|
+
const format = (value) => value === null ? text.notAvailable : formatValue(value);
|
|
38
|
+
const statistics = ['min', 'q1', 'median', 'q3', 'max'];
|
|
39
|
+
const exactRows = model.rows.map(row => ({
|
|
40
|
+
id: row.id,
|
|
41
|
+
label: `${row.label}: ${statistics.map(key => `${stats[key]}: ${format(row[key])}`).join(', ')}. ${stats.outliers}: ${(row.outliers ?? []).map(formatValue).join(', ') || '—'}`
|
|
42
|
+
}));
|
|
43
|
+
return (_jsx(Chart, { ...props, className: composeClassName('ui-box-plot', className), summary: summary ?? (model.valid ? exactRows.map(row => row.label).join('. ') : text.invalidData), children: !model.valid || model.rows.length === 0 ?
|
|
44
|
+
_jsx("p", { className: "ui-chart__empty", role: "status", children: model.valid ? text.empty : text.invalidData }) :
|
|
45
|
+
(_jsxs(_Fragment, { children: [_jsx("ul", { className: "ui-box-plot__rows", children: model.rows.map((row, index) => (_jsxs("li", { className: getLumenChartToneClassName(row.tone, index), children: [_jsxs("div", { className: "ui-box-plot__label", children: [_jsx("span", { children: row.label }), _jsxs("strong", { children: [stats.median, ":", ' ', format(row.median)] })] }), _jsxs("div", { className: "ui-box-plot__track", "aria-hidden": "true", children: [row.minPosition !== null && row.maxPosition !== null && (_jsx("span", { className: "ui-box-plot__whisker", style: { left: `${row.minPosition * 100}%`, width: `${(row.maxPosition - row.minPosition) * 100}%` } })), row.q1Position !== null && row.q3Position !== null && (_jsx("span", { className: "ui-box-plot__box", style: { left: `${row.q1Position * 100}%`, width: `${(row.q3Position - row.q1Position) * 100}%` } })), row.medianPosition !== null && (_jsx("span", { className: "ui-box-plot__median", style: { left: `${row.medianPosition * 100}%` } })), [{ id: 'min', position: row.minPosition }, { id: 'max', position: row.maxPosition }].map(({ position, id }) => position !== null && (_jsx("span", { className: "ui-box-plot__cap", style: { left: `${position * 100}%` } }, id))), Array.from(new Set(row.outlierPositions)).map(position => (_jsx("span", { className: "ui-box-plot__outlier", style: { left: `${position * 100}%` } }, position)))] })] }, row.id))) }), _jsx("div", { className: "ui-bullet-chart__ticks", "aria-hidden": "true", children: model.ticks.map(tick => _jsx("span", { children: formatValue(tick) }, tick)) }), !showTable && _jsx("ul", { className: "ui-sr-only", children: exactRows.map(row => _jsx("li", { children: row.label }, row.id)) }), showTable && (_jsx(DataTable, { text: text, headings: [{ id: 'category', label: text.category }, ...statistics.map(key => ({ id: key, label: stats[key] })), { id: 'outliers', label: stats.outliers }], children: model.rows.map(row => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: row.label }), statistics.map(key => _jsx("td", { children: format(row[key]) }, key)), _jsx("td", { children: (row.outliers ?? []).map(formatValue).join(', ') || '—' })] }, row.id))) }))] })) }));
|
|
46
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
export interface FloatingPanelOptions {
|
|
3
|
+
/** Logical alignment follows the trigger's inherited text direction. */
|
|
4
|
+
placement?: 'bottom-end' | 'bottom-start' | 'top-end' | 'top-start' | undefined;
|
|
5
|
+
offset?: number | undefined;
|
|
6
|
+
collisionPadding?: number | undefined;
|
|
7
|
+
/** Disable placement when an application owns a specialized panel layout. */
|
|
8
|
+
positioning?: 'anchored' | 'none' | undefined;
|
|
9
|
+
}
|
|
10
|
+
export declare const useFloatingPanel: (open: boolean, triggerRef: RefObject<HTMLElement | null>, panelRef: RefObject<HTMLElement | null>, close: () => void, { placement, offset, collisionPadding, positioning }: FloatingPanelOptions) => void;
|
|
11
|
+
//# sourceMappingURL=floating-panel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"floating-panel.d.ts","sourceRoot":"","sources":["../src/floating-panel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,MAAM,WAAW,oBAAoB;IACnC,wEAAwE;IACxE,SAAS,CAAC,EAAE,YAAY,GAAG,cAAc,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAA;IAC/E,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACrC,6EAA6E;IAC7E,WAAW,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAAA;CAC9C;AAgND,eAAO,MAAM,gBAAgB,GAC3B,MAAM,OAAO,EACb,YAAY,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,EACzC,UAAU,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,EACvC,OAAO,MAAM,IAAI,EACjB,sDAAoF,oBAAoB,KACvG,IA4BF,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useEffect,useLayoutEffect,useRef}from"react";const finiteDistance=(value,fallback)=>value===void 0||!Number.isFinite(value)?fallback:Math.max(0,value),clamp=(value,minimum,maximum)=>{const upperBound=Math.max(minimum,maximum);return Math.max(minimum,Math.min(value,upperBound))},styleKeys=["box-sizing","position","inset","left","top","right","bottom","margin","max-width","max-height","overflow-y"],viewportBounds=({view,padding})=>{const viewport=view.visualViewport,left=(viewport?.offsetLeft??0)+padding,top=(viewport?.offsetTop??0)+padding,width=Math.max(0,(viewport?.width??view.innerWidth)-padding*2),height=Math.max(0,(viewport?.height??view.innerHeight)-padding*2);return{left,top,width,height,right:left+width,bottom:top+height}},shouldPlaceBelow=(placement,above,below,height)=>placement.startsWith("bottom")?below>=height||below>=above:!(above>=height||above>=below),positionPanel=context=>{const{panel,trigger,view,placement,gap}=context,viewport=viewportBounds(context),anchor=trigger.getBoundingClientRect();panel.style.boxSizing="border-box",panel.style.position="fixed",panel.style.inset="auto",panel.style.margin="0",panel.style.maxWidth=`${viewport.width}px`,panel.style.maxHeight=`${viewport.height}px`,panel.style.overflowY="auto";const below=Math.max(0,viewport.bottom-anchor.bottom-gap),above=Math.max(0,anchor.top-viewport.top-gap),placeBelow=shouldPlaceBelow(placement,above,below,panel.getBoundingClientRect().height),availableHeight=placeBelow?below:above;panel.style.maxHeight=`${Math.min(viewport.height,availableHeight||viewport.height)}px`;const bounds=panel.getBoundingClientRect(),rtl=view.getComputedStyle(trigger).direction==="rtl",x=placement.endsWith("end")!==rtl?anchor.right-bounds.width:anchor.left,y=placeBelow?anchor.bottom+gap:anchor.top-bounds.height-gap,alignment=placement.endsWith("end")?"end":"start";panel.style.left=`${clamp(x,viewport.left,viewport.right-bounds.width)}px`,panel.style.top=`${clamp(y,viewport.top,viewport.bottom-bounds.height)}px`,panel.dataset.placement=`${placeBelow?"bottom":"top"}-${alignment}`},observePosition=(context,close)=>{const{panel,trigger,view}=context,document=panel.ownerDocument,NodeType=document.defaultView?.Node,position=()=>{positionPanel(context)},focusOutside=event=>{NodeType&&event.target instanceof NodeType&&!panel.contains(event.target)&&!trigger.contains(event.target)&&close()},scroll=event=>{(!(NodeType&&event.target instanceof NodeType)||!panel.contains(event.target))&&position()},observer=typeof ResizeObserver=="function"?new ResizeObserver(position):void 0;return observer?.observe(trigger),observer?.observe(panel),document.addEventListener("focusin",focusOutside),document.addEventListener("scroll",scroll,!0),view.addEventListener("resize",position),view.visualViewport?.addEventListener("resize",position),view.visualViewport?.addEventListener("scroll",position),()=>{observer?.disconnect(),document.removeEventListener("focusin",focusOutside),document.removeEventListener("scroll",scroll,!0),view.removeEventListener("resize",position),view.visualViewport?.removeEventListener("resize",position),view.visualViewport?.removeEventListener("scroll",position)}},restoreExternalFocus=(owner,focused)=>{const ElementType=owner.defaultView?.HTMLElement;ElementType&&focused instanceof ElementType&&focused.isConnected&&owner.activeElement!==focused&&focused.focus({preventScroll:!0})},restoreFocus=(panel,trigger,topLayer)=>{const document=panel.ownerDocument,focused=document.activeElement,ownedFocus=focused!==null&&panel.contains(focused);topLayer&&panel.matches(":popover-open")&&panel.hidePopover(),ownedFocus&&trigger.isConnected&&trigger.focus({preventScroll:!0}),ownedFocus||restoreExternalFocus(document,focused)},restoreAttribute=(panel,name,value)=>{value===null?panel.removeAttribute(name):panel.setAttribute(name,value)},connectPanel=(context,close)=>{const{panel,trigger}=context,previousPopover=panel.getAttribute("popover"),previousPlacement=panel.getAttribute("data-placement"),previousStyles=styleKeys.map(key=>[key,panel.style.getPropertyValue(key),panel.style.getPropertyPriority(key)]),topLayer=typeof panel.showPopover=="function";panel.hidden=!1,topLayer&&(panel.setAttribute("popover","manual"),panel.showPopover()),positionPanel(context),panel.querySelector('[role="menuitem"]:not(:disabled):not([aria-disabled="true"])')?.focus({preventScroll:!0});const stopObserving=observePosition(context,close);return()=>{stopObserving(),restoreFocus(panel,trigger,topLayer),restoreAttribute(panel,"popover",previousPopover),restoreAttribute(panel,"data-placement",previousPlacement);for(const[key,value,priority]of previousStyles)value?panel.style.setProperty(key,value,priority):panel.style.removeProperty(key)}},useFloatingPanel=(open,triggerRef,panelRef,close,{placement="bottom-start",offset,collisionPadding,positioning="anchored"})=>{const closeRef=useRef(close);useLayoutEffect(()=>{closeRef.current=close},[close]),useEffect(()=>{const panel=panelRef.current,trigger=triggerRef.current;if(!open||positioning==="none"||!panel||!trigger)return;const view=panel.ownerDocument.defaultView;if(view)return connectPanel({panel,trigger,view,placement,gap:finiteDistance(offset,6),padding:finiteDistance(collisionPadding,8)},()=>{closeRef.current()})},[open,positioning,placement,offset,collisionPadding,triggerRef,panelRef])};export{useFloatingPanel};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { RefObject, SyntheticEvent } from 'react';
|
|
2
|
+
export type LumenFormControl = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
3
|
+
export interface LumenFormIssue {
|
|
4
|
+
name: string;
|
|
5
|
+
controlId: string;
|
|
6
|
+
message: string;
|
|
7
|
+
label?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const isLumenFormControl: (element: EventTarget) => element is LumenFormControl;
|
|
10
|
+
export interface LumenFormWorkflowOptions {
|
|
11
|
+
getControlName?: (control: LumenFormControl) => string;
|
|
12
|
+
validateControl?: (control: LumenFormControl) => string;
|
|
13
|
+
validateValues?: (form: HTMLFormElement) => readonly LumenFormIssue[];
|
|
14
|
+
/** Map a changed field name to the other field names that need validation. */
|
|
15
|
+
dependencies?: Readonly<Record<string, readonly string[]>>;
|
|
16
|
+
}
|
|
17
|
+
export interface LumenFormWorkflow {
|
|
18
|
+
formRef: RefObject<HTMLFormElement | null>;
|
|
19
|
+
errors: readonly LumenFormIssue[];
|
|
20
|
+
dirty: boolean;
|
|
21
|
+
attempted: boolean;
|
|
22
|
+
validate: (form?: HTMLFormElement | null, names?: readonly string[]) => boolean;
|
|
23
|
+
refresh: (form?: HTMLFormElement | null, changedName?: string) => void;
|
|
24
|
+
setErrors: (errors: readonly LumenFormIssue[]) => void;
|
|
25
|
+
markSaved: () => void;
|
|
26
|
+
formProps: {
|
|
27
|
+
ref: RefObject<HTMLFormElement | null>;
|
|
28
|
+
noValidate: true;
|
|
29
|
+
onBlur: (event: SyntheticEvent<HTMLFormElement>) => void;
|
|
30
|
+
onChange: (event: SyntheticEvent<HTMLFormElement>) => void;
|
|
31
|
+
onInput: (event: SyntheticEvent<HTMLFormElement>) => void;
|
|
32
|
+
onReset: (event: SyntheticEvent<HTMLFormElement>) => void;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Validation and edit state; schemas, requests, draft storage and business policy remain application-owned. */
|
|
36
|
+
export declare const useLumenFormWorkflow: ({ getControlName, validateControl, validateValues, dependencies }?: LumenFormWorkflowOptions) => LumenFormWorkflow;
|
|
37
|
+
export interface LumenFieldArrayItem<Value> {
|
|
38
|
+
id: string;
|
|
39
|
+
value: Value;
|
|
40
|
+
}
|
|
41
|
+
/** Stable row identity survives insertions, removals and reordering. */
|
|
42
|
+
export declare const useLumenFieldArray: <Value>(initialValues?: readonly Value[]) => {
|
|
43
|
+
items: LumenFieldArrayItem<Value>[];
|
|
44
|
+
append: (value: Value) => string;
|
|
45
|
+
remove: (id: string) => void;
|
|
46
|
+
update: (id: string, value: Value) => void;
|
|
47
|
+
move: (id: string, offset: number) => void;
|
|
48
|
+
};
|
|
49
|
+
/** A failed or superseded check cannot replace the newest result. */
|
|
50
|
+
export declare const useLumenAsyncCheck: <Value, Result>(check: (value: Value, signal: AbortSignal) => Promise<Result>) => {
|
|
51
|
+
state: {
|
|
52
|
+
status: "idle" | "pending";
|
|
53
|
+
} | {
|
|
54
|
+
status: "success";
|
|
55
|
+
result: Result;
|
|
56
|
+
} | {
|
|
57
|
+
status: "error";
|
|
58
|
+
error: unknown;
|
|
59
|
+
};
|
|
60
|
+
run: (value: Value) => Promise<Result | undefined>;
|
|
61
|
+
cancel: () => void;
|
|
62
|
+
};
|
|
63
|
+
/** Steps remain application-defined and validate before advancing. */
|
|
64
|
+
export declare const useLumenFormSteps: (steps: readonly {
|
|
65
|
+
id: string;
|
|
66
|
+
fields: readonly string[];
|
|
67
|
+
}[], validate: (names: readonly string[]) => boolean) => {
|
|
68
|
+
activeId: string;
|
|
69
|
+
index: number;
|
|
70
|
+
goTo: (id: string) => boolean;
|
|
71
|
+
next: () => boolean;
|
|
72
|
+
previous: () => boolean;
|
|
73
|
+
};
|
|
74
|
+
/** Prompt on reload/close; application routers and dialogs should use the same dirty state. */
|
|
75
|
+
export declare const useLumenBeforeUnload: (dirty: boolean) => void;
|
|
76
|
+
export { type LumenReviewResult, type LumenReviewStatus, type LumenReviewWorkflowOptions, useLumenReviewWorkflow } from './review-workflow.js';
|
|
77
|
+
//# sourceMappingURL=form-workflow.d.ts.map
|