@pallasoft/ps-core-components 2.42.0 → 2.44.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 (90) hide show
  1. package/dist/components/ps-button.js +3 -1
  2. package/dist/components/ps-reflective-app-shell.d.ts +3 -1
  3. package/dist/components/ps-reflective-app-shell.js +10 -3
  4. package/dist/components/ps-scroll-restoration.d.ts +6 -0
  5. package/dist/components/ps-scroll-restoration.js +9 -0
  6. package/dist/components/ps-skeleton-table.d.ts +3 -1
  7. package/dist/components/ps-skeleton-table.js +15 -2
  8. package/dist/constants/ps-flag-param-value.d.ts +3 -0
  9. package/dist/constants/ps-flag-param-value.js +6 -0
  10. package/dist/constants/ps-query-param-config.d.ts +4 -0
  11. package/dist/constants/ps-query-param-config.js +6 -0
  12. package/dist/constants/ps-rerender-config.d.ts +3 -0
  13. package/dist/constants/ps-rerender-config.js +6 -0
  14. package/dist/constants/ps-scroll-restoration-config.d.ts +10 -0
  15. package/dist/constants/ps-scroll-restoration-config.js +13 -0
  16. package/dist/constants/ps-skeleton-config.d.ts +2 -0
  17. package/dist/constants/ps-skeleton-config.js +2 -0
  18. package/dist/esm/components/ps-button.js +3 -1
  19. package/dist/esm/components/ps-reflective-app-shell.js +10 -3
  20. package/dist/esm/components/ps-scroll-restoration.js +5 -0
  21. package/dist/esm/components/ps-skeleton-table.js +15 -2
  22. package/dist/esm/constants/ps-flag-param-value.js +3 -0
  23. package/dist/esm/constants/ps-query-param-config.js +3 -0
  24. package/dist/esm/constants/ps-rerender-config.js +3 -0
  25. package/dist/esm/constants/ps-scroll-restoration-config.js +10 -0
  26. package/dist/esm/constants/ps-skeleton-config.js +2 -0
  27. package/dist/esm/hooks/use-ps-browser-layout-effect.js +2 -0
  28. package/dist/esm/hooks/use-ps-choice-param.js +6 -0
  29. package/dist/esm/hooks/use-ps-flag-param.js +6 -0
  30. package/dist/esm/hooks/use-ps-rerender-at.js +21 -0
  31. package/dist/esm/hooks/use-ps-scroll-restoration.js +103 -0
  32. package/dist/esm/index.js +25 -0
  33. package/dist/esm/models/ps-choice-param-data.js +1 -0
  34. package/dist/esm/models/ps-choice-param-options.js +1 -0
  35. package/dist/esm/models/ps-flag-param-data.js +1 -0
  36. package/dist/esm/models/ps-flag-param-options.js +1 -0
  37. package/dist/esm/models/ps-scroll-offsets-data.js +1 -0
  38. package/dist/esm/models/ps-scroll-pending-save-data.js +1 -0
  39. package/dist/esm/models/ps-scroll-restoration-options.js +1 -0
  40. package/dist/esm/models/ps-search-params-set-options.js +1 -0
  41. package/dist/esm/models/ps-search-params-setter.js +1 -0
  42. package/dist/esm/models/ps-search-params-state.js +1 -0
  43. package/dist/esm/models/ps-skeleton-table-row.js +1 -0
  44. package/dist/esm/services/ps-query-param-service.js +32 -0
  45. package/dist/esm/services/ps-scroll-restore-service.js +52 -0
  46. package/dist/esm/services/ps-scroll-storage-service.js +66 -0
  47. package/dist/esm/services/ps-scroll-target-service.js +29 -0
  48. package/dist/hooks/use-ps-browser-layout-effect.d.ts +2 -0
  49. package/dist/hooks/use-ps-browser-layout-effect.js +5 -0
  50. package/dist/hooks/use-ps-choice-param.d.ts +4 -0
  51. package/dist/hooks/use-ps-choice-param.js +10 -0
  52. package/dist/hooks/use-ps-flag-param.d.ts +4 -0
  53. package/dist/hooks/use-ps-flag-param.js +10 -0
  54. package/dist/hooks/use-ps-rerender-at.d.ts +1 -0
  55. package/dist/hooks/use-ps-rerender-at.js +25 -0
  56. package/dist/hooks/use-ps-scroll-restoration.d.ts +2 -0
  57. package/dist/hooks/use-ps-scroll-restoration.js +107 -0
  58. package/dist/index.d.ts +25 -0
  59. package/dist/index.js +25 -0
  60. package/dist/models/ps-choice-param-data.d.ts +4 -0
  61. package/dist/models/ps-choice-param-data.js +2 -0
  62. package/dist/models/ps-choice-param-options.d.ts +6 -0
  63. package/dist/models/ps-choice-param-options.js +2 -0
  64. package/dist/models/ps-flag-param-data.d.ts +4 -0
  65. package/dist/models/ps-flag-param-data.js +2 -0
  66. package/dist/models/ps-flag-param-options.d.ts +4 -0
  67. package/dist/models/ps-flag-param-options.js +2 -0
  68. package/dist/models/ps-scroll-offsets-data.d.ts +1 -0
  69. package/dist/models/ps-scroll-offsets-data.js +2 -0
  70. package/dist/models/ps-scroll-pending-save-data.d.ts +4 -0
  71. package/dist/models/ps-scroll-pending-save-data.js +2 -0
  72. package/dist/models/ps-scroll-restoration-options.d.ts +7 -0
  73. package/dist/models/ps-scroll-restoration-options.js +2 -0
  74. package/dist/models/ps-search-params-set-options.d.ts +4 -0
  75. package/dist/models/ps-search-params-set-options.js +2 -0
  76. package/dist/models/ps-search-params-setter.d.ts +2 -0
  77. package/dist/models/ps-search-params-setter.js +2 -0
  78. package/dist/models/ps-search-params-state.d.ts +2 -0
  79. package/dist/models/ps-search-params-state.js +2 -0
  80. package/dist/models/ps-skeleton-table-row.d.ts +4 -0
  81. package/dist/models/ps-skeleton-table-row.js +2 -0
  82. package/dist/services/ps-query-param-service.d.ts +7 -0
  83. package/dist/services/ps-query-param-service.js +36 -0
  84. package/dist/services/ps-scroll-restore-service.d.ts +3 -0
  85. package/dist/services/ps-scroll-restore-service.js +56 -0
  86. package/dist/services/ps-scroll-storage-service.d.ts +11 -0
  87. package/dist/services/ps-scroll-storage-service.js +70 -0
  88. package/dist/services/ps-scroll-target-service.d.ts +8 -0
  89. package/dist/services/ps-scroll-target-service.js +33 -0
  90. package/package.json +1 -1
