@pallasoft/ps-core-components 2.44.2 → 2.46.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.
Files changed (59) hide show
  1. package/dist/components/ps-data-boundary.d.ts +7 -2
  2. package/dist/components/ps-data-boundary.js +13 -1
  3. package/dist/components/ps-drag-overlay.js +4 -4
  4. package/dist/components/ps-draggable-grid.js +3 -1
  5. package/dist/components/ps-sortable-list.js +5 -5
  6. package/dist/constants/ps-data-boundary-config.d.ts +4 -0
  7. package/dist/constants/ps-data-boundary-config.js +7 -0
  8. package/dist/constants/ps-data-boundary-variant.d.ts +4 -0
  9. package/dist/constants/ps-data-boundary-variant.js +8 -0
  10. package/dist/constants/ps-drag-config.d.ts +3 -0
  11. package/dist/constants/ps-drag-config.js +10 -4
  12. package/dist/constants/ps-draggable-grid.d.ts +2 -0
  13. package/dist/constants/ps-optimistic-config.d.ts +1 -0
  14. package/dist/constants/ps-optimistic-config.js +1 -0
  15. package/dist/constants/ps-skeleton-config.d.ts +1 -0
  16. package/dist/constants/ps-skeleton-config.js +1 -0
  17. package/dist/esm/components/ps-data-boundary.js +14 -2
  18. package/dist/esm/components/ps-drag-overlay.js +5 -5
  19. package/dist/esm/components/ps-draggable-grid.js +3 -1
  20. package/dist/esm/components/ps-sortable-list.js +6 -6
  21. package/dist/esm/constants/ps-data-boundary-config.js +4 -0
  22. package/dist/esm/constants/ps-data-boundary-variant.js +5 -0
  23. package/dist/esm/constants/ps-drag-config.js +9 -3
  24. package/dist/esm/constants/ps-optimistic-config.js +1 -0
  25. package/dist/esm/constants/ps-skeleton-config.js +1 -0
  26. package/dist/esm/hooks/use-ps-drag-sensors.js +2 -2
  27. package/dist/esm/hooks/use-ps-drag-transition.js +10 -0
  28. package/dist/esm/hooks/use-ps-optimistic-rows.js +23 -12
  29. package/dist/esm/hooks/use-ps-promise-value.js +15 -0
  30. package/dist/esm/hooks/use-ps-retained-promise.js +21 -0
  31. package/dist/esm/index.js +8 -0
  32. package/dist/esm/interfaces/ps-retained-promise-data.js +1 -0
  33. package/dist/esm/models/ps-drag-transition.js +1 -0
  34. package/dist/esm/services/ps-deep-equal-service.js +31 -0
  35. package/dist/esm/services/ps-optimistic-release-service.js +32 -0
  36. package/dist/esm/services/ps-skeleton-class-service.js +11 -1
  37. package/dist/hooks/use-ps-drag-sensors.d.ts +2 -2
  38. package/dist/hooks/use-ps-drag-sensors.js +2 -2
  39. package/dist/hooks/use-ps-drag-transition.d.ts +2 -0
  40. package/dist/hooks/use-ps-drag-transition.js +14 -0
  41. package/dist/hooks/use-ps-optimistic-rows.js +22 -11
  42. package/dist/hooks/use-ps-promise-value.d.ts +1 -0
  43. package/dist/hooks/use-ps-promise-value.js +19 -0
  44. package/dist/hooks/use-ps-retained-promise.d.ts +2 -0
  45. package/dist/hooks/use-ps-retained-promise.js +25 -0
  46. package/dist/index.d.ts +8 -0
  47. package/dist/index.js +8 -0
  48. package/dist/interfaces/ps-optimistic-changes.d.ts +2 -0
  49. package/dist/interfaces/ps-retained-promise-data.d.ts +4 -0
  50. package/dist/interfaces/ps-retained-promise-data.js +2 -0
  51. package/dist/models/ps-drag-transition.d.ts +4 -0
  52. package/dist/models/ps-drag-transition.js +2 -0
  53. package/dist/services/ps-deep-equal-service.d.ts +5 -0
  54. package/dist/services/ps-deep-equal-service.js +35 -0
  55. package/dist/services/ps-optimistic-release-service.d.ts +5 -0
  56. package/dist/services/ps-optimistic-release-service.js +36 -0
  57. package/dist/services/ps-skeleton-class-service.d.ts +1 -0
  58. package/dist/services/ps-skeleton-class-service.js +11 -1
  59. package/package.json +1 -1
@@ -1,9 +1,14 @@
1
1
  import { type ElementType, type ReactNode } from 'react';
2
+ import { PsDataBoundaryVariant } from '../constants/ps-data-boundary-variant';
2
3
  export interface PsDataBoundaryProps<Data> {
3
4
  AwaitComponent: ElementType;
4
- children: (data: Data) => ReactNode;
5
+ children: (data: Data, isPending: boolean) => ReactNode;
6
+ className?: string;
5
7
  errorElement: ReactNode;
6
8
  fallback: ReactNode;
7
9
  resolve: Promise<Data>;
10
+ retain?: boolean;
11
+ retainKey?: string;
12
+ variant?: PsDataBoundaryVariant;
8
13
  }
9
- export declare const PsDataBoundary: <Data>({ AwaitComponent, children, errorElement, fallback, resolve, }: PsDataBoundaryProps<Data>) => import("react").JSX.Element;
14
+ export declare const PsDataBoundary: <Data>({ AwaitComponent, children, className, errorElement, fallback, resolve, retain, retainKey, variant, }: PsDataBoundaryProps<Data>) => import("react").JSX.Element;
@@ -3,5 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PsDataBoundary = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
- const PsDataBoundary = ({ AwaitComponent, children, errorElement, fallback, resolve, }) => ((0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: fallback, children: (0, jsx_runtime_1.jsx)(AwaitComponent, { errorElement: errorElement, resolve: resolve, children: children }) }));
6
+ const ps_design_system_1 = require("@pallasoft/ps-design-system");
7
+ const ps_data_boundary_config_1 = require("../constants/ps-data-boundary-config");
8
+ const ps_data_boundary_variant_1 = require("../constants/ps-data-boundary-variant");
9
+ const PsDataBoundary = ({ AwaitComponent, children, className, errorElement, fallback, resolve, retain = false, retainKey, variant = ps_data_boundary_variant_1.PsDataBoundaryVariant.Page, }) => {
10
+ const deferred = (0, react_1.useDeferredValue)({ key: retainKey, resolve });
11
+ const isRetaining = retain && deferred.key === retainKey;
12
+ const shown = isRetaining ? deferred.resolve : resolve;
13
+ const isPending = shown !== resolve;
14
+ const content = ((0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: fallback, children: (0, jsx_runtime_1.jsx)(AwaitComponent, { errorElement: errorElement, resolve: shown, children: (data) => children(data, isPending) }) }));
15
+ if (variant === ps_data_boundary_variant_1.PsDataBoundaryVariant.Page)
16
+ return content;
17
+ return ((0, jsx_runtime_1.jsx)("div", { "aria-busy": isPending, className: ps_design_system_1.PallasoftClassNameService.merge(ps_data_boundary_config_1.PsDataBoundaryConfig.sectionClassName, isPending ? ps_data_boundary_config_1.PsDataBoundaryConfig.pendingClassName : null, className), children: content }));
18
+ };
7
19
  exports.PsDataBoundary = PsDataBoundary;
@@ -21,9 +21,7 @@ const PsDragOverlay = ({ children, className, frames, isDropAnimated, }) => {
21
21
  const isReduced = (0, use_ps_reduced_motion_1.usePsReducedMotion)();
22
22
  const zIndex = (0, use_ps_layer_z_index_1.usePsLayerZIndex)(ps_design_system_1.PallasoftZIndex.Popover);
23
23
  (0, react_1.useEffect)(() => setIsMounted(true), []);
24
- if (!isMounted)
25
- return null;
26
- const lift = (element) => {
24
+ const lift = (0, react_1.useCallback)((element) => {
27
25
  if (element === null || isReduced || typeof element.animate !== 'function')
28
26
  return;
29
27
  element.animate(frames, {
@@ -31,7 +29,9 @@ const PsDragOverlay = ({ children, className, frames, isDropAnimated, }) => {
31
29
  easing: ps_drag_config_1.PsDragConfig.liftEasing,
32
30
  fill: 'forwards',
33
31
  });
34
- };
32
+ }, [frames, isReduced]);
33
+ if (!isMounted)
34
+ return null;
35
35
  return (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)(core_1.DragOverlay, { dropAnimation: isReduced || isDropAnimated === false ? null : dropAnimation, zIndex: zIndex, children: children === null ? null : ((0, jsx_runtime_1.jsx)("div", { className: className, ref: lift, children: children })) }), document.body);
36
36
  };
