@pallasoft/ps-core-components 2.45.0 → 2.46.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) 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/constants/ps-data-boundary-config.d.ts +4 -0
  4. package/dist/constants/ps-data-boundary-config.js +7 -0
  5. package/dist/constants/ps-data-boundary-variant.d.ts +4 -0
  6. package/dist/constants/ps-data-boundary-variant.js +8 -0
  7. package/dist/constants/ps-optimistic-config.d.ts +1 -0
  8. package/dist/constants/ps-optimistic-config.js +1 -0
  9. package/dist/constants/ps-skeleton-config.d.ts +1 -0
  10. package/dist/constants/ps-skeleton-config.js +1 -0
  11. package/dist/esm/components/ps-data-boundary.js +14 -2
  12. package/dist/esm/constants/ps-data-boundary-config.js +4 -0
  13. package/dist/esm/constants/ps-data-boundary-variant.js +5 -0
  14. package/dist/esm/constants/ps-optimistic-config.js +1 -0
  15. package/dist/esm/constants/ps-skeleton-config.js +1 -0
  16. package/dist/esm/hooks/use-ps-optimistic-rows.js +24 -12
  17. package/dist/esm/hooks/use-ps-promise-value.js +15 -0
  18. package/dist/esm/hooks/use-ps-retained-promise.js +21 -0
  19. package/dist/esm/index.js +6 -0
  20. package/dist/esm/interfaces/ps-retained-promise-data.js +1 -0
  21. package/dist/esm/services/ps-deep-equal-service.js +31 -0
  22. package/dist/esm/services/ps-optimistic-release-service.js +38 -0
  23. package/dist/esm/services/ps-skeleton-class-service.js +11 -1
  24. package/dist/hooks/use-ps-optimistic-rows.js +23 -11
  25. package/dist/hooks/use-ps-promise-value.d.ts +1 -0
  26. package/dist/hooks/use-ps-promise-value.js +19 -0
  27. package/dist/hooks/use-ps-retained-promise.d.ts +2 -0
  28. package/dist/hooks/use-ps-retained-promise.js +25 -0
  29. package/dist/index.d.ts +6 -0
  30. package/dist/index.js +6 -0
  31. package/dist/interfaces/ps-optimistic-changes.d.ts +3 -0
  32. package/dist/interfaces/ps-retained-promise-data.d.ts +4 -0
  33. package/dist/interfaces/ps-retained-promise-data.js +2 -0
  34. package/dist/services/ps-deep-equal-service.d.ts +5 -0
  35. package/dist/services/ps-deep-equal-service.js +35 -0
  36. package/dist/services/ps-optimistic-release-service.d.ts +5 -0
  37. package/dist/services/ps-optimistic-release-service.js +42 -0
  38. package/dist/services/ps-skeleton-class-service.d.ts +1 -0
  39. package/dist/services/ps-skeleton-class-service.js +11 -1
  40. 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;
@@ -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 = {}));
@@ -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
+ };
@@ -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 = {}));
@@ -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,17 +1,29 @@
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, isHolding, 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
+ wasHolding: previous.wasHolding,
25
+ })), [rows]);
26
+ const forgetLast = useCallback(() => setRemembered((previous) => ({ ...previous, changes: previous.changes.slice(0, -1) })), []);
27
+ const rollback = useCallback(() => setRemembered(PsOptimisticReleaseService.empty(rows)), [rows]);
16
28
  return { forgetLast, remember, rollback, rows: shown };
17
29
  };