@@ -5,5 +5,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const ps_design_system_1 = require("@pallasoft/ps-design-system");
6
6
  const ps_button_classes_1 = require("../constants/ps-button-classes");
7
7
  const ps_spinner_1 = require("./ps-spinner");
8
- const PsButton = ({ children, className, disabled, isLabelHiddenOnMobile = false, isLoading = false, leadingIcon, loadingLabel, size = 'default', trailingIcon, type = 'button', variant = 'secondary', ...props }) => ((0, jsx_runtime_1.jsxs)("button", { className: ps_design_system_1.PallasoftClassNameService.merge(ps_button_classes_1.PsButtonBaseClassName, 'disabled:cursor-not-allowed disabled:opacity-50', ps_button_classes_1.PsButtonSizeClassNames[size], isLabelHiddenOnMobile && leadingIcon ? 'min-w-11 max-sm:px-0' : '', ps_button_classes_1.PsButtonVariantClassNames[variant], className), "aria-busy": isLoading || undefined, disabled: disabled || isLoading, type: type, ...props, children: [isLoading ? ((0, jsx_runtime_1.jsx)(ps_spinner_1.PsSpinner, { className: 'absolute inset-0 m-auto', color: 'current', label: loadingLabel !== null && loadingLabel !== undefined ? loadingLabel : 'Loading', size: 'small' })) : null, (0, jsx_runtime_1.jsxs)("span", { "aria-hidden": isLoading || undefined, className: ps_design_system_1.PallasoftClassNameService.merge('inline-flex min-w-0 items-center gap-2', isLoading ? 'invisible' : ''), children: [leadingIcon ? (0, jsx_runtime_1.jsx)("span", { className: 'shrink-0', children: leadingIcon }) : null, (0, jsx_runtime_1.jsx)("span", { className: isLabelHiddenOnMobile && leadingIcon ? 'sr-only sm:not-sr-only sm:truncate' : 'truncate', children: children }), trailingIcon ? (0, jsx_runtime_1.jsx)("span", { className: 'shrink-0', children: trailingIcon }) : null] })] }));
8
+ const PsButton = ({ children, className, disabled, isLabelHiddenOnMobile = false, isLoading = false, leadingIcon, loadingLabel, size = 'default', trailingIcon, type = 'button', variant = 'secondary', ...props }) => ((0, jsx_runtime_1.jsxs)("button", { className: ps_design_system_1.PallasoftClassNameService.merge(ps_button_classes_1.PsButtonBaseClassName, isLoading && disabled !== true
9
+ ? 'cursor-progress'
10
+ : 'disabled:cursor-not-allowed disabled:opacity-50', ps_button_classes_1.PsButtonSizeClassNames[size], isLabelHiddenOnMobile && leadingIcon ? 'min-w-11 max-sm:px-0' : '', ps_button_classes_1.PsButtonVariantClassNames[variant], className), "aria-busy": isLoading || undefined, disabled: disabled || isLoading, type: type, ...props, children: [isLoading ? ((0, jsx_runtime_1.jsx)(ps_spinner_1.PsSpinner, { className: 'absolute inset-0 m-auto', color: 'current', label: loadingLabel !== null && loadingLabel !== undefined ? loadingLabel : 'Loading', size: 'small' })) : null, (0, jsx_runtime_1.jsxs)("span", { "aria-hidden": isLoading || undefined, className: ps_design_system_1.PallasoftClassNameService.merge('inline-flex min-w-0 items-center gap-2', isLoading ? 'invisible' : ''), children: [leadingIcon ? (0, jsx_runtime_1.jsx)("span", { className: 'shrink-0', children: leadingIcon }) : null, (0, jsx_runtime_1.jsx)("span", { className: isLabelHiddenOnMobile && leadingIcon ? 'sr-only sm:not-sr-only sm:truncate' : 'truncate', children: children }), trailingIcon ? (0, jsx_runtime_1.jsx)("span", { className: 'shrink-0', children: trailingIcon }) : null] })] }));
9
11
  exports.PsButton = PsButton;