37
37
  exports.PsDragOverlay = PsDragOverlay;
@@ -9,11 +9,13 @@ const utilities_1 = require("@dnd-kit/utilities");
9
9
  const ps_drag_config_1 = require("../constants/ps-drag-config");
10
10
  const ps_draggable_grid_1 = require("../constants/ps-draggable-grid");
11
11
  const use_ps_drag_sensors_1 = require("../hooks/use-ps-drag-sensors");
12
+ const use_ps_drag_transition_1 = require("../hooks/use-ps-drag-transition");
12
13
  const ps_drag_grip_1 = require("./ps-drag-grip");
13
14
  const ps_drag_overlay_1 = require("./ps-drag-overlay");
14
15
  const PsDraggableGridHandle = ({ attributes, label, listeners, setActivatorNodeRef, }) => ((0, jsx_runtime_1.jsx)("button", { ...attributes, ...listeners, "aria-label": `Move ${label}`, className: ps_draggable_grid_1.PsDraggableGridClasses.handle, ref: setActivatorNodeRef, title: `Move ${label}`, type: 'button', children: (0, jsx_runtime_1.jsx)(ps_drag_grip_1.PsDragGrip, {}) }));
15
16
  const PsSortableGridItem = ({ className, index, item, renderItem, }) => {
16
- const { attributes, isDragging, listeners, setActivatorNodeRef, setNodeRef, transform, transition, } = (0, sortable_1.useSortable)({ id: item.id });
17
+ const dragTransition = (0, use_ps_drag_transition_1.usePsDragTransition)();
18
+ const { attributes, isDragging, listeners, setActivatorNodeRef, setNodeRef, transform, transition, } = (0, sortable_1.useSortable)({ id: item.id, transition: dragTransition });
17
19
  const rendered = renderItem({
18
20
  handle: ((0, jsx_runtime_1.jsx)(PsDraggableGridHandle, { attributes: attributes, label: item.label, listeners: listeners, setActivatorNodeRef: setActivatorNodeRef })),
19
21
  index,
@@ -8,13 +8,13 @@ const sortable_1 = require("@dnd-kit/sortable");
8
8
  const utilities_1 = require("@dnd-kit/utilities");
9
9
  const ps_drag_config_1 = require("../constants/ps-drag-config");
10
10
  const use_ps_drag_sensors_1 = require("../hooks/use-ps-drag-sensors");
11
- const use_ps_reduced_motion_1 = require("../hooks/use-ps-reduced-motion");
11
+ const use_ps_drag_transition_1 = require("../hooks/use-ps-drag-transition");
12
12
  const ps_drag_grip_1 = require("./ps-drag-grip");
13
13
  const ps_drag_overlay_1 = require("./ps-drag-overlay");
14
14
  const PsSortableListHandle = ({ attributes, className, icon, label, listeners, setActivatorNodeRef, }) => ((0, jsx_runtime_1.jsx)("button", { ...attributes, ...listeners, "aria-label": label, className: className === undefined ? ps_drag_config_1.PsDragClasses.handle : className, ref: setActivatorNodeRef, title: label, type: 'button', children: icon === undefined ? (0, jsx_runtime_1.jsx)(ps_drag_grip_1.PsDragGrip, {}) : icon }));
15
15
  const PsSortableListRow = ({ className, handleClassName, handleIcon, getHandleLabel, index, isDisabled, item, renderItem, }) => {
16
- const isReduced = (0, use_ps_reduced_motion_1.usePsReducedMotion)();
17
- const { attributes, isDragging, listeners, setActivatorNodeRef, setNodeRef, transform, transition, } = (0, sortable_1.useSortable)({ disabled: isDisabled, id: item.id });
16
+ const dragTransition = (0, use_ps_drag_transition_1.usePsDragTransition)();
17
+ const { attributes, isDragging, listeners, setActivatorNodeRef, setNodeRef, transform, transition, } = (0, sortable_1.useSortable)({ disabled: isDisabled, id: item.id, transition: dragTransition });
18
18
  const rendered = renderItem({
19
19
  handle: ((0, jsx_runtime_1.jsx)(PsSortableListHandle, { attributes: attributes, className: handleClassName, icon: handleIcon, label: getHandleLabel(item.label), listeners: listeners, setActivatorNodeRef: setActivatorNodeRef })),
20
20
  index,
@@ -27,7 +27,7 @@ const PsSortableListRow = ({ className, handleClassName, handleIcon, getHandleLa
27
27
  return ((0, jsx_runtime_1.jsx)("div", { className: `${ps_drag_config_1.PsDragClasses.listItem} ${className} ${isDragging ? ps_drag_config_1.PsDragClasses.placeholder : ''}`, ref: setNodeRef, style: {
28
28
  opacity: isDragging ? ps_drag_config_1.PsDragConfig.placeholderOpacity : undefined,
29
29
  transform: utilities_1.CSS.Translate.toString(transform),
30
- transition: isReduced ? undefined : transition,
30
+ transition,
31
31
  }, children: rendered }));
32
32
  };
33
33
  const PsSortableList = ({ announcements: texts, className, getHandleLabel, getItemClassName, handleClassName, handleIcon, instructions, isHorizontal = false, role, items, onMove, renderItem, }) => {
@@ -66,7 +66,7 @@ const PsSortableList = ({ announcements: texts, className, getHandleLabel, getIt
66
66
  onMove(String(active.id), from, to);
67
67
  };
68
68
  const activeGroup = activeItem === null ? undefined : activeItem.groupKey;
69
- return ((0, jsx_runtime_1.jsxs)(core_1.DndContext, { accessibility: { announcements, screenReaderInstructions: { draggable: instructions } }, collisionDetection: core_1.closestCenter, onDragCancel: () => setActiveId(null), onDragEnd: handleDragEnd, onDragStart: ({ active }) => setActiveId(String(active.id)), sensors: sensors, children: [(0, jsx_runtime_1.jsx)(sortable_1.SortableContext, { items: items.map((item) => item.id), strategy: isHorizontal ? sortable_1.horizontalListSortingStrategy : sortable_1.verticalListSortingStrategy, children: (0, jsx_runtime_1.jsx)("div", { className: className, role: role, children: items.map((item, index) => ((0, jsx_runtime_1.jsx)(PsSortableListRow, { className: itemClassName(item), getHandleLabel: getHandleLabel, handleClassName: handleClassName, handleIcon: handleIcon, index: index, isDisabled: activeItem !== null && item.groupKey !== activeGroup, item: item, renderItem: renderItem }, item.id))) }) }), (0, jsx_runtime_1.jsx)(ps_drag_overlay_1.PsDragOverlay, { className: ps_drag_config_1.PsDragClasses.lifted, frames: ps_drag_config_1.PsDragListLiftFrames, children: activeItem !== null
69
+ return ((0, jsx_runtime_1.jsxs)(core_1.DndContext, { accessibility: { announcements, screenReaderInstructions: { draggable: instructions } }, collisionDetection: core_1.closestCenter, onDragCancel: () => setActiveId(null), onDragEnd: handleDragEnd, onDragStart: ({ active }) => setActiveId(String(active.id)), sensors: sensors, children: [(0, jsx_runtime_1.jsx)(sortable_1.SortableContext, { items: items.map((item) => item.id), strategy: isHorizontal ? sortable_1.horizontalListSortingStrategy : sortable_1.verticalListSortingStrategy, children: (0, jsx_runtime_1.jsx)("div", { className: className, role: role, children: items.map((item, index) => ((0, jsx_runtime_1.jsx)(PsSortableListRow, { className: itemClassName(item), getHandleLabel: getHandleLabel, handleClassName: handleClassName, handleIcon: handleIcon, index: index, isDisabled: activeItem !== null && item.groupKey !== activeGroup, item: item, renderItem: renderItem }, item.id))) }) }), (0, jsx_runtime_1.jsx)(ps_drag_overlay_1.PsDragOverlay, { className: ps_drag_config_1.PsDragClasses.lifted, frames: ps_drag_config_1.PsDragRowLiftFrames, children: activeItem !== null
70
70
  ? renderItem({
71
71
  handle: ((0, jsx_runtime_1.jsx)(PsSortableListHandle, { className: handleClassName, icon: handleIcon, label: getHandleLabel(activeItem.label) })),
72
72
  index: activeIndex,
@@ -0,0 +1,4 @@
1
+ export declare const PsDataBoundaryConfig: {
2
+ readonly pendingClassName: "opacity-60 transition-opacity";
3
+ readonly sectionClassName: "transition-opacity";
4
+ };
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsDataBoundaryConfig = void 0;
4
+ exports.PsDataBoundaryConfig = {
5
+ pendingClassName: 'opacity-60 transition-opacity',
6
+ sectionClassName: 'transition-opacity',
7
+ };
@@ -0,0 +1,4 @@
1
+ export declare enum PsDataBoundaryVariant {
2
+ Page = "page",
3
+ Section = "section"
4
+ }
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsDataBoundaryVariant = void 0;
4
+ var PsDataBoundaryVariant;
5
+ (function (PsDataBoundaryVariant) {
6
+ PsDataBoundaryVariant["Page"] = "page";
7
+ PsDataBoundaryVariant["Section"] = "section";
8
+ })(PsDataBoundaryVariant || (exports.PsDataBoundaryVariant = PsDataBoundaryVariant = {}));
@@ -5,6 +5,8 @@ export declare const PsDragConfig: {
5
5
  readonly liftDuration: 200;
6
6
  readonly liftEasing: "cubic-bezier(0.2, 0.9, 0.3, 1.2)";
7
7
  readonly placeholderOpacity: "0.4";
8
+ readonly sortDuration: 250;
9
+ readonly sortEasing: "ease";
8
10
  readonly touchDelay: 180;
9
11
  readonly touchTolerance: 6;
10
12
  };
@@ -15,4 +17,5 @@ export declare const PsDragClasses: {
15
17
  readonly placeholder: "outline-dashed outline-2 outline-offset-[-2px] outline-primary/35";
16
18
  };
17
19
  export declare const PsDragLiftFrames: Keyframe[];
20
+ export declare const PsDragRowLiftFrames: Keyframe[];
18
21
  export declare const PsDragListLiftFrames: Keyframe[];
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PsDragListLiftFrames = exports.PsDragLiftFrames = exports.PsDragClasses = exports.PsDragConfig = void 0;
3
+ exports.PsDragListLiftFrames = exports.PsDragRowLiftFrames = exports.PsDragLiftFrames = exports.PsDragClasses = exports.PsDragConfig = void 0;
4
4
  exports.PsDragConfig = {
5
5
  activationDistance: 6,
6
6
  dropDuration: 220,
@@ -8,6 +8,8 @@ exports.PsDragConfig = {
8
8
  liftDuration: 200,
9
9
  liftEasing: 'cubic-bezier(0.2, 0.9, 0.3, 1.2)',
10
10
  placeholderOpacity: '0.4',
11
+ sortDuration: 250,
12
+ sortEasing: 'ease',
11
13
  touchDelay: 180,
12
14
  touchTolerance: 6,
13
15
  };
@@ -24,7 +26,11 @@ exports.PsDragLiftFrames = [
24
26
  transform: 'scale(1.035) rotate(-1.2deg)',
25
27
  },
26
28
  ];
27
- exports.PsDragListLiftFrames = [
28
- { boxShadow: '0 0 0 0 rgb(15 23 42 / 0)', transform: 'scale(1)' },
29
- { boxShadow: '0 18px 40px -16px rgb(15 23 42 / 0.45)', transform: 'scale(1.015)' },
29
+ exports.PsDragRowLiftFrames = [
30
+ { boxShadow: '0 0 0 0 rgb(15 23 42 / 0)', transform: 'scale(1) rotate(0deg)' },
31
+ {
32
+ boxShadow: '0 28px 64px -18px rgb(15 23 42 / 0.5)',
33
+ transform: 'scale(1.035) rotate(-0.25deg)',
34
+ },
30
35
  ];
36
+ exports.PsDragListLiftFrames = exports.PsDragRowLiftFrames;
@@ -5,6 +5,8 @@ export declare const PsDraggableGridConfig: {
5
5
  readonly liftDuration: 200;
6
6
  readonly liftEasing: "cubic-bezier(0.2, 0.9, 0.3, 1.2)";
7
7
  readonly placeholderOpacity: "0.4";
8
+ readonly sortDuration: 250;
9
+ readonly sortEasing: "ease";
8
10
  readonly touchDelay: 180;
9
11
  readonly touchTolerance: 6;
10
12
  };
@@ -1,4 +1,5 @@
1
1
  export declare const PsOptimisticConfig: {
2
2
  readonly pendingPrefix: "pending-";
3
+ readonly releaseTimeoutMs: 15000;
3
4
  readonly submitTimeoutMs: 30000;
4
5
  };
@@ -3,5 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PsOptimisticConfig = void 0;
4
4
  exports.PsOptimisticConfig = {
5
5
  pendingPrefix: 'pending-',
6
+ releaseTimeoutMs: 15000,
6
7
  submitTimeoutMs: 30000,
7
8
  };
@@ -10,6 +10,7 @@ export declare const PsSkeletonConfig: {
10
10
  readonly importantPrefix: "!";
11
11
  readonly widthUtilityPattern: RegExp;
12
12
  readonly heightUtilityPattern: RegExp;
13
+ readonly radiusUtilityPattern: RegExp;
13
14
  readonly compactControlHeightClassName: "h-ps-control-compact";
14
15
  readonly tabWidthClassName: "w-24";
15
16
  readonly tabCount: 4;
@@ -16,6 +16,7 @@ exports.PsSkeletonConfig = {
16
16
  importantPrefix: '!',
17
17
  widthUtilityPattern: /^(w|size)-/,
18
18
  heightUtilityPattern: /^(h|size)-/,
19
+ radiusUtilityPattern: /^rounded(-|$)/,
19
20
  compactControlHeightClassName: 'h-ps-control-compact',
20
21
  tabWidthClassName: 'w-24',
21
22
  tabCount: 4,
@@ -1,3 +1,15 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Suspense } from 'react';
3
- export const PsDataBoundary = ({ AwaitComponent, children, errorElement, fallback, resolve, }) => (_jsx(Suspense, { fallback: fallback, children: _jsx(AwaitComponent, { errorElement: errorElement, resolve: resolve, children: children }) }));
2
+ import { Suspense, useDeferredValue } from 'react';
3
+ import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
4
+ import { PsDataBoundaryConfig } from '../constants/ps-data-boundary-config.js';
5
+ import { PsDataBoundaryVariant } from '../constants/ps-data-boundary-variant.js';
6
+ export const PsDataBoundary = ({ AwaitComponent, children, className, errorElement, fallback, resolve, retain = false, retainKey, variant = PsDataBoundaryVariant.Page, }) => {
7
+ const deferred = useDeferredValue({ key: retainKey, resolve });
8
+ const isRetaining = retain && deferred.key === retainKey;
9
+ const shown = isRetaining ? deferred.resolve : resolve;
10
+ const isPending = shown !== resolve;
11
+ const content = (_jsx(Suspense, { fallback: fallback, children: _jsx(AwaitComponent, { errorElement: errorElement, resolve: shown, children: (data) => children(data, isPending) }) }));
12
+ if (variant === PsDataBoundaryVariant.Page)
13
+ return content;
14
+ return (_jsx("div", { "aria-busy": isPending, className: PallasoftClassNameService.merge(PsDataBoundaryConfig.sectionClassName, isPending ? PsDataBoundaryConfig.pendingClassName : null, className), children: content }));
15
+ };
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useEffect, useState } from 'react';
2
+ import { useCallback, useEffect, useState } from 'react';
3
3
  import { DragOverlay, defaultDropAnimationSideEffects } from '@dnd-kit/core';
4
4
  import { PallasoftZIndex } from '@pallasoft/ps-design-system';
5
5
  import { createPortal } from 'react-dom';
@@ -18,9 +18,7 @@ export const PsDragOverlay = ({ children, className, frames, isDropAnimated, })
18
18
  const isReduced = usePsReducedMotion();
19
19
  const zIndex = usePsLayerZIndex(PallasoftZIndex.Popover);
20
20
  useEffect(() => setIsMounted(true), []);
21
- if (!isMounted)
22
- return null;
23
- const lift = (element) => {
21
+ const lift = useCallback((element) => {
24
22
  if (element === null || isReduced || typeof element.animate !== 'function')
25
23
  return;
26
24
  element.animate(frames, {
@@ -28,6 +26,8 @@ export const PsDragOverlay = ({ children, className, frames, isDropAnimated, })
28
26
  easing: PsDragConfig.liftEasing,
29
27
  fill: 'forwards',
30
28
  });
31
- };
29
+ }, [frames, isReduced]);
30
+ if (!isMounted)
31
+ return null;
32
32
  return createPortal(_jsx(DragOverlay, { dropAnimation: isReduced || isDropAnimated === false ? null : dropAnimation, zIndex: zIndex, children: children === null ? null : (_jsx("div", { className: className, ref: lift, children: children })) }), document.body);
33
33
  };
@@ -6,11 +6,13 @@ import { CSS } from '@dnd-kit/utilities';
6
6
  import { PsDragConfig } from '../constants/ps-drag-config.js';
7
7
  import { PsDraggableGridClasses, PsDraggableGridLiftFrames } from '../constants/ps-draggable-grid.js';
8
8
  import { usePsDragSensors } from '../hooks/use-ps-drag-sensors.js';
9
+ import { usePsDragTransition } from '../hooks/use-ps-drag-transition.js';
9
10
  import { PsDragGrip } from './ps-drag-grip.js';
10
11
  import { PsDragOverlay } from './ps-drag-overlay.js';
11
12
  const PsDraggableGridHandle = ({ attributes, label, listeners, setActivatorNodeRef, }) => (_jsx("button", { ...attributes, ...listeners, "aria-label": `Move ${label}`, className: PsDraggableGridClasses.handle, ref: setActivatorNodeRef, title: `Move ${label}`, type: 'button', children: _jsx(PsDragGrip, {}) }));
12
13
  const PsSortableGridItem = ({ className, index, item, renderItem, }) => {
13
- const { attributes, isDragging, listeners, setActivatorNodeRef, setNodeRef, transform, transition, } = useSortable({ id: item.id });
14
+ const dragTransition = usePsDragTransition();
15
+ const { attributes, isDragging, listeners, setActivatorNodeRef, setNodeRef, transform, transition, } = useSortable({ id: item.id, transition: dragTransition });
14
16
  const rendered = renderItem({
15
17
  handle: (_jsx(PsDraggableGridHandle, { attributes: attributes, label: item.label, listeners: listeners, setActivatorNodeRef: setActivatorNodeRef })),
16
18
  index,
@@ -3,15 +3,15 @@ import { useState } from 'react';
3
3
  import { DndContext, closestCenter, } from '@dnd-kit/core';
4
4
  import { SortableContext, horizontalListSortingStrategy, useSortable, verticalListSortingStrategy, } from '@dnd-kit/sortable';
5
5
  import { CSS } from '@dnd-kit/utilities';
6
- import { PsDragClasses, PsDragConfig, PsDragListLiftFrames } from '../constants/ps-drag-config.js';
6
+ import { PsDragClasses, PsDragConfig, PsDragRowLiftFrames } from '../constants/ps-drag-config.js';
7
7
  import { usePsDragSensors } from '../hooks/use-ps-drag-sensors.js';
8
- import { usePsReducedMotion } from '../hooks/use-ps-reduced-motion.js';
8
+ import { usePsDragTransition } from '../hooks/use-ps-drag-transition.js';
9
9
  import { PsDragGrip } from './ps-drag-grip.js';
10
10
  import { PsDragOverlay } from './ps-drag-overlay.js';
11
11
  const PsSortableListHandle = ({ attributes, className, icon, label, listeners, setActivatorNodeRef, }) => (_jsx("button", { ...attributes, ...listeners, "aria-label": label, className: className === undefined ? PsDragClasses.handle : className, ref: setActivatorNodeRef, title: label, type: 'button', children: icon === undefined ? _jsx(PsDragGrip, {}) : icon }));
12
12
  const PsSortableListRow = ({ className, handleClassName, handleIcon, getHandleLabel, index, isDisabled, item, renderItem, }) => {
13
- const isReduced = usePsReducedMotion();
14
- const { attributes, isDragging, listeners, setActivatorNodeRef, setNodeRef, transform, transition, } = useSortable({ disabled: isDisabled, id: item.id });
13
+ const dragTransition = usePsDragTransition();
14
+ const { attributes, isDragging, listeners, setActivatorNodeRef, setNodeRef, transform, transition, } = useSortable({ disabled: isDisabled, id: item.id, transition: dragTransition });
15
15
  const rendered = renderItem({
16
16
  handle: (_jsx(PsSortableListHandle, { attributes: attributes, className: handleClassName, icon: handleIcon, label: getHandleLabel(item.label), listeners: listeners, setActivatorNodeRef: setActivatorNodeRef })),
17
17
  index,
@@ -24,7 +24,7 @@ const PsSortableListRow = ({ className, handleClassName, handleIcon, getHandleLa
24
24
  return (_jsx("div", { className: `${PsDragClasses.listItem} ${className} ${isDragging ? PsDragClasses.placeholder : ''}`, ref: setNodeRef, style: {
25
25
  opacity: isDragging ? PsDragConfig.placeholderOpacity : undefined,
26
26
  transform: CSS.Translate.toString(transform),
27
- transition: isReduced ? undefined : transition,
27
+ transition,
28
28
  }, children: rendered }));
29
29
  };
30
30
  export const PsSortableList = ({ announcements: texts, className, getHandleLabel, getItemClassName, handleClassName, handleIcon, instructions, isHorizontal = false, role, items, onMove, renderItem, }) => {
@@ -63,7 +63,7 @@ export const PsSortableList = ({ announcements: texts, className, getHandleLabel
63
63
  onMove(String(active.id), from, to);
64
64
  };
65
65
  const activeGroup = activeItem === null ? undefined : activeItem.groupKey;
66
- return (_jsxs(DndContext, { accessibility: { announcements, screenReaderInstructions: { draggable: instructions } }, collisionDetection: closestCenter, onDragCancel: () => setActiveId(null), onDragEnd: handleDragEnd, onDragStart: ({ active }) => setActiveId(String(active.id)), sensors: sensors, children: [_jsx(SortableContext, { items: items.map((item) => item.id), strategy: isHorizontal ? horizontalListSortingStrategy : verticalListSortingStrategy, children: _jsx("div", { className: className, role: role, children: items.map((item, index) => (_jsx(PsSortableListRow, { className: itemClassName(item), getHandleLabel: getHandleLabel, handleClassName: handleClassName, handleIcon: handleIcon, index: index, isDisabled: activeItem !== null && item.groupKey !== activeGroup, item: item, renderItem: renderItem }, item.id))) }) }), _jsx(PsDragOverlay, { className: PsDragClasses.lifted, frames: PsDragListLiftFrames, children: activeItem !== null
66
+ return (_jsxs(DndContext, { accessibility: { announcements, screenReaderInstructions: { draggable: instructions } }, collisionDetection: closestCenter, onDragCancel: () => setActiveId(null), onDragEnd: handleDragEnd, onDragStart: ({ active }) => setActiveId(String(active.id)), sensors: sensors, children: [_jsx(SortableContext, { items: items.map((item) => item.id), strategy: isHorizontal ? horizontalListSortingStrategy : verticalListSortingStrategy, children: _jsx("div", { className: className, role: role, children: items.map((item, index) => (_jsx(PsSortableListRow, { className: itemClassName(item), getHandleLabel: getHandleLabel, handleClassName: handleClassName, handleIcon: handleIcon, index: index, isDisabled: activeItem !== null && item.groupKey !== activeGroup, item: item, renderItem: renderItem }, item.id))) }) }), _jsx(PsDragOverlay, { className: PsDragClasses.lifted, frames: PsDragRowLiftFrames, children: activeItem !== null
67
67
  ? renderItem({
68
68
  handle: (_jsx(PsSortableListHandle, { className: handleClassName, icon: handleIcon, label: getHandleLabel(activeItem.label) })),
69
69
  index: activeIndex,
@@ -0,0 +1,4 @@
1
+ export const PsDataBoundaryConfig = {
2
+ pendingClassName: 'opacity-60 transition-opacity',
3
+ sectionClassName: 'transition-opacity',
4
+ };
@@ -0,0 +1,5 @@
1
+ export var PsDataBoundaryVariant;
2
+ (function (PsDataBoundaryVariant) {
3
+ PsDataBoundaryVariant["Page"] = "page";
4
+ PsDataBoundaryVariant["Section"] = "section";
5
+ })(PsDataBoundaryVariant || (PsDataBoundaryVariant = {}));
@@ -5,6 +5,8 @@ export const PsDragConfig = {
5
5
  liftDuration: 200,
6
6
  liftEasing: 'cubic-bezier(0.2, 0.9, 0.3, 1.2)',
7
7
  placeholderOpacity: '0.4',
8
+ sortDuration: 250,
9
+ sortEasing: 'ease',
8
10
  touchDelay: 180,
9
11
  touchTolerance: 6,
10
12
  };
@@ -21,7 +23,11 @@ export const PsDragLiftFrames = [
21
23
  transform: 'scale(1.035) rotate(-1.2deg)',
22
24
  },
23
25
  ];
24
- export const PsDragListLiftFrames = [
25
- { boxShadow: '0 0 0 0 rgb(15 23 42 / 0)', transform: 'scale(1)' },
26
- { boxShadow: '0 18px 40px -16px rgb(15 23 42 / 0.45)', transform: 'scale(1.015)' },
26
+ export const PsDragRowLiftFrames = [
27
+ { boxShadow: '0 0 0 0 rgb(15 23 42 / 0)', transform: 'scale(1) rotate(0deg)' },
28
+ {
29
+ boxShadow: '0 28px 64px -18px rgb(15 23 42 / 0.5)',
30
+ transform: 'scale(1.035) rotate(-0.25deg)',
31
+ },
27
32
  ];
33
+ export const PsDragListLiftFrames = PsDragRowLiftFrames;
@@ -1,4 +1,5 @@
1
1
  export const PsOptimisticConfig = {
2
2
  pendingPrefix: 'pending-',
3
+ releaseTimeoutMs: 15000,
3
4
  submitTimeoutMs: 30000,
4
5
  };
@@ -13,6 +13,7 @@ export const PsSkeletonConfig = {
13
13
  importantPrefix: '!',
14
14
  widthUtilityPattern: /^(w|size)-/,
15
15
  heightUtilityPattern: /^(h|size)-/,
16
+ radiusUtilityPattern: /^rounded(-|$)/,
16
17
  compactControlHeightClassName: 'h-ps-control-compact',
17
18
  tabWidthClassName: 'w-24',
18
19
  tabCount: 4,
@@ -1,11 +1,11 @@
1
1
  import { KeyboardSensor, MouseSensor, TouchSensor, useSensor, useSensors, } from '@dnd-kit/core';
2
2
  import { sortableKeyboardCoordinates } from '@dnd-kit/sortable';
3
3
  import { PsDragConfig } from '../constants/ps-drag-config.js';
4
- export const usePsDragSensors = () => useSensors(useSensor(MouseSensor, {
4
+ export const usePsDragSensors = (coordinateGetter = sortableKeyboardCoordinates) => useSensors(useSensor(MouseSensor, {
5
5
  activationConstraint: { distance: PsDragConfig.activationDistance },
6
6
  }), useSensor(TouchSensor, {
7
7
  activationConstraint: {
8
8
  delay: PsDragConfig.touchDelay,
9
9
  tolerance: PsDragConfig.touchTolerance,
10
10
  },
11
- }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
11
+ }), useSensor(KeyboardSensor, { coordinateGetter }));
@@ -0,0 +1,10 @@
1
+ import { PsDragConfig } from '../constants/ps-drag-config.js';
2
+ import { usePsReducedMotion } from './use-ps-reduced-motion.js';
3
+ const transition = {
4
+ duration: PsDragConfig.sortDuration,
5
+ easing: PsDragConfig.sortEasing,
6
+ };
7
+ export const usePsDragTransition = () => {
8
+ const isReduced = usePsReducedMotion();
9
+ return isReduced ? null : transition;
10
+ };
@@ -1,17 +1,28 @@
1
- import { useCallback, useMemo, useState } from 'react';
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { PsOptimisticConfig } from '../constants/ps-optimistic-config.js';
3
+ import { PsOptimisticReleaseService } from '../services/ps-optimistic-release-service.js';
2
4
  export const usePsOptimisticRows = ({ apply, isHolding = false, isSaving, rows, }) => {
3
- const [remembered, setRemembered] = useState({
4
- base: rows,
5
- changes: [],
6
- });
5
+ const [remembered, setRemembered] = useState(() => PsOptimisticReleaseService.empty(rows));
7
6
  const isActive = isSaving || isHolding;
8
- const isStale = remembered.base !== rows && !isActive;
9
- if (isStale)
10
- setRemembered({ base: rows, changes: [] });
11
- const changes = isStale ? [] : remembered.changes;
7
+ const current = PsOptimisticReleaseService.next(remembered, rows, isActive, apply);
8
+ if (current !== remembered)
9
+ setRemembered(current);
10
+ const { changes } = current;
11
+ const isHeld = changes.length > 0 && !isActive;
12
12
  const shown = useMemo(() => (changes.length === 0 ? rows : apply(rows, changes)), [apply, changes, rows]);
13
- const remember = useCallback((change) => setRemembered((current) => ({ base: rows, changes: [...current.changes, change] })), [rows]);
14
- const forgetLast = useCallback(() => setRemembered((current) => ({ base: current.base, changes: current.changes.slice(0, -1) })), []);
15
- const rollback = useCallback(() => setRemembered({ base: rows, changes: [] }), [rows]);
13
+ useEffect(() => {
14
+ if (!isHeld)
15
+ return undefined;
16
+ const timer = setTimeout(() => setRemembered(PsOptimisticReleaseService.empty(rows)), PsOptimisticConfig.releaseTimeoutMs);
17
+ return () => clearTimeout(timer);
18
+ }, [isHeld, changes, rows]);
19
+ const remember = useCallback((change) => setRemembered((previous) => ({
20
+ base: rows,
21
+ changes: [...previous.changes, change],
22
+ drainBase: null,
23
+ wasActive: previous.wasActive,
24
+ })), [rows]);
25
+ const forgetLast = useCallback(() => setRemembered((previous) => ({ ...previous, changes: previous.changes.slice(0, -1) })), []);
26
+ const rollback = useCallback(() => setRemembered(PsOptimisticReleaseService.empty(rows)), [rows]);
16
27
  return { forgetLast, remember, rollback, rows: shown };
17
28
  };
@@ -0,0 +1,15 @@
1
+ import { useEffect, useState } from 'react';
2
+ export const usePsPromiseValue = (promise, initial) => {
3
+ const [value, setValue] = useState(initial);
4
+ useEffect(() => {
5
+ let isCurrent = true;
6
+ void promise.then((resolved) => {
7
+ if (isCurrent)
8
+ setValue(resolved);
9
+ }, () => undefined);
10
+ return () => {
11
+ isCurrent = false;
12
+ };
13
+ }, [promise]);
14
+ return value;
15
+ };
@@ -0,0 +1,21 @@
1
+ import { useEffect, useState } from 'react';
2
+ export const usePsRetainedPromise = (promise) => {
3
+ const [state, setState] = useState({
4
+ hasFailed: false,
5
+ value: null,
6
+ });
7
+ useEffect(() => {
8
+ let isCurrent = true;
9
+ void promise.then((value) => {
10
+ if (isCurrent)
11
+ setState({ hasFailed: false, value });
12
+ }, () => {
13
+ if (isCurrent)
14
+ setState((previous) => ({ ...previous, hasFailed: true }));
15
+ });
16
+ return () => {
17
+ isCurrent = false;
18
+ };
19
+ }, [promise]);
20
+ return state;
21
+ };
package/dist/esm/index.js CHANGED
@@ -127,6 +127,12 @@ export * from './components/ps-active-filter-chip.js';
127
127
  export * from './components/ps-back-link.js';
128
128
  export * from './components/ps-card-block-skeleton.js';
129
129
  export * from './components/ps-data-boundary.js';
130
+ export * from './constants/ps-data-boundary-config.js';
131
+ export * from './constants/ps-data-boundary-variant.js';
132
+ export * from './hooks/use-ps-retained-promise.js';
133
+ export * from './hooks/use-ps-promise-value.js';
134
+ export * from './interfaces/ps-retained-promise-data.js';
135
+ export * from './services/ps-deep-equal-service.js';
130
136
  export * from './components/ps-faq-answer.js';
131
137
  export * from './components/ps-feature-grid.js';
132
138
  export * from './components/ps-feature-card.js';
@@ -188,6 +194,8 @@ export * from './models/ps-summary-item.js';
188
194
  export * from './components/ps-drag-grip.js';
189
195
  export * from './components/ps-drag-overlay.js';
190
196
  export * from './hooks/use-ps-drag-sensors.js';
197
+ export * from './hooks/use-ps-drag-transition.js';
198
+ export * from './models/ps-drag-transition.js';
191
199
  export * from './hooks/use-ps-list-keys.js';
192
200
  export * from './interfaces/ps-list-keys-result.js';
193
201
  export * from './services/ps-list-key-service.js';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ export class PsDeepEqualService {
2
+ static isEqual(left, right) {
3
+ if (Object.is(left, right))
4
+ return true;
5
+ if (typeof left !== 'object' || typeof right !== 'object')
6
+ return false;
7
+ if (left === null || right === null)
8
+ return false;
9
+ if (Array.isArray(left) || Array.isArray(right))
10
+ return PsDeepEqualService.isArrayEqual(left, right);
11
+ if (left instanceof Date || right instanceof Date) {
12
+ return left instanceof Date && right instanceof Date && left.getTime() === right.getTime();
13
+ }
14
+ return PsDeepEqualService.isRecordEqual(left, right);
15
+ }
16
+ static isArrayEqual(left, right) {
17
+ if (!Array.isArray(left) || !Array.isArray(right))
18
+ return false;
19
+ if (left.length !== right.length)
20
+ return false;
21
+ return left.every((item, index) => PsDeepEqualService.isEqual(item, right[index]));
22
+ }
23
+ static isRecordEqual(left, right) {
24
+ const leftEntries = Object.entries(left);
25
+ const rightRecord = { ...right };
26
+ if (leftEntries.length !== Object.keys(rightRecord).length)
27
+ return false;
28
+ return leftEntries.every(([key, value]) => Object.prototype.hasOwnProperty.call(rightRecord, key) &&
29
+ PsDeepEqualService.isEqual(value, rightRecord[key]));
30
+ }
31
+ }
@@ -0,0 +1,32 @@
1
+ import { PsDeepEqualService } from './ps-deep-equal-service.js';
2
+ export class PsOptimisticReleaseService {
3
+ static empty(rows) {
4
+ return { base: rows, changes: [], drainBase: null, wasActive: false };
5
+ }
6
+ static next(remembered, rows, isActive, apply) {
7
+ if (isActive) {
8
+ if (remembered.wasActive && remembered.drainBase === null)
9
+ return remembered;
10
+ return { ...remembered, drainBase: null, wasActive: true };
11
+ }
12
+ if (remembered.changes.length === 0) {
13
+ if (remembered.base === rows && !remembered.wasActive)
14
+ return remembered;
15
+ return PsOptimisticReleaseService.empty(rows);
16
+ }
17
+ if (remembered.wasActive) {
18
+ if (!PsDeepEqualService.isEqual(rows, remembered.base)) {
19
+ return PsOptimisticReleaseService.empty(rows);
20
+ }
21
+ return { ...remembered, drainBase: rows, wasActive: false };
22
+ }
23
+ const reference = remembered.drainBase === null ? remembered.base : remembered.drainBase;
24
+ if (!PsDeepEqualService.isEqual(rows, reference)) {
25
+ return PsOptimisticReleaseService.empty(rows);
26
+ }
27
+ if (PsDeepEqualService.isEqual(apply(rows, remembered.changes), rows)) {
28
+ return PsOptimisticReleaseService.empty(rows);
29
+ }
30
+ return remembered;
31
+ }
32
+ }
@@ -10,8 +10,18 @@ export class PsSkeletonClassService {
10
10
  : [];
11
11
  const hasWidth = utilities.some((utility) => PsSkeletonConfig.widthUtilityPattern.test(utility));
12
12
  const hasHeight = utilities.some((utility) => PsSkeletonConfig.heightUtilityPattern.test(utility));
13
+ const hasRadius = utilities.some((utility) => PsSkeletonConfig.radiusUtilityPattern.test(utility));
13
14
  const size = PsSkeletonConfig.shapeSizes[shape];
14
- return PallasoftClassNameService.merge(PsSkeletonConfig.baseClassName, PsSkeletonConfig.shapeClassNames[shape], hasHeight ? null : size.height, hasWidth ? null : size.width, className);
15
+ const shapeClassName = hasRadius
16
+ ? PsSkeletonClassService.withoutRadius(PsSkeletonConfig.shapeClassNames[shape])
17
+ : PsSkeletonConfig.shapeClassNames[shape];
18
+ return PallasoftClassNameService.merge(PsSkeletonConfig.baseClassName, shapeClassName, hasHeight ? null : size.height, hasWidth ? null : size.width, className);
19
+ }
20
+ static withoutRadius(className) {
21
+ return className
22
+ .split(PsSkeletonConfig.classSeparator)
23
+ .filter((token) => !PsSkeletonConfig.radiusUtilityPattern.test(token))
24
+ .join(' ');
15
25
  }
16
26
  static withoutImportant(token) {
17
27
  return token.startsWith(PsSkeletonConfig.importantPrefix) ? token.slice(1) : token;
@@ -1,2 +1,2 @@
1
- import { type SensorDescriptor, type SensorOptions } from '@dnd-kit/core';
2
- export declare const usePsDragSensors: () => SensorDescriptor<SensorOptions>[];
1
+ import { type KeyboardCoordinateGetter, type SensorDescriptor, type SensorOptions } from '@dnd-kit/core';
2
+ export declare const usePsDragSensors: (coordinateGetter?: KeyboardCoordinateGetter) => SensorDescriptor<SensorOptions>[];
@@ -4,12 +4,12 @@ exports.usePsDragSensors = void 0;
4
4
  const core_1 = require("@dnd-kit/core");
5
5
  const sortable_1 = require("@dnd-kit/sortable");
6
6
  const ps_drag_config_1 = require("../constants/ps-drag-config");
7
- const usePsDragSensors = () => (0, core_1.useSensors)((0, core_1.useSensor)(core_1.MouseSensor, {
7
+ const usePsDragSensors = (coordinateGetter = sortable_1.sortableKeyboardCoordinates) => (0, core_1.useSensors)((0, core_1.useSensor)(core_1.MouseSensor, {
8
8
  activationConstraint: { distance: ps_drag_config_1.PsDragConfig.activationDistance },
9
9
  }), (0, core_1.useSensor)(core_1.TouchSensor, {
10
10
  activationConstraint: {
11
11
  delay: ps_drag_config_1.PsDragConfig.touchDelay,
12
12
  tolerance: ps_drag_config_1.PsDragConfig.touchTolerance,
13
13
  },
14
- }), (0, core_1.useSensor)(core_1.KeyboardSensor, { coordinateGetter: sortable_1.sortableKeyboardCoordinates }));
14
+ }), (0, core_1.useSensor)(core_1.KeyboardSensor, { coordinateGetter }));
15
15
  exports.usePsDragSensors = usePsDragSensors;
@@ -0,0 +1,2 @@
1
+ import type { PsDragTransition } from '../models/ps-drag-transition';
2
+ export declare const usePsDragTransition: () => PsDragTransition | null;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usePsDragTransition = void 0;
4
+ const ps_drag_config_1 = require("../constants/ps-drag-config");
5
+ const use_ps_reduced_motion_1 = require("./use-ps-reduced-motion");
6
+ const transition = {
7
+ duration: ps_drag_config_1.PsDragConfig.sortDuration,
8
+ easing: ps_drag_config_1.PsDragConfig.sortEasing,
9
+ };
10
+ const usePsDragTransition = () => {
11
+ const isReduced = (0, use_ps_reduced_motion_1.usePsReducedMotion)();
12
+ return isReduced ? null : transition;
13
+ };
14
+ exports.usePsDragTransition = usePsDragTransition;
@@ -2,20 +2,31 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.usePsOptimisticRows = void 0;
4
4
  const react_1 = require("react");
5
+ const ps_optimistic_config_1 = require("../constants/ps-optimistic-config");
6
+ const ps_optimistic_release_service_1 = require("../services/ps-optimistic-release-service");
5
7
  const usePsOptimisticRows = ({ apply, isHolding = false, isSaving, rows, }) => {
6
- const [remembered, setRemembered] = (0, react_1.useState)({
7
- base: rows,
8
- changes: [],
9
- });
8
+ const [remembered, setRemembered] = (0, react_1.useState)(() => ps_optimistic_release_service_1.PsOptimisticReleaseService.empty(rows));
10
9
  const isActive = isSaving || isHolding;
11
- const isStale = remembered.base !== rows && !isActive;
12
- if (isStale)
13
- setRemembered({ base: rows, changes: [] });
14
- const changes = isStale ? [] : remembered.changes;
10
+ const current = ps_optimistic_release_service_1.PsOptimisticReleaseService.next(remembered, rows, isActive, apply);
11
+ if (current !== remembered)
12
+ setRemembered(current);
13
+ const { changes } = current;
14
+ const isHeld = changes.length > 0 && !isActive;
15
15
  const shown = (0, react_1.useMemo)(() => (changes.length === 0 ? rows : apply(rows, changes)), [apply, changes, rows]);
16
- const remember = (0, react_1.useCallback)((change) => setRemembered((current) => ({ base: rows, changes: [...current.changes, change] })), [rows]);
17
- const forgetLast = (0, react_1.useCallback)(() => setRemembered((current) => ({ base: current.base, changes: current.changes.slice(0, -1) })), []);
18
- const rollback = (0, react_1.useCallback)(() => setRemembered({ base: rows, changes: [] }), [rows]);
16
+ (0, react_1.useEffect)(() => {
17
+ if (!isHeld)
18
+ return undefined;
19
+ const timer = setTimeout(() => setRemembered(ps_optimistic_release_service_1.PsOptimisticReleaseService.empty(rows)), ps_optimistic_config_1.PsOptimisticConfig.releaseTimeoutMs);
20
+ return () => clearTimeout(timer);
21
+ }, [isHeld, changes, rows]);
22
+ const remember = (0, react_1.useCallback)((change) => setRemembered((previous) => ({
23
+ base: rows,
24
+ changes: [...previous.changes, change],
25
+ drainBase: null,
26
+ wasActive: previous.wasActive,
27
+ })), [rows]);
28
+ const forgetLast = (0, react_1.useCallback)(() => setRemembered((previous) => ({ ...previous, changes: previous.changes.slice(0, -1) })), []);
29
+ const rollback = (0, react_1.useCallback)(() => setRemembered(ps_optimistic_release_service_1.PsOptimisticReleaseService.empty(rows)), [rows]);
19
30
  return { forgetLast, remember, rollback, rows: shown };
20
31
  };
21
32
  exports.usePsOptimisticRows = usePsOptimisticRows;
@@ -0,0 +1 @@
1
+ export declare const usePsPromiseValue: <Value>(promise: Promise<Value>, initial: Value) => Value;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usePsPromiseValue = void 0;
4
+ const react_1 = require("react");
5
+ const usePsPromiseValue = (promise, initial) => {
6
+ const [value, setValue] = (0, react_1.useState)(initial);
7
+ (0, react_1.useEffect)(() => {
8
+ let isCurrent = true;
9
+ void promise.then((resolved) => {
10
+ if (isCurrent)
11
+ setValue(resolved);
12
+ }, () => undefined);
13
+ return () => {
14
+ isCurrent = false;
15
+ };
16
+ }, [promise]);
17
+ return value;
18
+ };
19
+ exports.usePsPromiseValue = usePsPromiseValue;
@@ -0,0 +1,2 @@
1
+ import type { PsRetainedPromiseData } from '../interfaces/ps-retained-promise-data';
2
+ export declare const usePsRetainedPromise: <Value>(promise: Promise<Value>) => PsRetainedPromiseData<Value>;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usePsRetainedPromise = void 0;
4
+ const react_1 = require("react");
5
+ const usePsRetainedPromise = (promise) => {
6
+ const [state, setState] = (0, react_1.useState)({
7
+ hasFailed: false,
8
+ value: null,
9
+ });
10
+ (0, react_1.useEffect)(() => {
11
+ let isCurrent = true;
12
+ void promise.then((value) => {
13
+ if (isCurrent)
14
+ setState({ hasFailed: false, value });
15
+ }, () => {
16
+ if (isCurrent)
17
+ setState((previous) => ({ ...previous, hasFailed: true }));
18
+ });
19
+ return () => {
20
+ isCurrent = false;
21
+ };
22
+ }, [promise]);
23
+ return state;
24
+ };
25
+ exports.usePsRetainedPromise = usePsRetainedPromise;
package/dist/index.d.ts CHANGED
@@ -127,6 +127,12 @@ export * from './components/ps-active-filter-chip';
127
127
  export * from './components/ps-back-link';
128
128
  export * from './components/ps-card-block-skeleton';
129
129
  export * from './components/ps-data-boundary';
130
+ export * from './constants/ps-data-boundary-config';
131
+ export * from './constants/ps-data-boundary-variant';
132
+ export * from './hooks/use-ps-retained-promise';
133
+ export * from './hooks/use-ps-promise-value';
134
+ export * from './interfaces/ps-retained-promise-data';
135
+ export * from './services/ps-deep-equal-service';
130
136
  export * from './components/ps-faq-answer';
131
137
  export * from './components/ps-feature-grid';
132
138
  export * from './components/ps-feature-card';
@@ -188,6 +194,8 @@ export * from './models/ps-summary-item';
188
194
  export * from './components/ps-drag-grip';
189
195
  export * from './components/ps-drag-overlay';
190
196
  export * from './hooks/use-ps-drag-sensors';
197
+ export * from './hooks/use-ps-drag-transition';
198
+ export * from './models/ps-drag-transition';
191
199
  export * from './hooks/use-ps-list-keys';
192
200
  export * from './interfaces/ps-list-keys-result';
193
201
  export * from './services/ps-list-key-service';
package/dist/index.js CHANGED
@@ -143,6 +143,12 @@ __exportStar(require("./components/ps-active-filter-chip"), exports);
143
143
  __exportStar(require("./components/ps-back-link"), exports);
144
144
  __exportStar(require("./components/ps-card-block-skeleton"), exports);
145
145
  __exportStar(require("./components/ps-data-boundary"), exports);
146
+ __exportStar(require("./constants/ps-data-boundary-config"), exports);
147
+ __exportStar(require("./constants/ps-data-boundary-variant"), exports);
148
+ __exportStar(require("./hooks/use-ps-retained-promise"), exports);
149
+ __exportStar(require("./hooks/use-ps-promise-value"), exports);
150
+ __exportStar(require("./interfaces/ps-retained-promise-data"), exports);
151
+ __exportStar(require("./services/ps-deep-equal-service"), exports);
146
152
  __exportStar(require("./components/ps-faq-answer"), exports);
147
153
  __exportStar(require("./components/ps-feature-grid"), exports);
148
154
  __exportStar(require("./components/ps-feature-card"), exports);
@@ -204,6 +210,8 @@ __exportStar(require("./models/ps-summary-item"), exports);
204
210
  __exportStar(require("./components/ps-drag-grip"), exports);
205
211
  __exportStar(require("./components/ps-drag-overlay"), exports);
206
212
  __exportStar(require("./hooks/use-ps-drag-sensors"), exports);
213
+ __exportStar(require("./hooks/use-ps-drag-transition"), exports);
214
+ __exportStar(require("./models/ps-drag-transition"), exports);
207
215
  __exportStar(require("./hooks/use-ps-list-keys"), exports);
208
216
  __exportStar(require("./interfaces/ps-list-keys-result"), exports);
209
217
  __exportStar(require("./services/ps-list-key-service"), exports);
@@ -1,4 +1,6 @@
1
1
  export interface PsOptimisticChanges<Row, Change> {
2
2
  base: Row[];
3
3
  changes: Change[];
4
+ drainBase: Row[] | null;
5
+ wasActive: boolean;
4
6
  }
@@ -0,0 +1,4 @@
1
+ export interface PsRetainedPromiseData<Value> {
2
+ hasFailed: boolean;
3
+ value: Value | null;
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ export interface PsDragTransition {
2
+ duration: number;
3
+ easing: string;
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,5 @@
1
+ export declare class PsDeepEqualService {
2
+ static isEqual(left: unknown, right: unknown): boolean;
3
+ private static isArrayEqual;
4
+ private static isRecordEqual;
5
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsDeepEqualService = void 0;
4
+ class PsDeepEqualService {
5
+ static isEqual(left, right) {
6
+ if (Object.is(left, right))
7
+ return true;
8
+ if (typeof left !== 'object' || typeof right !== 'object')
9
+ return false;
10
+ if (left === null || right === null)
11
+ return false;
12
+ if (Array.isArray(left) || Array.isArray(right))
13
+ return PsDeepEqualService.isArrayEqual(left, right);
14
+ if (left instanceof Date || right instanceof Date) {
15
+ return left instanceof Date && right instanceof Date && left.getTime() === right.getTime();
16
+ }
17
+ return PsDeepEqualService.isRecordEqual(left, right);
18
+ }
19
+ static isArrayEqual(left, right) {
20
+ if (!Array.isArray(left) || !Array.isArray(right))
21
+ return false;
22
+ if (left.length !== right.length)
23
+ return false;
24
+ return left.every((item, index) => PsDeepEqualService.isEqual(item, right[index]));
25
+ }
26
+ static isRecordEqual(left, right) {
27
+ const leftEntries = Object.entries(left);
28
+ const rightRecord = { ...right };
29
+ if (leftEntries.length !== Object.keys(rightRecord).length)
30
+ return false;
31
+ return leftEntries.every(([key, value]) => Object.prototype.hasOwnProperty.call(rightRecord, key) &&
32
+ PsDeepEqualService.isEqual(value, rightRecord[key]));
33
+ }
34
+ }
35
+ exports.PsDeepEqualService = PsDeepEqualService;
@@ -0,0 +1,5 @@
1
+ import type { PsOptimisticChanges } from '../interfaces/ps-optimistic-changes';
2
+ export declare class PsOptimisticReleaseService {
3
+ static empty<Row, Change>(rows: Row[]): PsOptimisticChanges<Row, Change>;
4
+ static next<Row, Change>(remembered: PsOptimisticChanges<Row, Change>, rows: Row[], isActive: boolean, apply: (rows: Row[], changes: Change[]) => Row[]): PsOptimisticChanges<Row, Change>;
5
+ }
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsOptimisticReleaseService = void 0;
4
+ const ps_deep_equal_service_1 = require("./ps-deep-equal-service");
5
+ class PsOptimisticReleaseService {
6
+ static empty(rows) {
7
+ return { base: rows, changes: [], drainBase: null, wasActive: false };
8
+ }
9
+ static next(remembered, rows, isActive, apply) {
10
+ if (isActive) {
11
+ if (remembered.wasActive && remembered.drainBase === null)
12
+ return remembered;
13
+ return { ...remembered, drainBase: null, wasActive: true };
14
+ }
15
+ if (remembered.changes.length === 0) {
16
+ if (remembered.base === rows && !remembered.wasActive)
17
+ return remembered;
18
+ return PsOptimisticReleaseService.empty(rows);
19
+ }
20
+ if (remembered.wasActive) {
21
+ if (!ps_deep_equal_service_1.PsDeepEqualService.isEqual(rows, remembered.base)) {
22
+ return PsOptimisticReleaseService.empty(rows);
23
+ }
24
+ return { ...remembered, drainBase: rows, wasActive: false };
25
+ }
26
+ const reference = remembered.drainBase === null ? remembered.base : remembered.drainBase;
27
+ if (!ps_deep_equal_service_1.PsDeepEqualService.isEqual(rows, reference)) {
28
+ return PsOptimisticReleaseService.empty(rows);
29
+ }
30
+ if (ps_deep_equal_service_1.PsDeepEqualService.isEqual(apply(rows, remembered.changes), rows)) {
31
+ return PsOptimisticReleaseService.empty(rows);
32
+ }
33
+ return remembered;
34
+ }
35
+ }
36
+ exports.PsOptimisticReleaseService = PsOptimisticReleaseService;
@@ -1,5 +1,6 @@
1
1
  import { PsSkeletonShape } from '../constants/ps-skeleton-shape';
2
2
  export declare class PsSkeletonClassService {
3
3
  static resolve(shape: PsSkeletonShape, className?: string): string;
4
+ private static withoutRadius;
4
5
  private static withoutImportant;
5
6
  }
@@ -13,8 +13,18 @@ class PsSkeletonClassService {
13
13
  : [];
14
14
  const hasWidth = utilities.some((utility) => ps_skeleton_config_1.PsSkeletonConfig.widthUtilityPattern.test(utility));
15
15
  const hasHeight = utilities.some((utility) => ps_skeleton_config_1.PsSkeletonConfig.heightUtilityPattern.test(utility));
16
+ const hasRadius = utilities.some((utility) => ps_skeleton_config_1.PsSkeletonConfig.radiusUtilityPattern.test(utility));
16
17
  const size = ps_skeleton_config_1.PsSkeletonConfig.shapeSizes[shape];
17
- return ps_design_system_1.PallasoftClassNameService.merge(ps_skeleton_config_1.PsSkeletonConfig.baseClassName, ps_skeleton_config_1.PsSkeletonConfig.shapeClassNames[shape], hasHeight ? null : size.height, hasWidth ? null : size.width, className);
18
+ const shapeClassName = hasRadius
19
+ ? PsSkeletonClassService.withoutRadius(ps_skeleton_config_1.PsSkeletonConfig.shapeClassNames[shape])
20
+ : ps_skeleton_config_1.PsSkeletonConfig.shapeClassNames[shape];
21
+ return ps_design_system_1.PallasoftClassNameService.merge(ps_skeleton_config_1.PsSkeletonConfig.baseClassName, shapeClassName, hasHeight ? null : size.height, hasWidth ? null : size.width, className);
22
+ }
23
+ static withoutRadius(className) {
24
+ return className
25
+ .split(ps_skeleton_config_1.PsSkeletonConfig.classSeparator)
26
+ .filter((token) => !ps_skeleton_config_1.PsSkeletonConfig.radiusUtilityPattern.test(token))
27
+ .join(' ');
18
28
  }
19
29
  static withoutImportant(token) {
20
30
  return token.startsWith(ps_skeleton_config_1.PsSkeletonConfig.importantPrefix) ? token.slice(1) : token;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pallasoft/ps-core-components",
3
- "version": "2.44.2",
3
+ "version": "2.46.0",
4
4
  "private": false,
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/esm/index.js",