@pallasoft/ps-core-components 2.35.0 → 2.35.2
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 +13 -0
- package/dist/components/ps-action-tile.js +1 -1
- package/dist/components/ps-badge.js +1 -1
- package/dist/components/ps-card-block-skeleton.js +1 -1
- package/dist/components/ps-card.js +1 -1
- package/dist/components/ps-form-fields-skeleton.js +1 -1
- package/dist/components/ps-intro-skeleton.js +1 -1
- package/dist/components/ps-page-skeleton.js +1 -1
- package/dist/components/ps-pill.js +1 -1
- package/dist/components/ps-reflective-app-shell.js +1 -1
- package/dist/components/ps-section-header.js +1 -1
- package/dist/components/ps-skeleton-media.js +1 -1
- package/dist/components/ps-skeleton-table.js +2 -2
- package/dist/components/ps-tab-row-skeleton.js +3 -1
- package/dist/constants/ps-optimistic-config.d.ts +1 -0
- package/dist/constants/ps-optimistic-config.js +1 -0
- package/dist/constants/ps-skeleton-config.d.ts +4 -0
- package/dist/constants/ps-skeleton-config.js +22 -4
- package/dist/constants/ps-skeleton-shape.d.ts +5 -0
- package/dist/constants/ps-skeleton-shape.js +5 -0
- package/dist/esm/components/ps-action-tile.js +1 -1
- package/dist/esm/components/ps-badge.js +1 -1
- package/dist/esm/components/ps-card-block-skeleton.js +1 -1
- package/dist/esm/components/ps-card.js +1 -1
- package/dist/esm/components/ps-form-fields-skeleton.js +1 -1
- package/dist/esm/components/ps-intro-skeleton.js +1 -1
- package/dist/esm/components/ps-page-skeleton.js +1 -1
- package/dist/esm/components/ps-pill.js +1 -1
- package/dist/esm/components/ps-reflective-app-shell.js +1 -1
- package/dist/esm/components/ps-section-header.js +1 -1
- package/dist/esm/components/ps-skeleton-media.js +1 -1
- package/dist/esm/components/ps-skeleton-table.js +2 -2
- package/dist/esm/components/ps-tab-row-skeleton.js +3 -1
- package/dist/esm/constants/ps-optimistic-config.js +1 -0
- package/dist/esm/constants/ps-skeleton-config.js +22 -4
- package/dist/esm/constants/ps-skeleton-shape.js +5 -0
- package/dist/esm/hooks/use-ps-change-queue.js +7 -2
- package/dist/esm/hooks/use-ps-optimistic-fetcher.js +48 -10
- package/dist/esm/hooks/use-ps-optimistic-save.js +2 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/interfaces/ps-optimistic-failure-reason.js +1 -0
- package/dist/esm/interfaces/ps-optimistic-failure.js +1 -0
- package/dist/esm/interfaces/ps-optimistic-outcome.js +1 -0
- package/dist/esm/services/ps-optimistic-fetcher-service.js +12 -0
- package/dist/hooks/use-ps-change-queue.js +7 -2
- package/dist/hooks/use-ps-optimistic-fetcher.d.ts +1 -1
- package/dist/hooks/use-ps-optimistic-fetcher.js +48 -10
- package/dist/hooks/use-ps-optimistic-save.d.ts +1 -1
- package/dist/hooks/use-ps-optimistic-save.js +2 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/interfaces/ps-optimistic-failure-reason.d.ts +1 -0
- package/dist/interfaces/ps-optimistic-failure-reason.js +2 -0
- package/dist/interfaces/ps-optimistic-failure.d.ts +6 -0
- package/dist/interfaces/ps-optimistic-failure.js +2 -0
- package/dist/interfaces/ps-optimistic-fetcher-input.d.ts +2 -0
- package/dist/interfaces/ps-optimistic-outcome.d.ts +5 -0
- package/dist/interfaces/ps-optimistic-outcome.js +2 -0
- package/dist/interfaces/ps-optimistic-save-input.d.ts +2 -0
- package/dist/services/ps-optimistic-fetcher-service.d.ts +6 -0
- package/dist/services/ps-optimistic-fetcher-service.js +16 -0
- package/package.json +1 -3
package/README.md
CHANGED
|
@@ -8,3 +8,16 @@ Shared Pallasoft core components package.
|
|
|
8
8
|
- `npm run lint` runs ESLint using the shared package baseline.
|
|
9
9
|
- `npm run format:check` checks Prettier formatting.
|
|
10
10
|
- `npm run release` publishes through semantic-release.
|
|
11
|
+
|
|
12
|
+
## Optimistic saves
|
|
13
|
+
|
|
14
|
+
Apps use `usePsOptimisticSave` for row lists and `usePsOptimisticFetcher` for custom submissions
|
|
15
|
+
(batches go through `merge`). `usePsChangeQueue` is internal plumbing; do not use it in apps.
|
|
16
|
+
|
|
17
|
+
A change shows at once, is sent in order, and either confirms or rolls back. A change counts as
|
|
18
|
+
failed when the server refuses it (`isSuccess` is false), when no new response arrives (network
|
|
19
|
+
failure or a thrown action), when the request takes longer than
|
|
20
|
+
`PsOptimisticConfig.submitTimeoutMs`, or when the component unmounts mid-request. Rows then roll
|
|
21
|
+
back, including changes queued behind the failed one. Pass the optional `onError` to tell the user:
|
|
22
|
+
`onError({ changes, data, reason })`, where `reason` is `rejected`, `no-response`, `timeout`,
|
|
23
|
+
`unmounted` or `error`.
|
|
@@ -9,5 +9,5 @@ const variantClassNames = {
|
|
|
9
9
|
default: 'ps-action-tile',
|
|
10
10
|
primary: 'ps-action-tile ps-action-tile-primary',
|
|
11
11
|
};
|
|
12
|
-
const PsActionTile = ({ className, description, icon, isLoading = false, label, type = 'button', variant = 'default', ...props }) => isLoading ? ((0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: ps_design_system_1.PallasoftClassNameService.merge('h-16
|
|
12
|
+
const PsActionTile = ({ className, description, icon, isLoading = false, label, type = 'button', variant = 'default', ...props }) => isLoading ? ((0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: ps_design_system_1.PallasoftClassNameService.merge('h-16', className), shape: ps_skeleton_shape_1.PsSkeletonShape.Card })) : ((0, jsx_runtime_1.jsxs)("button", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-focus-ring w-full', variantClassNames[variant], className), type: type, ...props, children: [(0, jsx_runtime_1.jsxs)("span", { className: 'min-w-0', children: [(0, jsx_runtime_1.jsx)("span", { className: 'block break-words', children: label }), description ? ((0, jsx_runtime_1.jsx)("span", { className: ps_design_system_1.PallasoftClassNameService.merge('mt-0.5 block text-xs font-medium leading-5', variant === 'primary' ? 'text-white/75' : 'text-ink/50'), children: description })) : null] }), icon ? ((0, jsx_runtime_1.jsx)("span", { className: ps_design_system_1.PallasoftClassNameService.merge('flex h-9 w-9 shrink-0 items-center justify-center rounded-lg', variant === 'primary' ? 'bg-white/15 text-white' : 'ps-icon-surface'), children: icon })) : null] }));
|
|
13
13
|
exports.PsActionTile = PsActionTile;
|
|
@@ -12,5 +12,5 @@ const toneClassNames = {
|
|
|
12
12
|
success: 'bg-success-soft text-emerald-700 dark:bg-emerald-400/18 dark:text-emerald-50',
|
|
13
13
|
warning: 'bg-warning-soft text-amber-800 dark:bg-amber-400/18 dark:text-amber-50',
|
|
14
14
|
};
|
|
15
|
-
const PsBadge = ({ children, className, isLoading = false, tone = 'neutral', }) => isLoading ? ((0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className:
|
|
15
|
+
const PsBadge = ({ children, className, isLoading = false, tone = 'neutral', }) => isLoading ? ((0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: className, shape: ps_skeleton_shape_1.PsSkeletonShape.Badge })) : ((0, jsx_runtime_1.jsx)("span", { className: ps_design_system_1.PallasoftClassNameService.merge('inline-flex min-h-7 w-fit items-center justify-center whitespace-nowrap rounded-ps-control px-3 py-0.5 text-center text-xs font-semibold capitalize', toneClassNames[tone], className), children: children }));
|
|
16
16
|
exports.PsBadge = PsBadge;
|
|
@@ -4,5 +4,5 @@ exports.PsCardBlockSkeleton = void 0;
|
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const ps_skeleton_shape_1 = require("../constants/ps-skeleton-shape");
|
|
6
6
|
const ps_skeleton_1 = require("./ps-skeleton");
|
|
7
|
-
const PsCardBlockSkeleton = ({ height }) => ((0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { height: height,
|
|
7
|
+
const PsCardBlockSkeleton = ({ height }) => ((0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { height: height, shape: ps_skeleton_shape_1.PsSkeletonShape.Card }));
|
|
8
8
|
exports.PsCardBlockSkeleton = PsCardBlockSkeleton;
|
|
@@ -17,5 +17,5 @@ const variantClassNames = {
|
|
|
17
17
|
glass: 'ps-glass',
|
|
18
18
|
muted: 'ps-card-muted',
|
|
19
19
|
};
|
|
20
|
-
const PsCard = ({ children, className, density = 'normal', eyebrow, isLoading = false, title, trailing, variant = 'default', }) => isLoading ? ((0, jsx_runtime_1.jsxs)(ps_skeleton_group_1.PsSkeletonGroup, { className: ps_design_system_1.PallasoftClassNameService.merge('flex min-w-0 flex-col gap-3 rounded-ps-card', variantClassNames[variant], densityClassNames[density], className), label: title === undefined ? 'Loading' : title, children: [(0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: 'h-3 w-32' }), (0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: 'h-20
|
|
20
|
+
const PsCard = ({ children, className, density = 'normal', eyebrow, isLoading = false, title, trailing, variant = 'default', }) => isLoading ? ((0, jsx_runtime_1.jsxs)(ps_skeleton_group_1.PsSkeletonGroup, { className: ps_design_system_1.PallasoftClassNameService.merge('flex min-w-0 flex-col gap-3 rounded-ps-card', variantClassNames[variant], densityClassNames[density], className), label: title === undefined ? 'Loading' : title, children: [(0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: 'h-3 w-32' }), (0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { shape: ps_skeleton_shape_1.PsSkeletonShape.Control, className: 'h-20' })] })) : ((0, jsx_runtime_1.jsxs)("section", { className: ps_design_system_1.PallasoftClassNameService.merge('min-w-0 overflow-hidden rounded-ps-card', variantClassNames[variant], densityClassNames[density], className), children: [title ? ((0, jsx_runtime_1.jsxs)("div", { className: 'mb-3 flex min-w-0 items-start justify-between gap-3', children: [(0, jsx_runtime_1.jsxs)("div", { className: 'min-w-0', children: [eyebrow ? (0, jsx_runtime_1.jsx)("p", { className: 'ps-eyebrow break-words', children: eyebrow }) : null, (0, jsx_runtime_1.jsx)("h2", { className: 'mt-1 break-words text-base font-bold text-ink', children: title })] }), trailing ? (0, jsx_runtime_1.jsx)("div", { className: 'shrink-0', children: trailing }) : null] })) : null, children] }));
|
|
21
21
|
exports.PsCard = PsCard;
|
|
@@ -6,5 +6,5 @@ const ps_design_system_1 = require("@pallasoft/ps-design-system");
|
|
|
6
6
|
const ps_page_skeleton_config_1 = require("../constants/ps-page-skeleton-config");
|
|
7
7
|
const ps_skeleton_shape_1 = require("../constants/ps-skeleton-shape");
|
|
8
8
|
const ps_skeleton_1 = require("./ps-skeleton");
|
|
9
|
-
const PsFormFieldsSkeleton = ({ columnsClassName = ps_page_skeleton_config_1.PsPageSkeletonConfig.formColumnsClassName, fields = ps_page_skeleton_config_1.PsPageSkeletonConfig.formFields, }) => ((0, jsx_runtime_1.jsx)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('grid gap-4', columnsClassName), children: fields.map((field) => ((0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-col gap-2', children: [(0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: ps_page_skeleton_config_1.PsPageSkeletonConfig.formLabelClassName }), (0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, {
|
|
9
|
+
const PsFormFieldsSkeleton = ({ columnsClassName = ps_page_skeleton_config_1.PsPageSkeletonConfig.formColumnsClassName, fields = ps_page_skeleton_config_1.PsPageSkeletonConfig.formFields, }) => ((0, jsx_runtime_1.jsx)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('grid gap-4', columnsClassName), children: fields.map((field) => ((0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-col gap-2', children: [(0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: ps_page_skeleton_config_1.PsPageSkeletonConfig.formLabelClassName }), (0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { shape: ps_skeleton_shape_1.PsSkeletonShape.Control })] }, field))) }));
|
|
10
10
|
exports.PsFormFieldsSkeleton = PsFormFieldsSkeleton;
|
|
@@ -3,5 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.PsIntroSkeleton = void 0;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const ps_skeleton_1 = require("./ps-skeleton");
|
|
6
|
-
const PsIntroSkeleton = () => ((0, jsx_runtime_1.jsxs)("div", { className: 'mx-auto flex w-full max-w-3xl flex-col items-center gap-4', children: [(0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, {
|
|
6
|
+
const PsIntroSkeleton = () => ((0, jsx_runtime_1.jsxs)("div", { className: 'mx-auto flex w-full max-w-3xl flex-col items-center gap-4', children: [(0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: 'h-3.5 w-24' }), (0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: 'h-10 w-[70%]' }), (0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: 'h-[1.125rem] w-[85%]' })] }));
|
|
7
7
|
exports.PsIntroSkeleton = PsIntroSkeleton;
|
|
@@ -8,5 +8,5 @@ const ps_page_layout_1 = require("./ps-page-layout");
|
|
|
8
8
|
const ps_page_stack_1 = require("./ps-page-stack");
|
|
9
9
|
const ps_section_header_1 = require("./ps-section-header");
|
|
10
10
|
const ps_skeleton_1 = require("./ps-skeleton");
|
|
11
|
-
const PsPageSkeleton = ({ children, hasActions = false, isNarrow = false, loadingLabel, title, }) => ((0, jsx_runtime_1.jsx)(ps_page_layout_1.PsPageLayout, { isNarrow: isNarrow, children: (0, jsx_runtime_1.jsxs)(ps_page_stack_1.PsPageStack, { children: [title === undefined ? ((0, jsx_runtime_1.jsx)(ps_section_header_1.PsSectionHeader, { isLoading: true, title: loadingLabel, trailing: hasActions })) : ((0, jsx_runtime_1.jsx)(ps_page_header_1.PsPageHeader, { actions: hasActions ? (
|
|
11
|
+
const PsPageSkeleton = ({ children, hasActions = false, isNarrow = false, loadingLabel, title, }) => ((0, jsx_runtime_1.jsx)(ps_page_layout_1.PsPageLayout, { isNarrow: isNarrow, children: (0, jsx_runtime_1.jsxs)(ps_page_stack_1.PsPageStack, { children: [title === undefined ? ((0, jsx_runtime_1.jsx)(ps_section_header_1.PsSectionHeader, { isLoading: true, title: loadingLabel, trailing: hasActions })) : ((0, jsx_runtime_1.jsx)(ps_page_header_1.PsPageHeader, { actions: hasActions ? (0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { shape: ps_skeleton_shape_1.PsSkeletonShape.Button }) : undefined, title: title })), children] }) }));
|
|
12
12
|
exports.PsPageSkeleton = PsPageSkeleton;
|
|
@@ -12,5 +12,5 @@ const toneClassNames = {
|
|
|
12
12
|
success: 'bg-emerald-50 text-emerald-700 ring-emerald-100 dark:bg-emerald-400/18 dark:text-emerald-50 dark:ring-emerald-200/22',
|
|
13
13
|
warning: 'bg-amber-50 text-amber-800 ring-amber-100 dark:bg-amber-400/18 dark:text-amber-50 dark:ring-amber-200/24',
|
|
14
14
|
};
|
|
15
|
-
const PsPill = ({ children, className, isLoading = false, tone = 'neutral', }) => isLoading ? ((0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className:
|
|
15
|
+
const PsPill = ({ children, className, isLoading = false, tone = 'neutral', }) => isLoading ? ((0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: className, shape: ps_skeleton_shape_1.PsSkeletonShape.Pill })) : ((0, jsx_runtime_1.jsx)("span", { className: ps_design_system_1.PallasoftClassNameService.merge('inline-flex min-h-8 min-w-[5.5rem] max-w-full items-center justify-center rounded-ps-control px-5 py-1 text-center text-[0.72rem] font-black capitalize tracking-[0.01em] ring-1 ring-inset', toneClassNames[tone], className), children: (0, jsx_runtime_1.jsx)("span", { className: 'min-w-0 break-words', children: children }) }));
|
|
16
16
|
exports.PsPill = PsPill;
|
|
@@ -12,6 +12,6 @@ const PsReflectiveAppShell = ({ children, className, contentClassName, frameClas
|
|
|
12
12
|
contentRef.current?.scrollTo({ top: 0, left: 0 });
|
|
13
13
|
window.scrollTo({ top: 0, left: 0 });
|
|
14
14
|
}, [scrollResetKey]);
|
|
15
|
-
return ((0, jsx_runtime_1.jsx)("section", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-reflective-frame h-[100dvh] overflow-hidden text-ink', className), children: (0, jsx_runtime_1.jsxs)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-liquid-strong grid h-full min-h-0 w-full overflow-hidden border-line/70 bg-white/70 backdrop-blur-2xl transition-[grid-template-columns] duration-200 dark:border-white/10 dark:bg-surface/70 md:grid-cols-[var(--ps-sidebar-width)_minmax(0,1fr)]', frameClassName), style: { ['--ps-sidebar-width']: sidebarWidth }, children: [sidebar, (0, jsx_runtime_1.jsxs)("div", { className: 'flex min-h-0 min-w-0 flex-col bg-white/32 dark:bg-white/[0.02]', children: [topbar, (0, jsx_runtime_1.jsx)("main", { ref: contentRef, className: ps_design_system_1.PallasoftClassNameService.merge('ps-scrollbar min-h-0 flex-1 overflow-y-auto px-4
|
|
15
|
+
return ((0, jsx_runtime_1.jsx)("section", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-reflective-frame h-[100dvh] overflow-hidden text-ink', className), children: (0, jsx_runtime_1.jsxs)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-liquid-strong grid h-full min-h-0 w-full overflow-hidden border-line/70 bg-white/70 backdrop-blur-2xl transition-[grid-template-columns] duration-200 dark:border-white/10 dark:bg-surface/70 md:grid-cols-[var(--ps-sidebar-width)_minmax(0,1fr)]', frameClassName), style: { ['--ps-sidebar-width']: sidebarWidth }, children: [sidebar, (0, jsx_runtime_1.jsxs)("div", { className: 'flex min-h-0 min-w-0 flex-col bg-white/32 dark:bg-white/[0.02]', children: [topbar, (0, jsx_runtime_1.jsx)("main", { ref: contentRef, className: ps_design_system_1.PallasoftClassNameService.merge('ps-scrollbar min-h-0 flex-1 overflow-y-auto px-4 ps-page-enter sm:px-5 lg:px-6 xl:px-7', contentClassName), children: (0, jsx_runtime_1.jsx)("div", { className: 'py-5', children: children }) })] })] }) }));
|
|
16
16
|
};
|
|
17
17
|
exports.PsReflectiveAppShell = PsReflectiveAppShell;
|
|
@@ -5,5 +5,5 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
5
5
|
const ps_skeleton_shape_1 = require("../constants/ps-skeleton-shape");
|
|
6
6
|
const ps_skeleton_1 = require("./ps-skeleton");
|
|
7
7
|
const ps_skeleton_group_1 = require("./ps-skeleton-group");
|
|
8
|
-
const PsSectionHeader = ({ description, eyebrow, isLoading = false, leading, title, trailing, }) => isLoading ? ((0, jsx_runtime_1.jsxs)(ps_skeleton_group_1.PsSkeletonGroup, { className: 'flex flex-wrap items-end justify-between gap-x-4 gap-y-3', label: title, children: [(0, jsx_runtime_1.jsxs)("div", { className: 'flex min-w-0 flex-1 basis-auto flex-col gap-2', children: [eyebrow ? (0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: 'h-2.5 w-24' }) : null, (0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: 'h-6 w-52' }), description ? (0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: 'h-3 w-72' }) : null] }), trailing ? (0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, {
|
|
8
|
+
const PsSectionHeader = ({ description, eyebrow, isLoading = false, leading, title, trailing, }) => isLoading ? ((0, jsx_runtime_1.jsxs)(ps_skeleton_group_1.PsSkeletonGroup, { className: 'flex flex-wrap items-end justify-between gap-x-4 gap-y-3', label: title, children: [(0, jsx_runtime_1.jsxs)("div", { className: 'flex min-w-0 flex-1 basis-auto flex-col gap-2', children: [eyebrow ? (0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: 'h-2.5 w-24' }) : null, (0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: 'h-6 w-52' }), description ? (0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: 'h-3 w-72' }) : null] }), trailing ? (0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { shape: ps_skeleton_shape_1.PsSkeletonShape.Button }) : null] })) : ((0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-wrap items-end justify-between gap-x-4 gap-y-3', children: [(0, jsx_runtime_1.jsxs)("div", { className: 'flex min-w-0 flex-1 basis-auto flex-col gap-2', children: [leading, eyebrow ? ((0, jsx_runtime_1.jsx)("p", { className: 'text-[0.68rem] font-semibold uppercase tracking-[0.22em] text-ink/50', children: eyebrow })) : null, (0, jsx_runtime_1.jsx)("h1", { className: 'break-words text-xl font-extrabold text-ink sm:text-2xl', children: title }), description ? ((0, jsx_runtime_1.jsx)("p", { className: 'max-w-3xl text-sm leading-6 text-ink/60', children: description })) : null] }), trailing ? ((0, jsx_runtime_1.jsx)("div", { className: 'flex w-full shrink-0 items-center justify-end sm:ml-auto sm:w-auto', children: trailing })) : null] }));
|
|
9
9
|
exports.PsSectionHeader = PsSectionHeader;
|
|
@@ -6,5 +6,5 @@ const ps_design_system_1 = require("@pallasoft/ps-design-system");
|
|
|
6
6
|
const ps_skeleton_shape_1 = require("../constants/ps-skeleton-shape");
|
|
7
7
|
const ps_skeleton_1 = require("./ps-skeleton");
|
|
8
8
|
const ps_skeleton_group_1 = require("./ps-skeleton-group");
|
|
9
|
-
const PsSkeletonMedia = ({ aspect = '16 / 9', className, hasCaption = true, label, }) => ((0, jsx_runtime_1.jsxs)(ps_skeleton_group_1.PsSkeletonGroup, { className: ps_design_system_1.PallasoftClassNameService.merge('w-full', className), label: label, children: [(0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { aspect: aspect,
|
|
9
|
+
const PsSkeletonMedia = ({ aspect = '16 / 9', className, hasCaption = true, label, }) => ((0, jsx_runtime_1.jsxs)(ps_skeleton_group_1.PsSkeletonGroup, { className: ps_design_system_1.PallasoftClassNameService.merge('w-full', className), label: label, children: [(0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { aspect: aspect, shape: ps_skeleton_shape_1.PsSkeletonShape.Card }), hasCaption ? (0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { lines: 2, shape: ps_skeleton_shape_1.PsSkeletonShape.Text }) : null] }));
|
|
10
10
|
exports.PsSkeletonMedia = PsSkeletonMedia;
|
|
@@ -15,8 +15,8 @@ const PsSkeletonTable = ({ alignments, className, columns = 4, hasFooter = false
|
|
|
15
15
|
const cell = (rowIndex, columnIndex, isStrong) => ((0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: ps_design_system_1.PallasoftClassNameService.merge(isRight(columnIndex)
|
|
16
16
|
? 'ml-auto w-16'
|
|
17
17
|
: ps_skeleton_config_1.PsSkeletonConfig.lineWidths[(rowIndex + columnIndex) % ps_skeleton_config_1.PsSkeletonConfig.lineWidths.length], isStrong ? 'h-3.5' : '') }, columnIndex));
|
|
18
|
-
return ((0, jsx_runtime_1.jsxs)(ps_skeleton_group_1.PsSkeletonGroup, { className: ps_design_system_1.PallasoftClassNameService.merge('flex flex-col gap-3', className), children: [hasToolbar ? ((0, jsx_runtime_1.jsxs)("div", { className: 'flex items-center gap-3', children: [(0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: 'max-w-sm', shape: ps_skeleton_shape_1.PsSkeletonShape.
|
|
19
|
-
? 'overflow-hidden
|
|
18
|
+
return ((0, jsx_runtime_1.jsxs)(ps_skeleton_group_1.PsSkeletonGroup, { className: ps_design_system_1.PallasoftClassNameService.merge('flex flex-col gap-3', className), children: [hasToolbar ? ((0, jsx_runtime_1.jsxs)("div", { className: 'flex items-center gap-3', children: [(0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: 'max-w-sm', shape: ps_skeleton_shape_1.PsSkeletonShape.Control }), (0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: 'ml-auto w-24', shape: ps_skeleton_shape_1.PsSkeletonShape.Button })] })) : null, (0, jsx_runtime_1.jsxs)("div", { className: isFramed
|
|
19
|
+
? ps_design_system_1.PallasoftClassNameService.merge('overflow-hidden border border-line bg-surface', ps_skeleton_config_1.PsSkeletonConfig.tableFrameClassName)
|
|
20
20
|
: 'overflow-hidden', children: [(0, jsx_runtime_1.jsx)("div", { className: 'grid gap-4 bg-mist/70 px-5 py-4', style: { gridTemplateColumns }, children: Array.from({ length: columns }, (_, index) => ((0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: isRight(index) ? 'ml-auto h-2.5 w-12' : 'h-2.5 w-16' }, index))) }), Array.from({ length: rows }, (_, rowIndex) => ((0, jsx_runtime_1.jsx)("div", { className: 'grid items-center gap-4 border-t border-line px-5 py-4', style: { gridTemplateColumns }, children: Array.from({ length: columns }, (__, columnIndex) => cell(rowIndex, columnIndex, false)) }, rowIndex))), hasFooter ? ((0, jsx_runtime_1.jsx)("div", { className: 'grid items-center gap-4 border-t-2 border-line px-5 py-4', style: { gridTemplateColumns }, children: Array.from({ length: columns }, (__, columnIndex) => cell(rows, columnIndex, true)) })) : null] })] }));
|
|
21
21
|
};
|
|
22
22
|
exports.PsSkeletonTable = PsSkeletonTable;
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PsTabRowSkeleton = void 0;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const ps_skeleton_config_1 = require("../constants/ps-skeleton-config");
|
|
5
6
|
const ps_skeleton_shape_1 = require("../constants/ps-skeleton-shape");
|
|
6
7
|
const ps_skeleton_1 = require("./ps-skeleton");
|
|
7
|
-
const
|
|
8
|
+
const ps_skeleton_group_1 = require("./ps-skeleton-group");
|
|
9
|
+
const PsTabRowSkeleton = () => ((0, jsx_runtime_1.jsx)(ps_skeleton_group_1.PsSkeletonGroup, { direction: 'row', gap: 'gap-2', children: Array.from({ length: ps_skeleton_config_1.PsSkeletonConfig.tabCount }, (_, index) => ((0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: `${ps_skeleton_config_1.PsSkeletonConfig.compactControlHeightClassName} ${ps_skeleton_config_1.PsSkeletonConfig.tabWidthClassName}`, shape: ps_skeleton_shape_1.PsSkeletonShape.Control }, index))) }));
|
|
8
10
|
exports.PsTabRowSkeleton = PsTabRowSkeleton;
|
|
@@ -10,6 +10,10 @@ export declare const PsSkeletonConfig: {
|
|
|
10
10
|
readonly importantPrefix: "!";
|
|
11
11
|
readonly widthUtilityPattern: RegExp;
|
|
12
12
|
readonly heightUtilityPattern: RegExp;
|
|
13
|
+
readonly compactControlHeightClassName: "h-ps-control-compact";
|
|
14
|
+
readonly tabWidthClassName: "w-24";
|
|
15
|
+
readonly tabCount: 4;
|
|
16
|
+
readonly tableFrameClassName: "rounded-[0.875rem]";
|
|
13
17
|
readonly shapeClassNames: Record<PsSkeletonShape, string>;
|
|
14
18
|
readonly shapeSizes: Record<PsSkeletonShape, PsSkeletonSize>;
|
|
15
19
|
};
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PsSkeletonConfig = void 0;
|
|
4
4
|
const ps_skeleton_shape_1 = require("./ps-skeleton-shape");
|
|
5
|
+
const controlRadius = 'rounded-ps-control';
|
|
6
|
+
const cardRadius = 'rounded-ps-card';
|
|
7
|
+
const textRadius = 'rounded-full';
|
|
8
|
+
const controlSize = { height: 'h-ps-control', width: 'w-full' };
|
|
5
9
|
exports.PsSkeletonConfig = {
|
|
6
10
|
defaultTextLines: 3,
|
|
7
11
|
lastLineWidth: 'w-2/3',
|
|
@@ -12,16 +16,30 @@ exports.PsSkeletonConfig = {
|
|
|
12
16
|
importantPrefix: '!',
|
|
13
17
|
widthUtilityPattern: /^(w|size)-/,
|
|
14
18
|
heightUtilityPattern: /^(h|size)-/,
|
|
19
|
+
compactControlHeightClassName: 'h-ps-control-compact',
|
|
20
|
+
tabWidthClassName: 'w-24',
|
|
21
|
+
tabCount: 4,
|
|
22
|
+
tableFrameClassName: 'rounded-[0.875rem]',
|
|
15
23
|
shapeClassNames: {
|
|
16
|
-
[ps_skeleton_shape_1.PsSkeletonShape.
|
|
24
|
+
[ps_skeleton_shape_1.PsSkeletonShape.Badge]: controlRadius,
|
|
25
|
+
[ps_skeleton_shape_1.PsSkeletonShape.Block]: controlRadius,
|
|
26
|
+
[ps_skeleton_shape_1.PsSkeletonShape.Button]: controlRadius,
|
|
27
|
+
[ps_skeleton_shape_1.PsSkeletonShape.Card]: cardRadius,
|
|
17
28
|
[ps_skeleton_shape_1.PsSkeletonShape.Circle]: 'shrink-0 rounded-full',
|
|
18
|
-
[ps_skeleton_shape_1.PsSkeletonShape.
|
|
19
|
-
[ps_skeleton_shape_1.PsSkeletonShape.
|
|
29
|
+
[ps_skeleton_shape_1.PsSkeletonShape.Control]: controlRadius,
|
|
30
|
+
[ps_skeleton_shape_1.PsSkeletonShape.Line]: textRadius,
|
|
31
|
+
[ps_skeleton_shape_1.PsSkeletonShape.Pill]: controlRadius,
|
|
32
|
+
[ps_skeleton_shape_1.PsSkeletonShape.Text]: textRadius,
|
|
20
33
|
},
|
|
21
34
|
shapeSizes: {
|
|
22
|
-
[ps_skeleton_shape_1.PsSkeletonShape.
|
|
35
|
+
[ps_skeleton_shape_1.PsSkeletonShape.Badge]: { height: 'h-7', width: 'w-20' },
|
|
36
|
+
[ps_skeleton_shape_1.PsSkeletonShape.Block]: controlSize,
|
|
37
|
+
[ps_skeleton_shape_1.PsSkeletonShape.Button]: { height: 'h-ps-control', width: 'w-32' },
|
|
38
|
+
[ps_skeleton_shape_1.PsSkeletonShape.Card]: { height: 'h-20', width: 'w-full' },
|
|
23
39
|
[ps_skeleton_shape_1.PsSkeletonShape.Circle]: { height: 'h-9', width: 'w-9' },
|
|
40
|
+
[ps_skeleton_shape_1.PsSkeletonShape.Control]: controlSize,
|
|
24
41
|
[ps_skeleton_shape_1.PsSkeletonShape.Line]: { height: 'h-3', width: 'w-full' },
|
|
42
|
+
[ps_skeleton_shape_1.PsSkeletonShape.Pill]: { height: 'h-8', width: 'w-24' },
|
|
25
43
|
[ps_skeleton_shape_1.PsSkeletonShape.Text]: { height: 'h-3', width: 'w-full' },
|
|
26
44
|
},
|
|
27
45
|
};
|
|
@@ -3,8 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.PsSkeletonShape = void 0;
|
|
4
4
|
var PsSkeletonShape;
|
|
5
5
|
(function (PsSkeletonShape) {
|
|
6
|
+
PsSkeletonShape["Badge"] = "badge";
|
|
6
7
|
PsSkeletonShape["Block"] = "block";
|
|
8
|
+
PsSkeletonShape["Button"] = "button";
|
|
9
|
+
PsSkeletonShape["Card"] = "card";
|
|
7
10
|
PsSkeletonShape["Circle"] = "circle";
|
|
11
|
+
PsSkeletonShape["Control"] = "control";
|
|
8
12
|
PsSkeletonShape["Line"] = "line";
|
|
13
|
+
PsSkeletonShape["Pill"] = "pill";
|
|
9
14
|
PsSkeletonShape["Text"] = "text";
|
|
10
15
|
})(PsSkeletonShape || (exports.PsSkeletonShape = PsSkeletonShape = {}));
|
|
@@ -6,4 +6,4 @@ const variantClassNames = {
|
|
|
6
6
|
default: 'ps-action-tile',
|
|
7
7
|
primary: 'ps-action-tile ps-action-tile-primary',
|
|
8
8
|
};
|
|
9
|
-
export const PsActionTile = ({ className, description, icon, isLoading = false, label, type = 'button', variant = 'default', ...props }) => isLoading ? (_jsx(PsSkeleton, { className: PallasoftClassNameService.merge('h-16
|
|
9
|
+
export const PsActionTile = ({ className, description, icon, isLoading = false, label, type = 'button', variant = 'default', ...props }) => isLoading ? (_jsx(PsSkeleton, { className: PallasoftClassNameService.merge('h-16', className), shape: PsSkeletonShape.Card })) : (_jsxs("button", { className: PallasoftClassNameService.merge('ps-focus-ring w-full', variantClassNames[variant], className), type: type, ...props, children: [_jsxs("span", { className: 'min-w-0', children: [_jsx("span", { className: 'block break-words', children: label }), description ? (_jsx("span", { className: PallasoftClassNameService.merge('mt-0.5 block text-xs font-medium leading-5', variant === 'primary' ? 'text-white/75' : 'text-ink/50'), children: description })) : null] }), icon ? (_jsx("span", { className: PallasoftClassNameService.merge('flex h-9 w-9 shrink-0 items-center justify-center rounded-lg', variant === 'primary' ? 'bg-white/15 text-white' : 'ps-icon-surface'), children: icon })) : null] }));
|
|
@@ -9,4 +9,4 @@ const toneClassNames = {
|
|
|
9
9
|
success: 'bg-success-soft text-emerald-700 dark:bg-emerald-400/18 dark:text-emerald-50',
|
|
10
10
|
warning: 'bg-warning-soft text-amber-800 dark:bg-amber-400/18 dark:text-amber-50',
|
|
11
11
|
};
|
|
12
|
-
export const PsBadge = ({ children, className, isLoading = false, tone = 'neutral', }) => isLoading ? (_jsx(PsSkeleton, { className:
|
|
12
|
+
export const PsBadge = ({ children, className, isLoading = false, tone = 'neutral', }) => isLoading ? (_jsx(PsSkeleton, { className: className, shape: PsSkeletonShape.Badge })) : (_jsx("span", { className: PallasoftClassNameService.merge('inline-flex min-h-7 w-fit items-center justify-center whitespace-nowrap rounded-ps-control px-3 py-0.5 text-center text-xs font-semibold capitalize', toneClassNames[tone], className), children: children }));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { PsSkeletonShape } from '../constants/ps-skeleton-shape.js';
|
|
3
3
|
import { PsSkeleton } from './ps-skeleton.js';
|
|
4
|
-
export const PsCardBlockSkeleton = ({ height }) => (_jsx(PsSkeleton, { height: height,
|
|
4
|
+
export const PsCardBlockSkeleton = ({ height }) => (_jsx(PsSkeleton, { height: height, shape: PsSkeletonShape.Card }));
|
|
@@ -14,4 +14,4 @@ const variantClassNames = {
|
|
|
14
14
|
glass: 'ps-glass',
|
|
15
15
|
muted: 'ps-card-muted',
|
|
16
16
|
};
|
|
17
|
-
export const PsCard = ({ children, className, density = 'normal', eyebrow, isLoading = false, title, trailing, variant = 'default', }) => isLoading ? (_jsxs(PsSkeletonGroup, { className: PallasoftClassNameService.merge('flex min-w-0 flex-col gap-3 rounded-ps-card', variantClassNames[variant], densityClassNames[density], className), label: title === undefined ? 'Loading' : title, children: [_jsx(PsSkeleton, { className: 'h-3 w-32' }), _jsx(PsSkeleton, { className: 'h-20
|
|
17
|
+
export const PsCard = ({ children, className, density = 'normal', eyebrow, isLoading = false, title, trailing, variant = 'default', }) => isLoading ? (_jsxs(PsSkeletonGroup, { className: PallasoftClassNameService.merge('flex min-w-0 flex-col gap-3 rounded-ps-card', variantClassNames[variant], densityClassNames[density], className), label: title === undefined ? 'Loading' : title, children: [_jsx(PsSkeleton, { className: 'h-3 w-32' }), _jsx(PsSkeleton, { shape: PsSkeletonShape.Control, className: 'h-20' })] })) : (_jsxs("section", { className: PallasoftClassNameService.merge('min-w-0 overflow-hidden rounded-ps-card', variantClassNames[variant], densityClassNames[density], className), children: [title ? (_jsxs("div", { className: 'mb-3 flex min-w-0 items-start justify-between gap-3', children: [_jsxs("div", { className: 'min-w-0', children: [eyebrow ? _jsx("p", { className: 'ps-eyebrow break-words', children: eyebrow }) : null, _jsx("h2", { className: 'mt-1 break-words text-base font-bold text-ink', children: title })] }), trailing ? _jsx("div", { className: 'shrink-0', children: trailing }) : null] })) : null, children] }));
|
|
@@ -3,4 +3,4 @@ import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
|
3
3
|
import { PsPageSkeletonConfig } from '../constants/ps-page-skeleton-config.js';
|
|
4
4
|
import { PsSkeletonShape } from '../constants/ps-skeleton-shape.js';
|
|
5
5
|
import { PsSkeleton } from './ps-skeleton.js';
|
|
6
|
-
export const PsFormFieldsSkeleton = ({ columnsClassName = PsPageSkeletonConfig.formColumnsClassName, fields = PsPageSkeletonConfig.formFields, }) => (_jsx("div", { className: PallasoftClassNameService.merge('grid gap-4', columnsClassName), children: fields.map((field) => (_jsxs("div", { className: 'flex flex-col gap-2', children: [_jsx(PsSkeleton, { className: PsPageSkeletonConfig.formLabelClassName }), _jsx(PsSkeleton, {
|
|
6
|
+
export const PsFormFieldsSkeleton = ({ columnsClassName = PsPageSkeletonConfig.formColumnsClassName, fields = PsPageSkeletonConfig.formFields, }) => (_jsx("div", { className: PallasoftClassNameService.merge('grid gap-4', columnsClassName), children: fields.map((field) => (_jsxs("div", { className: 'flex flex-col gap-2', children: [_jsx(PsSkeleton, { className: PsPageSkeletonConfig.formLabelClassName }), _jsx(PsSkeleton, { shape: PsSkeletonShape.Control })] }, field))) }));
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { PsSkeleton } from './ps-skeleton.js';
|
|
3
|
-
export const PsIntroSkeleton = () => (_jsxs("div", { className: 'mx-auto flex w-full max-w-3xl flex-col items-center gap-4', children: [_jsx(PsSkeleton, {
|
|
3
|
+
export const PsIntroSkeleton = () => (_jsxs("div", { className: 'mx-auto flex w-full max-w-3xl flex-col items-center gap-4', children: [_jsx(PsSkeleton, { className: 'h-3.5 w-24' }), _jsx(PsSkeleton, { className: 'h-10 w-[70%]' }), _jsx(PsSkeleton, { className: 'h-[1.125rem] w-[85%]' })] }));
|
|
@@ -5,4 +5,4 @@ import { PsPageLayout } from './ps-page-layout.js';
|
|
|
5
5
|
import { PsPageStack } from './ps-page-stack.js';
|
|
6
6
|
import { PsSectionHeader } from './ps-section-header.js';
|
|
7
7
|
import { PsSkeleton } from './ps-skeleton.js';
|
|
8
|
-
export const PsPageSkeleton = ({ children, hasActions = false, isNarrow = false, loadingLabel, title, }) => (_jsx(PsPageLayout, { isNarrow: isNarrow, children: _jsxs(PsPageStack, { children: [title === undefined ? (_jsx(PsSectionHeader, { isLoading: true, title: loadingLabel, trailing: hasActions })) : (_jsx(PsPageHeader, { actions: hasActions ?
|
|
8
|
+
export const PsPageSkeleton = ({ children, hasActions = false, isNarrow = false, loadingLabel, title, }) => (_jsx(PsPageLayout, { isNarrow: isNarrow, children: _jsxs(PsPageStack, { children: [title === undefined ? (_jsx(PsSectionHeader, { isLoading: true, title: loadingLabel, trailing: hasActions })) : (_jsx(PsPageHeader, { actions: hasActions ? _jsx(PsSkeleton, { shape: PsSkeletonShape.Button }) : undefined, title: title })), children] }) }));
|
|
@@ -9,4 +9,4 @@ const toneClassNames = {
|
|
|
9
9
|
success: 'bg-emerald-50 text-emerald-700 ring-emerald-100 dark:bg-emerald-400/18 dark:text-emerald-50 dark:ring-emerald-200/22',
|
|
10
10
|
warning: 'bg-amber-50 text-amber-800 ring-amber-100 dark:bg-amber-400/18 dark:text-amber-50 dark:ring-amber-200/24',
|
|
11
11
|
};
|
|
12
|
-
export const PsPill = ({ children, className, isLoading = false, tone = 'neutral', }) => isLoading ? (_jsx(PsSkeleton, { className:
|
|
12
|
+
export const PsPill = ({ children, className, isLoading = false, tone = 'neutral', }) => isLoading ? (_jsx(PsSkeleton, { className: className, shape: PsSkeletonShape.Pill })) : (_jsx("span", { className: PallasoftClassNameService.merge('inline-flex min-h-8 min-w-[5.5rem] max-w-full items-center justify-center rounded-ps-control px-5 py-1 text-center text-[0.72rem] font-black capitalize tracking-[0.01em] ring-1 ring-inset', toneClassNames[tone], className), children: _jsx("span", { className: 'min-w-0 break-words', children: children }) }));
|
|
@@ -9,5 +9,5 @@ export const PsReflectiveAppShell = ({ children, className, contentClassName, fr
|
|
|
9
9
|
contentRef.current?.scrollTo({ top: 0, left: 0 });
|
|
10
10
|
window.scrollTo({ top: 0, left: 0 });
|
|
11
11
|
}, [scrollResetKey]);
|
|
12
|
-
return (_jsx("section", { className: PallasoftClassNameService.merge('ps-reflective-frame h-[100dvh] overflow-hidden text-ink', className), children: _jsxs("div", { className: PallasoftClassNameService.merge('ps-liquid-strong grid h-full min-h-0 w-full overflow-hidden border-line/70 bg-white/70 backdrop-blur-2xl transition-[grid-template-columns] duration-200 dark:border-white/10 dark:bg-surface/70 md:grid-cols-[var(--ps-sidebar-width)_minmax(0,1fr)]', frameClassName), style: { ['--ps-sidebar-width']: sidebarWidth }, children: [sidebar, _jsxs("div", { className: 'flex min-h-0 min-w-0 flex-col bg-white/32 dark:bg-white/[0.02]', children: [topbar, _jsx("main", { ref: contentRef, className: PallasoftClassNameService.merge('ps-scrollbar min-h-0 flex-1 overflow-y-auto px-4
|
|
12
|
+
return (_jsx("section", { className: PallasoftClassNameService.merge('ps-reflective-frame h-[100dvh] overflow-hidden text-ink', className), children: _jsxs("div", { className: PallasoftClassNameService.merge('ps-liquid-strong grid h-full min-h-0 w-full overflow-hidden border-line/70 bg-white/70 backdrop-blur-2xl transition-[grid-template-columns] duration-200 dark:border-white/10 dark:bg-surface/70 md:grid-cols-[var(--ps-sidebar-width)_minmax(0,1fr)]', frameClassName), style: { ['--ps-sidebar-width']: sidebarWidth }, children: [sidebar, _jsxs("div", { className: 'flex min-h-0 min-w-0 flex-col bg-white/32 dark:bg-white/[0.02]', children: [topbar, _jsx("main", { ref: contentRef, className: PallasoftClassNameService.merge('ps-scrollbar min-h-0 flex-1 overflow-y-auto px-4 ps-page-enter sm:px-5 lg:px-6 xl:px-7', contentClassName), children: _jsx("div", { className: 'py-5', children: children }) })] })] }) }));
|
|
13
13
|
};
|
|
@@ -2,4 +2,4 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { PsSkeletonShape } from '../constants/ps-skeleton-shape.js';
|
|
3
3
|
import { PsSkeleton } from './ps-skeleton.js';
|
|
4
4
|
import { PsSkeletonGroup } from './ps-skeleton-group.js';
|
|
5
|
-
export const PsSectionHeader = ({ description, eyebrow, isLoading = false, leading, title, trailing, }) => isLoading ? (_jsxs(PsSkeletonGroup, { className: 'flex flex-wrap items-end justify-between gap-x-4 gap-y-3', label: title, children: [_jsxs("div", { className: 'flex min-w-0 flex-1 basis-auto flex-col gap-2', children: [eyebrow ? _jsx(PsSkeleton, { className: 'h-2.5 w-24' }) : null, _jsx(PsSkeleton, { className: 'h-6 w-52' }), description ? _jsx(PsSkeleton, { className: 'h-3 w-72' }) : null] }), trailing ? _jsx(PsSkeleton, {
|
|
5
|
+
export const PsSectionHeader = ({ description, eyebrow, isLoading = false, leading, title, trailing, }) => isLoading ? (_jsxs(PsSkeletonGroup, { className: 'flex flex-wrap items-end justify-between gap-x-4 gap-y-3', label: title, children: [_jsxs("div", { className: 'flex min-w-0 flex-1 basis-auto flex-col gap-2', children: [eyebrow ? _jsx(PsSkeleton, { className: 'h-2.5 w-24' }) : null, _jsx(PsSkeleton, { className: 'h-6 w-52' }), description ? _jsx(PsSkeleton, { className: 'h-3 w-72' }) : null] }), trailing ? _jsx(PsSkeleton, { shape: PsSkeletonShape.Button }) : null] })) : (_jsxs("div", { className: 'flex flex-wrap items-end justify-between gap-x-4 gap-y-3', children: [_jsxs("div", { className: 'flex min-w-0 flex-1 basis-auto flex-col gap-2', children: [leading, eyebrow ? (_jsx("p", { className: 'text-[0.68rem] font-semibold uppercase tracking-[0.22em] text-ink/50', children: eyebrow })) : null, _jsx("h1", { className: 'break-words text-xl font-extrabold text-ink sm:text-2xl', children: title }), description ? (_jsx("p", { className: 'max-w-3xl text-sm leading-6 text-ink/60', children: description })) : null] }), trailing ? (_jsx("div", { className: 'flex w-full shrink-0 items-center justify-end sm:ml-auto sm:w-auto', children: trailing })) : null] }));
|
|
@@ -3,4 +3,4 @@ import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
|
3
3
|
import { PsSkeletonShape } from '../constants/ps-skeleton-shape.js';
|
|
4
4
|
import { PsSkeleton } from './ps-skeleton.js';
|
|
5
5
|
import { PsSkeletonGroup } from './ps-skeleton-group.js';
|
|
6
|
-
export const PsSkeletonMedia = ({ aspect = '16 / 9', className, hasCaption = true, label, }) => (_jsxs(PsSkeletonGroup, { className: PallasoftClassNameService.merge('w-full', className), label: label, children: [_jsx(PsSkeleton, { aspect: aspect,
|
|
6
|
+
export const PsSkeletonMedia = ({ aspect = '16 / 9', className, hasCaption = true, label, }) => (_jsxs(PsSkeletonGroup, { className: PallasoftClassNameService.merge('w-full', className), label: label, children: [_jsx(PsSkeleton, { aspect: aspect, shape: PsSkeletonShape.Card }), hasCaption ? _jsx(PsSkeleton, { lines: 2, shape: PsSkeletonShape.Text }) : null] }));
|
|
@@ -12,7 +12,7 @@ export const PsSkeletonTable = ({ alignments, className, columns = 4, hasFooter
|
|
|
12
12
|
const cell = (rowIndex, columnIndex, isStrong) => (_jsx(PsSkeleton, { className: PallasoftClassNameService.merge(isRight(columnIndex)
|
|
13
13
|
? 'ml-auto w-16'
|
|
14
14
|
: PsSkeletonConfig.lineWidths[(rowIndex + columnIndex) % PsSkeletonConfig.lineWidths.length], isStrong ? 'h-3.5' : '') }, columnIndex));
|
|
15
|
-
return (_jsxs(PsSkeletonGroup, { className: PallasoftClassNameService.merge('flex flex-col gap-3', className), children: [hasToolbar ? (_jsxs("div", { className: 'flex items-center gap-3', children: [_jsx(PsSkeleton, { className: 'max-w-sm', shape: PsSkeletonShape.
|
|
16
|
-
? 'overflow-hidden
|
|
15
|
+
return (_jsxs(PsSkeletonGroup, { className: PallasoftClassNameService.merge('flex flex-col gap-3', className), children: [hasToolbar ? (_jsxs("div", { className: 'flex items-center gap-3', children: [_jsx(PsSkeleton, { className: 'max-w-sm', shape: PsSkeletonShape.Control }), _jsx(PsSkeleton, { className: 'ml-auto w-24', shape: PsSkeletonShape.Button })] })) : null, _jsxs("div", { className: isFramed
|
|
16
|
+
? PallasoftClassNameService.merge('overflow-hidden border border-line bg-surface', PsSkeletonConfig.tableFrameClassName)
|
|
17
17
|
: 'overflow-hidden', children: [_jsx("div", { className: 'grid gap-4 bg-mist/70 px-5 py-4', style: { gridTemplateColumns }, children: Array.from({ length: columns }, (_, index) => (_jsx(PsSkeleton, { className: isRight(index) ? 'ml-auto h-2.5 w-12' : 'h-2.5 w-16' }, index))) }), Array.from({ length: rows }, (_, rowIndex) => (_jsx("div", { className: 'grid items-center gap-4 border-t border-line px-5 py-4', style: { gridTemplateColumns }, children: Array.from({ length: columns }, (__, columnIndex) => cell(rowIndex, columnIndex, false)) }, rowIndex))), hasFooter ? (_jsx("div", { className: 'grid items-center gap-4 border-t-2 border-line px-5 py-4', style: { gridTemplateColumns }, children: Array.from({ length: columns }, (__, columnIndex) => cell(rows, columnIndex, true)) })) : null] })] }));
|
|
18
18
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { PsSkeletonConfig } from '../constants/ps-skeleton-config.js';
|
|
2
3
|
import { PsSkeletonShape } from '../constants/ps-skeleton-shape.js';
|
|
3
4
|
import { PsSkeleton } from './ps-skeleton.js';
|
|
4
|
-
|
|
5
|
+
import { PsSkeletonGroup } from './ps-skeleton-group.js';
|
|
6
|
+
export const PsTabRowSkeleton = () => (_jsx(PsSkeletonGroup, { direction: 'row', gap: 'gap-2', children: Array.from({ length: PsSkeletonConfig.tabCount }, (_, index) => (_jsx(PsSkeleton, { className: `${PsSkeletonConfig.compactControlHeightClassName} ${PsSkeletonConfig.tabWidthClassName}`, shape: PsSkeletonShape.Control }, index))) }));
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { PsSkeletonShape } from './ps-skeleton-shape.js';
|
|
2
|
+
const controlRadius = 'rounded-ps-control';
|
|
3
|
+
const cardRadius = 'rounded-ps-card';
|
|
4
|
+
const textRadius = 'rounded-full';
|
|
5
|
+
const controlSize = { height: 'h-ps-control', width: 'w-full' };
|
|
2
6
|
export const PsSkeletonConfig = {
|
|
3
7
|
defaultTextLines: 3,
|
|
4
8
|
lastLineWidth: 'w-2/3',
|
|
@@ -9,16 +13,30 @@ export const PsSkeletonConfig = {
|
|
|
9
13
|
importantPrefix: '!',
|
|
10
14
|
widthUtilityPattern: /^(w|size)-/,
|
|
11
15
|
heightUtilityPattern: /^(h|size)-/,
|
|
16
|
+
compactControlHeightClassName: 'h-ps-control-compact',
|
|
17
|
+
tabWidthClassName: 'w-24',
|
|
18
|
+
tabCount: 4,
|
|
19
|
+
tableFrameClassName: 'rounded-[0.875rem]',
|
|
12
20
|
shapeClassNames: {
|
|
13
|
-
[PsSkeletonShape.
|
|
21
|
+
[PsSkeletonShape.Badge]: controlRadius,
|
|
22
|
+
[PsSkeletonShape.Block]: controlRadius,
|
|
23
|
+
[PsSkeletonShape.Button]: controlRadius,
|
|
24
|
+
[PsSkeletonShape.Card]: cardRadius,
|
|
14
25
|
[PsSkeletonShape.Circle]: 'shrink-0 rounded-full',
|
|
15
|
-
[PsSkeletonShape.
|
|
16
|
-
[PsSkeletonShape.
|
|
26
|
+
[PsSkeletonShape.Control]: controlRadius,
|
|
27
|
+
[PsSkeletonShape.Line]: textRadius,
|
|
28
|
+
[PsSkeletonShape.Pill]: controlRadius,
|
|
29
|
+
[PsSkeletonShape.Text]: textRadius,
|
|
17
30
|
},
|
|
18
31
|
shapeSizes: {
|
|
19
|
-
[PsSkeletonShape.
|
|
32
|
+
[PsSkeletonShape.Badge]: { height: 'h-7', width: 'w-20' },
|
|
33
|
+
[PsSkeletonShape.Block]: controlSize,
|
|
34
|
+
[PsSkeletonShape.Button]: { height: 'h-ps-control', width: 'w-32' },
|
|
35
|
+
[PsSkeletonShape.Card]: { height: 'h-20', width: 'w-full' },
|
|
20
36
|
[PsSkeletonShape.Circle]: { height: 'h-9', width: 'w-9' },
|
|
37
|
+
[PsSkeletonShape.Control]: controlSize,
|
|
21
38
|
[PsSkeletonShape.Line]: { height: 'h-3', width: 'w-full' },
|
|
39
|
+
[PsSkeletonShape.Pill]: { height: 'h-8', width: 'w-24' },
|
|
22
40
|
[PsSkeletonShape.Text]: { height: 'h-3', width: 'w-full' },
|
|
23
41
|
},
|
|
24
42
|
};
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
export var PsSkeletonShape;
|
|
2
2
|
(function (PsSkeletonShape) {
|
|
3
|
+
PsSkeletonShape["Badge"] = "badge";
|
|
3
4
|
PsSkeletonShape["Block"] = "block";
|
|
5
|
+
PsSkeletonShape["Button"] = "button";
|
|
6
|
+
PsSkeletonShape["Card"] = "card";
|
|
4
7
|
PsSkeletonShape["Circle"] = "circle";
|
|
8
|
+
PsSkeletonShape["Control"] = "control";
|
|
5
9
|
PsSkeletonShape["Line"] = "line";
|
|
10
|
+
PsSkeletonShape["Pill"] = "pill";
|
|
6
11
|
PsSkeletonShape["Text"] = "text";
|
|
7
12
|
})(PsSkeletonShape || (PsSkeletonShape = {}));
|
|
@@ -14,10 +14,15 @@ export const usePsChangeQueue = ({ onDrained, onFailed, send, }) => {
|
|
|
14
14
|
while (pending.current.length > 0) {
|
|
15
15
|
const batch = pending.current;
|
|
16
16
|
pending.current = [];
|
|
17
|
-
await latest.current.send(batch).catch(() => {
|
|
17
|
+
await new Promise((resolve) => resolve(latest.current.send(batch))).catch(() => {
|
|
18
18
|
const dropped = pending.current;
|
|
19
19
|
pending.current = [];
|
|
20
|
-
|
|
20
|
+
try {
|
|
21
|
+
latest.current.onFailed(batch, dropped);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
21
26
|
});
|
|
22
27
|
}
|
|
23
28
|
isRunning.current = false;
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { PsOptimisticConfig } from '../constants/ps-optimistic-config.js';
|
|
3
|
+
import { PsOptimisticFetcherService } from '../services/ps-optimistic-fetcher-service.js';
|
|
2
4
|
import { usePsChangeQueue } from './use-ps-change-queue.js';
|
|
3
5
|
import { usePsOptimisticRows } from './use-ps-optimistic-rows.js';
|
|
4
|
-
export const usePsOptimisticFetcher = ({ apply, fetcher, isHolding = false, isSuccess, merge, revalidator, rows, }) => {
|
|
6
|
+
export const usePsOptimisticFetcher = ({ apply, fetcher, isHolding = false, isSuccess, merge, onError, revalidator, rows, }) => {
|
|
5
7
|
const [isUnsettled, setIsUnsettled] = useState(false);
|
|
6
8
|
const submitted = useRef(0);
|
|
7
9
|
const settle = useRef(null);
|
|
8
10
|
const sawBusy = useRef(false);
|
|
9
11
|
const dataBefore = useRef(fetcher.data);
|
|
10
12
|
const dataSeen = useRef(fetcher.data);
|
|
13
|
+
const failure = useRef(null);
|
|
11
14
|
const { forgetLast, remember, rows: shown, } = usePsOptimisticRows({
|
|
12
15
|
apply,
|
|
13
16
|
isHolding,
|
|
@@ -27,32 +30,67 @@ export const usePsOptimisticFetcher = ({ apply, fetcher, isHolding = false, isSu
|
|
|
27
30
|
const done = settle.current;
|
|
28
31
|
settle.current = null;
|
|
29
32
|
sawBusy.current = false;
|
|
30
|
-
done(fetcher.data);
|
|
33
|
+
done(PsOptimisticFetcherService.outcome(fetcher.data, dataBefore.current));
|
|
31
34
|
}, [fetcher.data, fetcher.state]);
|
|
35
|
+
useEffect(() => () => {
|
|
36
|
+
if (settle.current === null)
|
|
37
|
+
return;
|
|
38
|
+
const done = settle.current;
|
|
39
|
+
settle.current = null;
|
|
40
|
+
done({ data: undefined, reason: 'unmounted' });
|
|
41
|
+
}, []);
|
|
32
42
|
const submitOne = useCallback((values) => new Promise((resolve) => {
|
|
33
|
-
|
|
43
|
+
const finish = (outcome) => {
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
if (settle.current === finish)
|
|
46
|
+
settle.current = null;
|
|
47
|
+
resolve(outcome);
|
|
48
|
+
};
|
|
49
|
+
const timer = setTimeout(() => finish({ data: undefined, reason: 'timeout' }), PsOptimisticConfig.submitTimeoutMs);
|
|
50
|
+
settle.current = finish;
|
|
34
51
|
sawBusy.current = false;
|
|
35
52
|
dataBefore.current = dataSeen.current;
|
|
36
|
-
|
|
53
|
+
try {
|
|
54
|
+
void Promise.resolve(fetcher.submit(values, { defaultShouldRevalidate: false, method: 'post' })).catch(() => finish({ data: undefined, reason: 'no-response' }));
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
finish({ data: undefined, reason: 'no-response' });
|
|
58
|
+
}
|
|
37
59
|
}), [fetcher]);
|
|
38
60
|
const send = useCallback(async (queued) => {
|
|
61
|
+
failure.current = null;
|
|
39
62
|
for (const submission of merge === undefined ? queued : merge(queued)) {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
63
|
+
const outcome = await submitOne(submission);
|
|
64
|
+
const reason = PsOptimisticFetcherService.failureReason(outcome, isSuccess);
|
|
65
|
+
if (reason !== null) {
|
|
66
|
+
failure.current = { data: outcome.data, reason };
|
|
67
|
+
throw new Error(reason);
|
|
68
|
+
}
|
|
43
69
|
}
|
|
44
70
|
}, [isSuccess, merge, submitOne]);
|
|
45
71
|
const revalidate = useCallback(() => {
|
|
46
72
|
const through = submitted.current;
|
|
47
|
-
|
|
73
|
+
const release = () => {
|
|
48
74
|
if (submitted.current === through)
|
|
49
75
|
setIsUnsettled(false);
|
|
50
|
-
}
|
|
76
|
+
};
|
|
77
|
+
void new Promise((resolve) => resolve(revalidator.revalidate()))
|
|
78
|
+
.catch(() => undefined)
|
|
79
|
+
.then(release);
|
|
51
80
|
}, [revalidator]);
|
|
52
81
|
const forgetFailed = useCallback((failed, dropped) => {
|
|
53
82
|
for (let index = 0; index < failed.length + dropped.length; index += 1)
|
|
54
83
|
forgetLast();
|
|
55
|
-
|
|
84
|
+
const cause = failure.current;
|
|
85
|
+
failure.current = null;
|
|
86
|
+
if (onError === undefined)
|
|
87
|
+
return;
|
|
88
|
+
if (cause === null) {
|
|
89
|
+
onError({ changes: [...failed, ...dropped], data: undefined, reason: 'error' });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
onError({ ...cause, changes: [...failed, ...dropped] });
|
|
93
|
+
}, [forgetLast, onError]);
|
|
56
94
|
const queue = usePsChangeQueue({
|
|
57
95
|
onDrained: revalidate,
|
|
58
96
|
onFailed: forgetFailed,
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
import { PsOptimisticSaveService } from '../services/ps-optimistic-save-service.js';
|
|
3
3
|
import { usePsOptimisticFetcher } from './use-ps-optimistic-fetcher.js';
|
|
4
|
-
export const usePsOptimisticSave = ({ fetcher, isSuccess, revalidator, rows, scopeId, spec, }) => {
|
|
4
|
+
export const usePsOptimisticSave = ({ fetcher, isSuccess, onError, revalidator, rows, scopeId, spec, }) => {
|
|
5
5
|
const apply = useCallback((base, changes) => PsOptimisticSaveService.apply(base, changes, spec), [spec]);
|
|
6
6
|
const optimistic = usePsOptimisticFetcher({
|
|
7
7
|
apply,
|
|
8
8
|
fetcher,
|
|
9
9
|
isSuccess,
|
|
10
|
+
onError,
|
|
10
11
|
revalidator,
|
|
11
12
|
rows,
|
|
12
13
|
});
|
package/dist/esm/index.js
CHANGED
|
@@ -95,6 +95,8 @@ export * from './interfaces/ps-live-input.js';
|
|
|
95
95
|
export * from './interfaces/ps-live-open-options.js';
|
|
96
96
|
export * from './interfaces/ps-optimistic-fetcher-input.js';
|
|
97
97
|
export * from './interfaces/ps-optimistic-fetcher-result.js';
|
|
98
|
+
export * from './interfaces/ps-optimistic-failure.js';
|
|
99
|
+
export * from './interfaces/ps-optimistic-failure-reason.js';
|
|
98
100
|
export * from './interfaces/ps-revalidator-like.js';
|
|
99
101
|
export * from './interfaces/ps-optimistic-rows-input.js';
|
|
100
102
|
export * from './interfaces/ps-optimistic-rows-result.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export class PsOptimisticFetcherService {
|
|
2
|
+
static outcome(data, before) {
|
|
3
|
+
if (data === before)
|
|
4
|
+
return { data: undefined, reason: 'no-response' };
|
|
5
|
+
return { data, reason: 'settled' };
|
|
6
|
+
}
|
|
7
|
+
static failureReason(outcome, isSuccess) {
|
|
8
|
+
if (outcome.reason !== 'settled')
|
|
9
|
+
return outcome.reason;
|
|
10
|
+
return isSuccess(outcome.data) ? null : 'rejected';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -17,10 +17,15 @@ const usePsChangeQueue = ({ onDrained, onFailed, send, }) => {
|
|
|
17
17
|
while (pending.current.length > 0) {
|
|
18
18
|
const batch = pending.current;
|
|
19
19
|
pending.current = [];
|
|
20
|
-
await latest.current.send(batch).catch(() => {
|
|
20
|
+
await new Promise((resolve) => resolve(latest.current.send(batch))).catch(() => {
|
|
21
21
|
const dropped = pending.current;
|
|
22
22
|
pending.current = [];
|
|
23
|
-
|
|
23
|
+
try {
|
|
24
|
+
latest.current.onFailed(batch, dropped);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
24
29
|
});
|
|
25
30
|
}
|
|
26
31
|
isRunning.current = false;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { PsOptimisticFetcherInput } from '../interfaces/ps-optimistic-fetcher-input';
|
|
2
2
|
import type { PsOptimisticFetcherResult } from '../interfaces/ps-optimistic-fetcher-result';
|
|
3
|
-
export declare const usePsOptimisticFetcher: <Row, Data>({ apply, fetcher, isHolding, isSuccess, merge, revalidator, rows, }: PsOptimisticFetcherInput<Row, Data>) => PsOptimisticFetcherResult<Row>;
|
|
3
|
+
export declare const usePsOptimisticFetcher: <Row, Data>({ apply, fetcher, isHolding, isSuccess, merge, onError, revalidator, rows, }: PsOptimisticFetcherInput<Row, Data>) => PsOptimisticFetcherResult<Row>;
|
|
@@ -2,15 +2,18 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.usePsOptimisticFetcher = void 0;
|
|
4
4
|
const react_1 = require("react");
|
|
5
|
+
const ps_optimistic_config_1 = require("../constants/ps-optimistic-config");
|
|
6
|
+
const ps_optimistic_fetcher_service_1 = require("../services/ps-optimistic-fetcher-service");
|
|
5
7
|
const use_ps_change_queue_1 = require("./use-ps-change-queue");
|
|
6
8
|
const use_ps_optimistic_rows_1 = require("./use-ps-optimistic-rows");
|
|
7
|
-
const usePsOptimisticFetcher = ({ apply, fetcher, isHolding = false, isSuccess, merge, revalidator, rows, }) => {
|
|
9
|
+
const usePsOptimisticFetcher = ({ apply, fetcher, isHolding = false, isSuccess, merge, onError, revalidator, rows, }) => {
|
|
8
10
|
const [isUnsettled, setIsUnsettled] = (0, react_1.useState)(false);
|
|
9
11
|
const submitted = (0, react_1.useRef)(0);
|
|
10
12
|
const settle = (0, react_1.useRef)(null);
|
|
11
13
|
const sawBusy = (0, react_1.useRef)(false);
|
|
12
14
|
const dataBefore = (0, react_1.useRef)(fetcher.data);
|
|
13
15
|
const dataSeen = (0, react_1.useRef)(fetcher.data);
|
|
16
|
+
const failure = (0, react_1.useRef)(null);
|
|
14
17
|
const { forgetLast, remember, rows: shown, } = (0, use_ps_optimistic_rows_1.usePsOptimisticRows)({
|
|
15
18
|
apply,
|
|
16
19
|
isHolding,
|
|
@@ -30,32 +33,67 @@ const usePsOptimisticFetcher = ({ apply, fetcher, isHolding = false, isSuccess,
|
|
|
30
33
|
const done = settle.current;
|
|
31
34
|
settle.current = null;
|
|
32
35
|
sawBusy.current = false;
|
|
33
|
-
done(fetcher.data);
|
|
36
|
+
done(ps_optimistic_fetcher_service_1.PsOptimisticFetcherService.outcome(fetcher.data, dataBefore.current));
|
|
34
37
|
}, [fetcher.data, fetcher.state]);
|
|
38
|
+
(0, react_1.useEffect)(() => () => {
|
|
39
|
+
if (settle.current === null)
|
|
40
|
+
return;
|
|
41
|
+
const done = settle.current;
|
|
42
|
+
settle.current = null;
|
|
43
|
+
done({ data: undefined, reason: 'unmounted' });
|
|
44
|
+
}, []);
|
|
35
45
|
const submitOne = (0, react_1.useCallback)((values) => new Promise((resolve) => {
|
|
36
|
-
|
|
46
|
+
const finish = (outcome) => {
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
if (settle.current === finish)
|
|
49
|
+
settle.current = null;
|
|
50
|
+
resolve(outcome);
|
|
51
|
+
};
|
|
52
|
+
const timer = setTimeout(() => finish({ data: undefined, reason: 'timeout' }), ps_optimistic_config_1.PsOptimisticConfig.submitTimeoutMs);
|
|
53
|
+
settle.current = finish;
|
|
37
54
|
sawBusy.current = false;
|
|
38
55
|
dataBefore.current = dataSeen.current;
|
|
39
|
-
|
|
56
|
+
try {
|
|
57
|
+
void Promise.resolve(fetcher.submit(values, { defaultShouldRevalidate: false, method: 'post' })).catch(() => finish({ data: undefined, reason: 'no-response' }));
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
finish({ data: undefined, reason: 'no-response' });
|
|
61
|
+
}
|
|
40
62
|
}), [fetcher]);
|
|
41
63
|
const send = (0, react_1.useCallback)(async (queued) => {
|
|
64
|
+
failure.current = null;
|
|
42
65
|
for (const submission of merge === undefined ? queued : merge(queued)) {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
66
|
+
const outcome = await submitOne(submission);
|
|
67
|
+
const reason = ps_optimistic_fetcher_service_1.PsOptimisticFetcherService.failureReason(outcome, isSuccess);
|
|
68
|
+
if (reason !== null) {
|
|
69
|
+
failure.current = { data: outcome.data, reason };
|
|
70
|
+
throw new Error(reason);
|
|
71
|
+
}
|
|
46
72
|
}
|
|
47
73
|
}, [isSuccess, merge, submitOne]);
|
|
48
74
|
const revalidate = (0, react_1.useCallback)(() => {
|
|
49
75
|
const through = submitted.current;
|
|
50
|
-
|
|
76
|
+
const release = () => {
|
|
51
77
|
if (submitted.current === through)
|
|
52
78
|
setIsUnsettled(false);
|
|
53
|
-
}
|
|
79
|
+
};
|
|
80
|
+
void new Promise((resolve) => resolve(revalidator.revalidate()))
|
|
81
|
+
.catch(() => undefined)
|
|
82
|
+
.then(release);
|
|
54
83
|
}, [revalidator]);
|
|
55
84
|
const forgetFailed = (0, react_1.useCallback)((failed, dropped) => {
|
|
56
85
|
for (let index = 0; index < failed.length + dropped.length; index += 1)
|
|
57
86
|
forgetLast();
|
|
58
|
-
|
|
87
|
+
const cause = failure.current;
|
|
88
|
+
failure.current = null;
|
|
89
|
+
if (onError === undefined)
|
|
90
|
+
return;
|
|
91
|
+
if (cause === null) {
|
|
92
|
+
onError({ changes: [...failed, ...dropped], data: undefined, reason: 'error' });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
onError({ ...cause, changes: [...failed, ...dropped] });
|
|
96
|
+
}, [forgetLast, onError]);
|
|
59
97
|
const queue = (0, use_ps_change_queue_1.usePsChangeQueue)({
|
|
60
98
|
onDrained: revalidate,
|
|
61
99
|
onFailed: forgetFailed,
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { PsOptimisticSaveInput } from '../interfaces/ps-optimistic-save-input';
|
|
2
2
|
import type { PsOptimisticSaveResult } from '../interfaces/ps-optimistic-save-result';
|
|
3
|
-
export declare const usePsOptimisticSave: <Row, Values, Data>({ fetcher, isSuccess, revalidator, rows, scopeId, spec, }: PsOptimisticSaveInput<Row, Values, Data>) => PsOptimisticSaveResult<Row, Values>;
|
|
3
|
+
export declare const usePsOptimisticSave: <Row, Values, Data>({ fetcher, isSuccess, onError, revalidator, rows, scopeId, spec, }: PsOptimisticSaveInput<Row, Values, Data>) => PsOptimisticSaveResult<Row, Values>;
|
|
@@ -4,12 +4,13 @@ exports.usePsOptimisticSave = void 0;
|
|
|
4
4
|
const react_1 = require("react");
|
|
5
5
|
const ps_optimistic_save_service_1 = require("../services/ps-optimistic-save-service");
|
|
6
6
|
const use_ps_optimistic_fetcher_1 = require("./use-ps-optimistic-fetcher");
|
|
7
|
-
const usePsOptimisticSave = ({ fetcher, isSuccess, revalidator, rows, scopeId, spec, }) => {
|
|
7
|
+
const usePsOptimisticSave = ({ fetcher, isSuccess, onError, revalidator, rows, scopeId, spec, }) => {
|
|
8
8
|
const apply = (0, react_1.useCallback)((base, changes) => ps_optimistic_save_service_1.PsOptimisticSaveService.apply(base, changes, spec), [spec]);
|
|
9
9
|
const optimistic = (0, use_ps_optimistic_fetcher_1.usePsOptimisticFetcher)({
|
|
10
10
|
apply,
|
|
11
11
|
fetcher,
|
|
12
12
|
isSuccess,
|
|
13
|
+
onError,
|
|
13
14
|
revalidator,
|
|
14
15
|
rows,
|
|
15
16
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -95,6 +95,8 @@ export * from './interfaces/ps-live-input';
|
|
|
95
95
|
export * from './interfaces/ps-live-open-options';
|
|
96
96
|
export * from './interfaces/ps-optimistic-fetcher-input';
|
|
97
97
|
export * from './interfaces/ps-optimistic-fetcher-result';
|
|
98
|
+
export * from './interfaces/ps-optimistic-failure';
|
|
99
|
+
export * from './interfaces/ps-optimistic-failure-reason';
|
|
98
100
|
export * from './interfaces/ps-revalidator-like';
|
|
99
101
|
export * from './interfaces/ps-optimistic-rows-input';
|
|
100
102
|
export * from './interfaces/ps-optimistic-rows-result';
|
package/dist/index.js
CHANGED
|
@@ -111,6 +111,8 @@ __exportStar(require("./interfaces/ps-live-input"), exports);
|
|
|
111
111
|
__exportStar(require("./interfaces/ps-live-open-options"), exports);
|
|
112
112
|
__exportStar(require("./interfaces/ps-optimistic-fetcher-input"), exports);
|
|
113
113
|
__exportStar(require("./interfaces/ps-optimistic-fetcher-result"), exports);
|
|
114
|
+
__exportStar(require("./interfaces/ps-optimistic-failure"), exports);
|
|
115
|
+
__exportStar(require("./interfaces/ps-optimistic-failure-reason"), exports);
|
|
114
116
|
__exportStar(require("./interfaces/ps-revalidator-like"), exports);
|
|
115
117
|
__exportStar(require("./interfaces/ps-optimistic-rows-input"), exports);
|
|
116
118
|
__exportStar(require("./interfaces/ps-optimistic-rows-result"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type PsOptimisticFailureReason = 'error' | 'no-response' | 'rejected' | 'timeout' | 'unmounted';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PsFetcherLike } from './ps-fetcher-like';
|
|
2
|
+
import type { PsOptimisticFailure } from './ps-optimistic-failure';
|
|
2
3
|
import type { PsRevalidatorLike } from './ps-revalidator-like';
|
|
3
4
|
export interface PsOptimisticFetcherInput<Row, Data> {
|
|
4
5
|
apply: (rows: Row[], changes: Record<string, string>[]) => Row[];
|
|
@@ -6,6 +7,7 @@ export interface PsOptimisticFetcherInput<Row, Data> {
|
|
|
6
7
|
isHolding?: boolean;
|
|
7
8
|
isSuccess: (data: Data | undefined) => boolean;
|
|
8
9
|
merge?: (changes: Record<string, string>[]) => Record<string, string>[];
|
|
10
|
+
onError?: (failure: PsOptimisticFailure<Data>) => void;
|
|
9
11
|
revalidator: PsRevalidatorLike;
|
|
10
12
|
rows: Row[];
|
|
11
13
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { PsFetcherLike } from './ps-fetcher-like';
|
|
2
|
+
import type { PsOptimisticFailure } from './ps-optimistic-failure';
|
|
2
3
|
import type { PsOptimisticSaveSpec } from './ps-optimistic-save-spec';
|
|
3
4
|
import type { PsRevalidatorLike } from './ps-revalidator-like';
|
|
4
5
|
export interface PsOptimisticSaveInput<Row, Values, Data> {
|
|
5
6
|
fetcher: PsFetcherLike<Data>;
|
|
6
7
|
isSuccess: (data: Data | undefined) => boolean;
|
|
8
|
+
onError?: (failure: PsOptimisticFailure<Data>) => void;
|
|
7
9
|
revalidator: PsRevalidatorLike;
|
|
8
10
|
rows: Row[];
|
|
9
11
|
scopeId: string;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PsOptimisticFailureReason } from '../interfaces/ps-optimistic-failure-reason';
|
|
2
|
+
import type { PsOptimisticOutcome } from '../interfaces/ps-optimistic-outcome';
|
|
3
|
+
export declare class PsOptimisticFetcherService {
|
|
4
|
+
static outcome<Data>(data: Data | undefined, before: Data | undefined): PsOptimisticOutcome<Data>;
|
|
5
|
+
static failureReason<Data>(outcome: PsOptimisticOutcome<Data>, isSuccess: (data: Data | undefined) => boolean): PsOptimisticFailureReason | null;
|
|
6
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsOptimisticFetcherService = void 0;
|
|
4
|
+
class PsOptimisticFetcherService {
|
|
5
|
+
static outcome(data, before) {
|
|
6
|
+
if (data === before)
|
|
7
|
+
return { data: undefined, reason: 'no-response' };
|
|
8
|
+
return { data, reason: 'settled' };
|
|
9
|
+
}
|
|
10
|
+
static failureReason(outcome, isSuccess) {
|
|
11
|
+
if (outcome.reason !== 'settled')
|
|
12
|
+
return outcome.reason;
|
|
13
|
+
return isSuccess(outcome.data) ? null : 'rejected';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.PsOptimisticFetcherService = PsOptimisticFetcherService;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pallasoft/ps-core-components",
|
|
3
|
-
"version": "2.35.
|
|
3
|
+
"version": "2.35.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -33,8 +33,6 @@
|
|
|
33
33
|
"scripts": {
|
|
34
34
|
"husky:prepare": "npx husky",
|
|
35
35
|
"build": "tsc && tsc -p tsconfig.esm.json && tsc-esm-fix --target=dist/esm --ext=.js && node -e \"require('fs').writeFileSync('dist/esm/package.json', JSON.stringify({ type: 'module' }))\"",
|
|
36
|
-
"setup:graphify": "sh scripts/graphify-setup.sh",
|
|
37
|
-
"update:graphify": "sh scripts/graphify-update.sh",
|
|
38
36
|
"lint": "npm run lint:eslint",
|
|
39
37
|
"lint:fix": "npm run lint:eslint:fix",
|
|
40
38
|
"format:check": "npx prettier --check --ignore-unknown \"src/**/*.{ts,tsx,json,md}\"",
|