@@ -4,9 +4,11 @@ export interface PsReflectiveAppShellProps {
4
4
  className?: string;
5
5
  contentClassName?: string;
6
6
  frameClassName?: string;
7
+ scrollPreventReset?: boolean;
7
8
  scrollResetKey?: string;
9
+ scrollRestoreKey?: string;
8
10
  sidebar: ReactNode;
9
11
  sidebarWidth?: string;
10
12
  topbar: ReactNode;
11
13
  }
12
- export declare const PsReflectiveAppShell: ({ children, className, contentClassName, frameClassName, scrollResetKey, sidebar, sidebarWidth, topbar, }: PsReflectiveAppShellProps) => import("react").JSX.Element;
14
+ export declare const PsReflectiveAppShell: ({ children, className, contentClassName, frameClassName, scrollPreventReset, scrollResetKey, scrollRestoreKey, sidebar, sidebarWidth, topbar, }: PsReflectiveAppShellProps) => import("react").JSX.Element;
@@ -4,14 +4,21 @@ exports.PsReflectiveAppShell = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
6
  const ps_design_system_1 = require("@pallasoft/ps-design-system");
7
- const PsReflectiveAppShell = ({ children, className, contentClassName, frameClassName, scrollResetKey, sidebar, sidebarWidth = '17rem', topbar, }) => {
7
+ const use_ps_scroll_restoration_1 = require("../hooks/use-ps-scroll-restoration");
8
+ const PsReflectiveAppShell = ({ children, className, contentClassName, frameClassName, scrollPreventReset, scrollResetKey, scrollRestoreKey, sidebar, sidebarWidth = '17rem', topbar, }) => {
8
9
  const contentRef = (0, react_1.useRef)(null);
10
+ (0, use_ps_scroll_restoration_1.usePsScrollRestoration)({
11
+ elementRef: contentRef,
12
+ preventScrollReset: scrollPreventReset,
13
+ resetKey: scrollResetKey,
14
+ restoreKey: scrollRestoreKey,
15
+ });
9
16
  (0, react_1.useLayoutEffect)(() => {
10
- if (scrollResetKey === undefined)
17
+ if (scrollResetKey === undefined || scrollRestoreKey !== undefined)
11
18
  return;
12
19
  contentRef.current?.scrollTo({ top: 0, left: 0 });
13
20
  window.scrollTo({ top: 0, left: 0 });
14
- }, [scrollResetKey]);
21
+ }, [scrollResetKey, scrollRestoreKey]);
15
22
  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
23
  };
17
24
  exports.PsReflectiveAppShell = PsReflectiveAppShell;