@@ -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';
@@ -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,38 @@
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, wasHolding: false };
5
+ }
6
+ static next(remembered, rows, isActive, isHolding, apply) {
7
+ if (isActive) {
8
+ const wasHolding = remembered.wasHolding || isHolding;
9
+ if (remembered.wasActive &&
10
+ remembered.drainBase === null &&
11
+ remembered.wasHolding === wasHolding)
12
+ return remembered;
13
+ return { ...remembered, drainBase: null, wasActive: true, wasHolding };
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 (remembered.wasHolding && rows !== remembered.base) {
22
+ return PsOptimisticReleaseService.empty(rows);
23
+ }
24
+ if (!PsDeepEqualService.isEqual(rows, remembered.base)) {
25
+ return PsOptimisticReleaseService.empty(rows);
26
+ }
27
+ return { ...remembered, drainBase: rows, wasActive: false, wasHolding: false };
28
+ }
29
+ const reference = remembered.drainBase === null ? remembered.base : remembered.drainBase;
30
+ if (!PsDeepEqualService.isEqual(rows, reference)) {
31
+ return PsOptimisticReleaseService.empty(rows);
32
+ }
33
+ if (PsDeepEqualService.isEqual(apply(rows, remembered.changes), rows)) {
34
+ return PsOptimisticReleaseService.empty(rows);
35
+ }
36
+ return remembered;
37
+ }
38
+ }
@@ -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;
@@ -2,20 +2,32 @@
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, isHolding, 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
+ wasHolding: previous.wasHolding,
28
+ })), [rows]);
29
+ const forgetLast = (0, react_1.useCallback)(() => setRemembered((previous) => ({ ...previous, changes: previous.changes.slice(0, -1) })), []);
30
+ const rollback = (0, react_1.useCallback)(() => setRemembered(ps_optimistic_release_service_1.PsOptimisticReleaseService.empty(rows)), [rows]);
19
31
  return { forgetLast, remember, rollback, rows: shown };
20
32
  };
21
33
  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';
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);
@@ -1,4 +1,7 @@
1
1
  export interface PsOptimisticChanges<Row, Change> {
2
2
  base: Row[];
3
3
  changes: Change[];
4
+ drainBase: Row[] | null;
5
+ wasActive: boolean;
6
+ wasHolding: boolean;
4
7
  }
@@ -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,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, isHolding: boolean, apply: (rows: Row[], changes: Change[]) => Row[]): PsOptimisticChanges<Row, Change>;
5
+ }
@@ -0,0 +1,42 @@
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, wasHolding: false };
8
+ }
9
+ static next(remembered, rows, isActive, isHolding, apply) {
10
+ if (isActive) {
11
+ const wasHolding = remembered.wasHolding || isHolding;
12
+ if (remembered.wasActive &&
13
+ remembered.drainBase === null &&
14
+ remembered.wasHolding === wasHolding)
15
+ return remembered;
16
+ return { ...remembered, drainBase: null, wasActive: true, wasHolding };
17
+ }
18
+ if (remembered.changes.length === 0) {
19
+ if (remembered.base === rows && !remembered.wasActive)
20
+ return remembered;
21
+ return PsOptimisticReleaseService.empty(rows);
22
+ }
23
+ if (remembered.wasActive) {
24
+ if (remembered.wasHolding && rows !== remembered.base) {
25
+ return PsOptimisticReleaseService.empty(rows);
26
+ }
27
+ if (!ps_deep_equal_service_1.PsDeepEqualService.isEqual(rows, remembered.base)) {
28
+ return PsOptimisticReleaseService.empty(rows);
29
+ }
30
+ return { ...remembered, drainBase: rows, wasActive: false, wasHolding: false };
31
+ }
32
+ const reference = remembered.drainBase === null ? remembered.base : remembered.drainBase;
33
+ if (!ps_deep_equal_service_1.PsDeepEqualService.isEqual(rows, reference)) {
34
+ return PsOptimisticReleaseService.empty(rows);
35
+ }
36
+ if (ps_deep_equal_service_1.PsDeepEqualService.isEqual(apply(rows, remembered.changes), rows)) {
37
+ return PsOptimisticReleaseService.empty(rows);
38
+ }
39
+ return remembered;
40
+ }
41
+ }
42
+ 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.45.0",
3
+ "version": "2.46.1",
4
4
  "private": false,
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/esm/index.js",