@@ -0,0 +1,6 @@
1
+ export interface PsScrollRestorationProps {
2
+ preventScrollReset?: boolean;
3
+ resetKey?: string;
4
+ restoreKey: string;
5
+ }
6
+ export declare const PsScrollRestoration: ({ preventScrollReset, resetKey, restoreKey, }: PsScrollRestorationProps) => null;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsScrollRestoration = void 0;
4
+ const use_ps_scroll_restoration_1 = require("../hooks/use-ps-scroll-restoration");
5
+ const PsScrollRestoration = ({ preventScrollReset, resetKey, restoreKey, }) => {
6
+ (0, use_ps_scroll_restoration_1.usePsScrollRestoration)({ preventScrollReset, resetKey, restoreKey });
7
+ return null;
8
+ };
9
+ exports.PsScrollRestoration = PsScrollRestoration;
@@ -1,3 +1,4 @@
1
+ import type { PsSkeletonTableRow } from '../models/ps-skeleton-table-row';
1
2
  export interface PsSkeletonTableProps {
2
3
  alignments?: ('left' | 'right')[];
3
4
  className?: string;
@@ -5,6 +6,7 @@ export interface PsSkeletonTableProps {
5
6
  hasFooter?: boolean;
6
7
  hasToolbar?: boolean;
7
8
  isFramed?: boolean;
9
+ rowLayout?: PsSkeletonTableRow[];
8
10
  rows?: number;
9
11
  }
10
- export declare const PsSkeletonTable: ({ alignments, className, columns, hasFooter, hasToolbar, isFramed, rows, }: PsSkeletonTableProps) => import("react").JSX.Element;
12
+ export declare const PsSkeletonTable: ({ alignments, className, columns, hasFooter, hasToolbar, isFramed, rowLayout, rows, }: PsSkeletonTableProps) => import("react").JSX.Element;
@@ -7,7 +7,8 @@ const ps_skeleton_config_1 = require("../constants/ps-skeleton-config");
7
7
  const ps_skeleton_shape_1 = require("../constants/ps-skeleton-shape");
8
8
  const ps_skeleton_1 = require("./ps-skeleton");
9
9
  const ps_skeleton_group_1 = require("./ps-skeleton-group");
10
- const PsSkeletonTable = ({ alignments, className, columns = 4, hasFooter = false, hasToolbar = false, isFramed = true, rows = 6, }) => {
10
+ const PsSkeletonTable = ({ alignments, className, columns = 4, hasFooter = false, hasToolbar = false, isFramed = true, rowLayout, rows = 6, }) => {
11
+ const layout = rowLayout === undefined ? Array.from({ length: rows }, () => ({})) : rowLayout;
11
12
  const gridTemplateColumns = `minmax(0, 2fr) repeat(${Math.max(columns - 1, 0)}, minmax(0, 1fr))`;
12
13
  const isRight = (columnIndex) => alignments !== undefined
13
14
  ? alignments[columnIndex] === 'right'
@@ -17,6 +18,18 @@ const PsSkeletonTable = ({ alignments, className, columns = 4, hasFooter = false
17
18
  : ps_skeleton_config_1.PsSkeletonConfig.lineWidths[(rowIndex + columnIndex) % ps_skeleton_config_1.PsSkeletonConfig.lineWidths.length], isStrong ? 'h-3.5' : '') }, columnIndex));
18
19
  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
20
  ? ps_design_system_1.PallasoftClassNameService.merge('overflow-hidden border border-line bg-surface', ps_skeleton_config_1.PsSkeletonConfig.tableFrameClassName)
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
+ : '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))) }), layout.map((row, rowIndex) => {
22
+ const isSection = row.isSection === true;
23
+ const depth = row.depth === undefined ? 0 : row.depth;
24
+ return ((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) => {
25
+ if (isSection && columnIndex > 0 && !isRight(columnIndex))
26
+ return (0, jsx_runtime_1.jsx)("span", {}, columnIndex);
27
+ if (columnIndex > 0 || (depth === 0 && !isSection))
28
+ return cell(rowIndex, columnIndex, isSection);
29
+ return ((0, jsx_runtime_1.jsxs)("div", { className: 'flex min-w-0 items-center gap-3', style: {
30
+ paddingInlineStart: `${depth * ps_skeleton_config_1.PsSkeletonConfig.tableIndentRem}rem`,
31
+ }, children: [isSection ? ((0, jsx_runtime_1.jsx)(ps_skeleton_1.PsSkeleton, { className: ps_skeleton_config_1.PsSkeletonConfig.tableSectionMarkerClassName })) : null, cell(rowIndex, columnIndex, isSection)] }, columnIndex));
32
+ }) }, rowIndex));
33
+ }), 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(layout.length, columnIndex, true)) })) : null] })] }));
21
34
  };
22
35
  exports.PsSkeletonTable = PsSkeletonTable;
@@ -0,0 +1,3 @@
1
+ export declare const PsFlagParamValue: {
2
+ readonly on: "1";
3
+ };
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsFlagParamValue = void 0;
4
+ exports.PsFlagParamValue = {
5
+ on: '1',
6
+ };
@@ -0,0 +1,4 @@
1
+ import type { PsSearchParamsSetOptions } from '../models/ps-search-params-set-options';
2
+ export declare const PsQueryParamConfig: {
3
+ setOptions: PsSearchParamsSetOptions;
4
+ };
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsQueryParamConfig = void 0;
4
+ exports.PsQueryParamConfig = {
5
+ setOptions: { preventScrollReset: true, replace: true },
6
+ };
@@ -0,0 +1,3 @@
1
+ export declare const PsRerenderConfig: {
2
+ readonly maxTimerMs: 2147483647;
3
+ };
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsRerenderConfig = void 0;
4
+ exports.PsRerenderConfig = {
5
+ maxTimerMs: 2147483647,
6
+ };
@@ -0,0 +1,10 @@
1
+ export declare const PsScrollRestorationConfig: {
2
+ readonly elementScope: "element:";
3
+ readonly inputEvents: readonly ["wheel", "touchstart", "keydown", "mousedown"];
4
+ readonly maxEntries: 100;
5
+ readonly persistThrottleMs: 150;
6
+ readonly restoreTimeoutMs: 4000;
7
+ readonly storageKey: "ps-scroll-restoration";
8
+ readonly tolerancePx: 2;
9
+ readonly windowScope: "window:";
10
+ };
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsScrollRestorationConfig = void 0;
4
+ exports.PsScrollRestorationConfig = {
5
+ elementScope: 'element:',
6
+ inputEvents: ['wheel', 'touchstart', 'keydown', 'mousedown'],
7
+ maxEntries: 100,
8
+ persistThrottleMs: 150,
9
+ restoreTimeoutMs: 4000,
10
+ storageKey: 'ps-scroll-restoration',
11
+ tolerancePx: 2,
12
+ windowScope: 'window:',
13
+ };
@@ -14,6 +14,8 @@ export declare const PsSkeletonConfig: {
14
14
  readonly tabWidthClassName: "w-24";
15
15
  readonly tabCount: 4;
16
16
  readonly tableFrameClassName: "rounded-[0.875rem]";
17
+ readonly tableIndentRem: 1.25;
18
+ readonly tableSectionMarkerClassName: "h-5 w-5 shrink-0 rounded-md";
17
19
  readonly shapeClassNames: Record<PsSkeletonShape, string>;
18
20
  readonly shapeSizes: Record<PsSkeletonShape, PsSkeletonSize>;
19
21
  };
@@ -20,6 +20,8 @@ exports.PsSkeletonConfig = {
20
20
  tabWidthClassName: 'w-24',
21
21
  tabCount: 4,
22
22
  tableFrameClassName: 'rounded-[0.875rem]',
23
+ tableIndentRem: 1.25,
24
+ tableSectionMarkerClassName: 'h-5 w-5 shrink-0 rounded-md',
23
25
  shapeClassNames: {
24
26
  [ps_skeleton_shape_1.PsSkeletonShape.Badge]: controlRadius,
25
27
  [ps_skeleton_shape_1.PsSkeletonShape.Block]: controlRadius,
@@ -2,4 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
3
3
  import { PsButtonBaseClassName, PsButtonSizeClassNames, PsButtonVariantClassNames, } from '../constants/ps-button-classes.js';
4
4
  import { PsSpinner } from './ps-spinner.js';
5
- export const PsButton = ({ children, className, disabled, isLabelHiddenOnMobile = false, isLoading = false, leadingIcon, loadingLabel, size = 'default', trailingIcon, type = 'button', variant = 'secondary', ...props }) => (_jsxs("button", { className: PallasoftClassNameService.merge(PsButtonBaseClassName, 'disabled:cursor-not-allowed disabled:opacity-50', PsButtonSizeClassNames[size], isLabelHiddenOnMobile && leadingIcon ? 'min-w-11 max-sm:px-0' : '', PsButtonVariantClassNames[variant], className), "aria-busy": isLoading || undefined, disabled: disabled || isLoading, type: type, ...props, children: [isLoading ? (_jsx(PsSpinner, { className: 'absolute inset-0 m-auto', color: 'current', label: loadingLabel !== null && loadingLabel !== undefined ? loadingLabel : 'Loading', size: 'small' })) : null, _jsxs("span", { "aria-hidden": isLoading || undefined, className: PallasoftClassNameService.merge('inline-flex min-w-0 items-center gap-2', isLoading ? 'invisible' : ''), children: [leadingIcon ? _jsx("span", { className: 'shrink-0', children: leadingIcon }) : null, _jsx("span", { className: isLabelHiddenOnMobile && leadingIcon ? 'sr-only sm:not-sr-only sm:truncate' : 'truncate', children: children }), trailingIcon ? _jsx("span", { className: 'shrink-0', children: trailingIcon }) : null] })] }));
5
+ export const PsButton = ({ children, className, disabled, isLabelHiddenOnMobile = false, isLoading = false, leadingIcon, loadingLabel, size = 'default', trailingIcon, type = 'button', variant = 'secondary', ...props }) => (_jsxs("button", { className: PallasoftClassNameService.merge(PsButtonBaseClassName, isLoading && disabled !== true
6
+ ? 'cursor-progress'
7
+ : 'disabled:cursor-not-allowed disabled:opacity-50', PsButtonSizeClassNames[size], isLabelHiddenOnMobile && leadingIcon ? 'min-w-11 max-sm:px-0' : '', PsButtonVariantClassNames[variant], className), "aria-busy": isLoading || undefined, disabled: disabled || isLoading, type: type, ...props, children: [isLoading ? (_jsx(PsSpinner, { className: 'absolute inset-0 m-auto', color: 'current', label: loadingLabel !== null && loadingLabel !== undefined ? loadingLabel : 'Loading', size: 'small' })) : null, _jsxs("span", { "aria-hidden": isLoading || undefined, className: PallasoftClassNameService.merge('inline-flex min-w-0 items-center gap-2', isLoading ? 'invisible' : ''), children: [leadingIcon ? _jsx("span", { className: 'shrink-0', children: leadingIcon }) : null, _jsx("span", { className: isLabelHiddenOnMobile && leadingIcon ? 'sr-only sm:not-sr-only sm:truncate' : 'truncate', children: children }), trailingIcon ? _jsx("span", { className: 'shrink-0', children: trailingIcon }) : null] })] }));
@@ -1,13 +1,20 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useLayoutEffect, useRef } from 'react';
3
3
  import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
4
- export const PsReflectiveAppShell = ({ children, className, contentClassName, frameClassName, scrollResetKey, sidebar, sidebarWidth = '17rem', topbar, }) => {
4
+ import { usePsScrollRestoration } from '../hooks/use-ps-scroll-restoration.js';
5
+ export const PsReflectiveAppShell = ({ children, className, contentClassName, frameClassName, scrollPreventReset, scrollResetKey, scrollRestoreKey, sidebar, sidebarWidth = '17rem', topbar, }) => {
5
6
  const contentRef = useRef(null);
7
+ usePsScrollRestoration({
8
+ elementRef: contentRef,
9
+ preventScrollReset: scrollPreventReset,
10
+ resetKey: scrollResetKey,
11
+ restoreKey: scrollRestoreKey,
12
+ });
6
13
  useLayoutEffect(() => {
7
- if (scrollResetKey === undefined)
14
+ if (scrollResetKey === undefined || scrollRestoreKey !== undefined)
8
15
  return;
9
16
  contentRef.current?.scrollTo({ top: 0, left: 0 });
10
17
  window.scrollTo({ top: 0, left: 0 });
11
- }, [scrollResetKey]);
18
+ }, [scrollResetKey, scrollRestoreKey]);
12
19
  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
20
  };
@@ -0,0 +1,5 @@
1
+ import { usePsScrollRestoration } from '../hooks/use-ps-scroll-restoration.js';
2
+ export const PsScrollRestoration = ({ preventScrollReset, resetKey, restoreKey, }) => {
3
+ usePsScrollRestoration({ preventScrollReset, resetKey, restoreKey });
4
+ return null;
5
+ };
@@ -4,7 +4,8 @@ import { PsSkeletonConfig } from '../constants/ps-skeleton-config.js';
4
4
  import { PsSkeletonShape } from '../constants/ps-skeleton-shape.js';
5
5
  import { PsSkeleton } from './ps-skeleton.js';
6
6
  import { PsSkeletonGroup } from './ps-skeleton-group.js';
7
- export const PsSkeletonTable = ({ alignments, className, columns = 4, hasFooter = false, hasToolbar = false, isFramed = true, rows = 6, }) => {
7
+ export const PsSkeletonTable = ({ alignments, className, columns = 4, hasFooter = false, hasToolbar = false, isFramed = true, rowLayout, rows = 6, }) => {
8
+ const layout = rowLayout === undefined ? Array.from({ length: rows }, () => ({})) : rowLayout;
8
9
  const gridTemplateColumns = `minmax(0, 2fr) repeat(${Math.max(columns - 1, 0)}, minmax(0, 1fr))`;
9
10
  const isRight = (columnIndex) => alignments !== undefined
10
11
  ? alignments[columnIndex] === 'right'
@@ -14,5 +15,17 @@ export const PsSkeletonTable = ({ alignments, className, columns = 4, hasFooter
14
15
  : PsSkeletonConfig.lineWidths[(rowIndex + columnIndex) % PsSkeletonConfig.lineWidths.length], isStrong ? 'h-3.5' : '') }, columnIndex));
15
16
  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
17
  ? PallasoftClassNameService.merge('overflow-hidden border border-line bg-surface', PsSkeletonConfig.tableFrameClassName)
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
+ : '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))) }), layout.map((row, rowIndex) => {
19
+ const isSection = row.isSection === true;
20
+ const depth = row.depth === undefined ? 0 : row.depth;
21
+ return (_jsx("div", { className: 'grid items-center gap-4 border-t border-line px-5 py-4', style: { gridTemplateColumns }, children: Array.from({ length: columns }, (__, columnIndex) => {
22
+ if (isSection && columnIndex > 0 && !isRight(columnIndex))
23
+ return _jsx("span", {}, columnIndex);
24
+ if (columnIndex > 0 || (depth === 0 && !isSection))
25
+ return cell(rowIndex, columnIndex, isSection);
26
+ return (_jsxs("div", { className: 'flex min-w-0 items-center gap-3', style: {
27
+ paddingInlineStart: `${depth * PsSkeletonConfig.tableIndentRem}rem`,
28
+ }, children: [isSection ? (_jsx(PsSkeleton, { className: PsSkeletonConfig.tableSectionMarkerClassName })) : null, cell(rowIndex, columnIndex, isSection)] }, columnIndex));
29
+ }) }, rowIndex));
30
+ }), 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(layout.length, columnIndex, true)) })) : null] })] }));
18
31
  };
@@ -0,0 +1,3 @@
1
+ export const PsFlagParamValue = {
2
+ on: '1',
3
+ };
@@ -0,0 +1,3 @@
1
+ export const PsQueryParamConfig = {
2
+ setOptions: { preventScrollReset: true, replace: true },
3
+ };
@@ -0,0 +1,3 @@
1
+ export const PsRerenderConfig = {
2
+ maxTimerMs: 2147483647,
3
+ };
@@ -0,0 +1,10 @@
1
+ export const PsScrollRestorationConfig = {
2
+ elementScope: 'element:',
3
+ inputEvents: ['wheel', 'touchstart', 'keydown', 'mousedown'],
4
+ maxEntries: 100,
5
+ persistThrottleMs: 150,
6
+ restoreTimeoutMs: 4000,
7
+ storageKey: 'ps-scroll-restoration',
8
+ tolerancePx: 2,
9
+ windowScope: 'window:',
10
+ };
@@ -17,6 +17,8 @@ export const PsSkeletonConfig = {
17
17
  tabWidthClassName: 'w-24',
18
18
  tabCount: 4,
19
19
  tableFrameClassName: 'rounded-[0.875rem]',
20
+ tableIndentRem: 1.25,
21
+ tableSectionMarkerClassName: 'h-5 w-5 shrink-0 rounded-md',
20
22
  shapeClassNames: {
21
23
  [PsSkeletonShape.Badge]: controlRadius,
22
24
  [PsSkeletonShape.Block]: controlRadius,
@@ -0,0 +1,2 @@
1
+ import { useEffect, useLayoutEffect } from 'react';
2
+ export const usePsBrowserLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
@@ -0,0 +1,6 @@
1
+ import { PsQueryParamConfig } from '../constants/ps-query-param-config.js';
2
+ import { PsQueryParamService } from '../services/ps-query-param-service.js';
3
+ export const usePsChoiceParam = ([searchParams, setSearchParams], { fallback, name, options, resetParams }) => ({
4
+ onChange: (value) => setSearchParams((current) => PsQueryParamService.withChoice(current, name, value, fallback, resetParams), PsQueryParamConfig.setOptions),
5
+ value: PsQueryParamService.choice(searchParams.get(name), options, fallback),
6
+ });
@@ -0,0 +1,6 @@
1
+ import { PsQueryParamConfig } from '../constants/ps-query-param-config.js';
2
+ import { PsQueryParamService } from '../services/ps-query-param-service.js';
3
+ export const usePsFlagParam = ([searchParams, setSearchParams], { name, resetParams }) => ({
4
+ isOn: PsQueryParamService.flag(searchParams.get(name)),
5
+ onChange: (isOn) => setSearchParams((current) => PsQueryParamService.withFlag(current, name, isOn, resetParams), PsQueryParamConfig.setOptions),
6
+ });
@@ -0,0 +1,21 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { PsRerenderConfig } from '../constants/ps-rerender-config.js';
3
+ export const usePsRerenderAt = (atMs) => {
4
+ const [, setTick] = useState(0);
5
+ useEffect(() => {
6
+ if (atMs === null || typeof window === 'undefined')
7
+ return;
8
+ let timer = 0;
9
+ const arm = () => {
10
+ const remaining = atMs - Date.now();
11
+ if (remaining <= 0) {
12
+ setTick((tick) => tick + 1);
13
+ return;
14
+ }
15
+ timer = window.setTimeout(arm, Math.min(remaining, PsRerenderConfig.maxTimerMs));
16
+ };
17
+ arm();
18
+ return () => window.clearTimeout(timer);
19
+ }, [atMs]);
20
+ return Date.now();
21
+ };
@@ -0,0 +1,103 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { PsScrollRestoreService } from '../services/ps-scroll-restore-service.js';
3
+ import { PsScrollStorageService } from '../services/ps-scroll-storage-service.js';
4
+ import { PsScrollTargetService } from '../services/ps-scroll-target-service.js';
5
+ import { usePsBrowserLayoutEffect } from './use-ps-browser-layout-effect.js';
6
+ export const usePsScrollRestoration = ({ elementRef, preventScrollReset, resetKey, restoreKey, }) => {
7
+ const keyRef = useRef(restoreKey);
8
+ const hasSavedRef = useRef(false);
9
+ const appliedKeyRef = useRef(undefined);
10
+ const appliedResetKeyRef = useRef(undefined);
11
+ const isRestoringRef = useRef(false);
12
+ const pendingRef = useRef(null);
13
+ const isEnabled = restoreKey !== undefined;
14
+ useEffect(() => {
15
+ if (!isEnabled ||
16
+ typeof window === 'undefined' ||
17
+ typeof history === 'undefined' ||
18
+ !('scrollRestoration' in history))
19
+ return;
20
+ const previous = history.scrollRestoration;
21
+ history.scrollRestoration = 'manual';
22
+ return () => {
23
+ history.scrollRestoration = previous;
24
+ };
25
+ }, [isEnabled]);
26
+ usePsBrowserLayoutEffect(() => {
27
+ keyRef.current = restoreKey;
28
+ if (typeof window === 'undefined')
29
+ return;
30
+ PsScrollStorageService.flush();
31
+ if (restoreKey === undefined)
32
+ return;
33
+ const element = elementRef === undefined ? null : elementRef.current;
34
+ const saved = PsScrollStorageService.read(PsScrollTargetService.storageKey(element, restoreKey));
35
+ hasSavedRef.current = saved !== undefined;
36
+ if (saved === undefined)
37
+ return;
38
+ isRestoringRef.current = true;
39
+ const stop = PsScrollRestoreService.start(element, saved, () => {
40
+ isRestoringRef.current = false;
41
+ });
42
+ return () => {
43
+ stop();
44
+ isRestoringRef.current = false;
45
+ };
46
+ }, [restoreKey, elementRef]);
47
+ usePsBrowserLayoutEffect(() => {
48
+ const previousKey = appliedKeyRef.current;
49
+ const previousResetKey = appliedResetKeyRef.current;
50
+ appliedKeyRef.current = restoreKey;
51
+ appliedResetKeyRef.current = resetKey;
52
+ if (restoreKey === undefined || typeof window === 'undefined')
53
+ return;
54
+ const hasChanged = previousResetKey !== undefined && previousResetKey !== resetKey;
55
+ const isRestored = previousKey !== restoreKey && hasSavedRef.current;
56
+ if (!hasChanged || isRestored || preventScrollReset === true)
57
+ return;
58
+ PsScrollTargetService.scrollTo(elementRef === undefined ? null : elementRef.current, 0);
59
+ }, [resetKey, restoreKey, elementRef, preventScrollReset]);
60
+ useEffect(() => {
61
+ if (!isEnabled || typeof window === 'undefined')
62
+ return;
63
+ const element = elementRef === undefined ? null : elementRef.current;
64
+ const target = PsScrollTargetService.eventTarget(element);
65
+ let frame = 0;
66
+ const flushPending = () => {
67
+ const pending = pendingRef.current;
68
+ pendingRef.current = null;
69
+ if (pending !== null)
70
+ PsScrollStorageService.write(pending.key, pending.top);
71
+ };
72
+ const onScroll = () => {
73
+ const key = keyRef.current;
74
+ if (key === undefined || isRestoringRef.current)
75
+ return;
76
+ pendingRef.current = {
77
+ key: PsScrollTargetService.storageKey(element, key),
78
+ top: PsScrollTargetService.top(element),
79
+ };
80
+ if (frame !== 0)
81
+ return;
82
+ frame = window.requestAnimationFrame(() => {
83
+ frame = 0;
84
+ flushPending();
85
+ });
86
+ };
87
+ const onPageHide = () => {
88
+ const key = keyRef.current;
89
+ pendingRef.current = null;
90
+ if (key !== undefined && !isRestoringRef.current)
91
+ PsScrollStorageService.write(PsScrollTargetService.storageKey(element, key), PsScrollTargetService.top(element));
92
+ PsScrollStorageService.flush();
93
+ };
94
+ target.addEventListener('scroll', onScroll, { passive: true });
95
+ window.addEventListener('pagehide', onPageHide);
96
+ return () => {
97
+ target.removeEventListener('scroll', onScroll);
98
+ window.removeEventListener('pagehide', onPageHide);
99
+ window.cancelAnimationFrame(frame);
100
+ flushPending();
101
+ };
102
+ }, [isEnabled, elementRef]);
103
+ };
package/dist/esm/index.js CHANGED
@@ -59,6 +59,7 @@ export * from './components/ps-skeleton.js';
59
59
  export * from './services/ps-scroll-lock-service.js';
60
60
  export * from './services/ps-skeleton-class-service.js';
61
61
  export * from './models/ps-skeleton-size.js';
62
+ export * from './models/ps-skeleton-table-row.js';
62
63
  export * from './constants/ps-skeleton-shape.js';
63
64
  export * from './components/ps-skeleton-card.js';
64
65
  export * from './components/ps-skeleton-group.js';
@@ -192,3 +193,27 @@ export * from './components/ps-divider.js';
192
193
  export * from './models/ps-divider-orientation.js';
193
194
  export * from './components/ps-rating-display.js';
194
195
  export * from './constants/ps-rating-display-size.js';
196
+ export * from './constants/ps-scroll-restoration-config.js';
197
+ export * from './models/ps-scroll-offsets-data.js';
198
+ export * from './models/ps-scroll-restoration-options.js';
199
+ export * from './services/ps-scroll-storage-service.js';
200
+ export * from './services/ps-scroll-target-service.js';
201
+ export * from './services/ps-scroll-restore-service.js';
202
+ export * from './hooks/use-ps-scroll-restoration.js';
203
+ export * from './components/ps-scroll-restoration.js';
204
+ export * from './hooks/use-ps-browser-layout-effect.js';
205
+ export * from './models/ps-scroll-pending-save-data.js';
206
+ export * from './constants/ps-flag-param-value.js';
207
+ export * from './constants/ps-query-param-config.js';
208
+ export * from './models/ps-search-params-set-options.js';
209
+ export * from './models/ps-search-params-setter.js';
210
+ export * from './models/ps-search-params-state.js';
211
+ export * from './models/ps-choice-param-data.js';
212
+ export * from './models/ps-flag-param-data.js';
213
+ export * from './models/ps-choice-param-options.js';
214
+ export * from './models/ps-flag-param-options.js';
215
+ export * from './services/ps-query-param-service.js';
216
+ export * from './hooks/use-ps-choice-param.js';
217
+ export * from './hooks/use-ps-flag-param.js';
218
+ export * from './constants/ps-rerender-config.js';
219
+ export * from './hooks/use-ps-rerender-at.js';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,32 @@
1
+ import { PsFlagParamValue } from '../constants/ps-flag-param-value.js';
2
+ export class PsQueryParamService {
3
+ static choice(value, options, fallback) {
4
+ const match = options.find((option) => option === value);
5
+ return match === undefined ? fallback : match;
6
+ }
7
+ static withChoice(params, name, value, fallback, resetParams = []) {
8
+ const next = PsQueryParamService.cleared(params, resetParams);
9
+ if (value === fallback)
10
+ next.delete(name);
11
+ else
12
+ next.set(name, value);
13
+ return next;
14
+ }
15
+ static flag(value) {
16
+ return value === PsFlagParamValue.on;
17
+ }
18
+ static withFlag(params, name, isOn, resetParams = []) {
19
+ const next = PsQueryParamService.cleared(params, resetParams);
20
+ if (isOn)
21
+ next.set(name, PsFlagParamValue.on);
22
+ else
23
+ next.delete(name);
24
+ return next;
25
+ }
26
+ static cleared(params, resetParams) {
27
+ const next = new URLSearchParams(params);
28
+ for (const name of resetParams)
29
+ next.delete(name);
30
+ return next;
31
+ }
32
+ }