@pallasoft/ps-core-components 2.41.0 → 2.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ps-button.js +3 -1
- package/dist/components/ps-portable-text-image.d.ts +7 -0
- package/dist/components/ps-portable-text-image.js +15 -0
- package/dist/components/ps-portable-text-message.d.ts +2 -0
- package/dist/components/ps-portable-text-message.js +49 -0
- package/dist/components/ps-portable-text.d.ts +6 -1
- package/dist/components/ps-portable-text.js +8 -3
- package/dist/components/ps-reflective-app-shell.d.ts +3 -1
- package/dist/components/ps-reflective-app-shell.js +10 -3
- package/dist/components/ps-scroll-restoration.d.ts +6 -0
- package/dist/components/ps-scroll-restoration.js +9 -0
- package/dist/constants/ps-portable-text-message-config.d.ts +32 -0
- package/dist/constants/ps-portable-text-message-config.js +35 -0
- package/dist/constants/ps-portable-text-variant.d.ts +4 -0
- package/dist/constants/ps-portable-text-variant.js +8 -0
- package/dist/constants/ps-scroll-restoration-config.d.ts +10 -0
- package/dist/constants/ps-scroll-restoration-config.js +13 -0
- package/dist/constants/ps-text-link-gap.d.ts +4 -0
- package/dist/constants/ps-text-link-gap.js +7 -0
- package/dist/esm/components/ps-button.js +3 -1
- package/dist/esm/components/ps-portable-text-image.js +11 -0
- package/dist/esm/components/ps-portable-text-message.js +45 -0
- package/dist/esm/components/ps-portable-text.js +8 -3
- package/dist/esm/components/ps-reflective-app-shell.js +10 -3
- package/dist/esm/components/ps-scroll-restoration.js +5 -0
- package/dist/esm/constants/ps-portable-text-message-config.js +32 -0
- package/dist/esm/constants/ps-portable-text-variant.js +5 -0
- package/dist/esm/constants/ps-scroll-restoration-config.js +10 -0
- package/dist/esm/constants/ps-text-link-gap.js +4 -0
- package/dist/esm/hooks/use-ps-browser-layout-effect.js +2 -0
- package/dist/esm/hooks/use-ps-scroll-restoration.js +103 -0
- package/dist/esm/index.js +17 -0
- package/dist/esm/models/ps-portable-text-image-data.js +1 -0
- package/dist/esm/models/ps-portable-text-image-labels.js +1 -0
- package/dist/esm/models/ps-portable-text-message-props.js +1 -0
- package/dist/esm/models/ps-scroll-offsets-data.js +1 -0
- package/dist/esm/models/ps-scroll-pending-save-data.js +1 -0
- package/dist/esm/models/ps-scroll-restoration-options.js +1 -0
- package/dist/esm/services/ps-portable-text-link-service.js +19 -0
- package/dist/esm/services/ps-scroll-restore-service.js +52 -0
- package/dist/esm/services/ps-scroll-storage-service.js +66 -0
- package/dist/esm/services/ps-scroll-target-service.js +29 -0
- package/dist/esm/services/ps-text-link-service.js +9 -0
- package/dist/hooks/use-ps-browser-layout-effect.d.ts +2 -0
- package/dist/hooks/use-ps-browser-layout-effect.js +5 -0
- package/dist/hooks/use-ps-scroll-restoration.d.ts +2 -0
- package/dist/hooks/use-ps-scroll-restoration.js +107 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +17 -0
- package/dist/models/ps-portable-text-image-data.d.ts +5 -0
- package/dist/models/ps-portable-text-image-data.js +2 -0
- package/dist/models/ps-portable-text-image-labels.d.ts +5 -0
- package/dist/models/ps-portable-text-image-labels.js +2 -0
- package/dist/models/ps-portable-text-message-props.d.ts +19 -0
- package/dist/models/ps-portable-text-message-props.js +2 -0
- package/dist/models/ps-scroll-offsets-data.d.ts +1 -0
- package/dist/models/ps-scroll-offsets-data.js +2 -0
- package/dist/models/ps-scroll-pending-save-data.d.ts +4 -0
- package/dist/models/ps-scroll-pending-save-data.js +2 -0
- package/dist/models/ps-scroll-restoration-options.d.ts +7 -0
- package/dist/models/ps-scroll-restoration-options.js +2 -0
- package/dist/services/ps-portable-text-link-service.d.ts +3 -0
- package/dist/services/ps-portable-text-link-service.js +19 -0
- package/dist/services/ps-scroll-restore-service.d.ts +3 -0
- package/dist/services/ps-scroll-restore-service.js +56 -0
- package/dist/services/ps-scroll-storage-service.d.ts +11 -0
- package/dist/services/ps-scroll-storage-service.js +70 -0
- package/dist/services/ps-scroll-target-service.d.ts +8 -0
- package/dist/services/ps-scroll-target-service.js +33 -0
- package/dist/services/ps-text-link-service.d.ts +1 -0
- package/dist/services/ps-text-link-service.js +9 -0
- package/package.json +2 -2
|
@@ -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
|
@@ -35,6 +35,13 @@ export * from './components/ps-list-tile.js';
|
|
|
35
35
|
export * from './components/ps-metric-card.js';
|
|
36
36
|
export * from './components/ps-pill.js';
|
|
37
37
|
export * from './components/ps-portable-text.js';
|
|
38
|
+
export * from './components/ps-portable-text-image.js';
|
|
39
|
+
export * from './components/ps-portable-text-message.js';
|
|
40
|
+
export * from './constants/ps-portable-text-variant.js';
|
|
41
|
+
export * from './constants/ps-portable-text-message-config.js';
|
|
42
|
+
export * from './models/ps-portable-text-image-data.js';
|
|
43
|
+
export * from './models/ps-portable-text-image-labels.js';
|
|
44
|
+
export * from './models/ps-portable-text-message-props.js';
|
|
38
45
|
export * from './components/ps-linked-text.js';
|
|
39
46
|
export * from './services/ps-text-link-service.js';
|
|
40
47
|
export * from './models/ps-text-link-segment-data.js';
|
|
@@ -185,3 +192,13 @@ export * from './components/ps-divider.js';
|
|
|
185
192
|
export * from './models/ps-divider-orientation.js';
|
|
186
193
|
export * from './components/ps-rating-display.js';
|
|
187
194
|
export * from './constants/ps-rating-display-size.js';
|
|
195
|
+
export * from './constants/ps-scroll-restoration-config.js';
|
|
196
|
+
export * from './models/ps-scroll-offsets-data.js';
|
|
197
|
+
export * from './models/ps-scroll-restoration-options.js';
|
|
198
|
+
export * from './services/ps-scroll-storage-service.js';
|
|
199
|
+
export * from './services/ps-scroll-target-service.js';
|
|
200
|
+
export * from './services/ps-scroll-restore-service.js';
|
|
201
|
+
export * from './hooks/use-ps-scroll-restoration.js';
|
|
202
|
+
export * from './components/ps-scroll-restoration.js';
|
|
203
|
+
export * from './hooks/use-ps-browser-layout-effect.js';
|
|
204
|
+
export * from './models/ps-scroll-pending-save-data.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 {};
|
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import { PsPortableTextAutoLinkType } from '../constants/ps-portable-text-auto-link-type.js';
|
|
2
|
+
import { PsPortableTextMessageConfig } from '../constants/ps-portable-text-message-config.js';
|
|
3
|
+
import { PsTextLinkProtocols } from '../constants/ps-text-link-protocols.js';
|
|
2
4
|
import { PsTextLinkService } from './ps-text-link-service.js';
|
|
3
5
|
export class PsPortableTextLinkService {
|
|
6
|
+
static isSafeLink(href) {
|
|
7
|
+
if (href.startsWith(PsPortableTextMessageConfig.relativePrefix) &&
|
|
8
|
+
!href.startsWith(PsPortableTextMessageConfig.protocolRelativePrefix))
|
|
9
|
+
return true;
|
|
10
|
+
return URL.canParse(href) && PsTextLinkProtocols.includes(new URL(href).protocol);
|
|
11
|
+
}
|
|
12
|
+
static hasImages(blocks) {
|
|
13
|
+
return blocks.some((block) => block._type === PsPortableTextMessageConfig.imageType &&
|
|
14
|
+
'src' in block &&
|
|
15
|
+
typeof block.src === 'string' &&
|
|
16
|
+
PsPortableTextLinkService.isSafeImage(block.src));
|
|
17
|
+
}
|
|
18
|
+
static isSafeImage(src) {
|
|
19
|
+
return (PsPortableTextMessageConfig.imageProtocolPattern.test(src) &&
|
|
20
|
+
URL.canParse(src) &&
|
|
21
|
+
PsTextLinkProtocols.includes(new URL(src).protocol));
|
|
22
|
+
}
|
|
4
23
|
static linkify(blocks) {
|
|
5
24
|
return blocks.map((block) => (this.isBlock(block) ? this.linkifyBlock(block) : block));
|
|
6
25
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { PsScrollRestorationConfig } from '../constants/ps-scroll-restoration-config.js';
|
|
2
|
+
import { PsScrollTargetService } from './ps-scroll-target-service.js';
|
|
3
|
+
export class PsScrollRestoreService {
|
|
4
|
+
static start(element, top, onDone) {
|
|
5
|
+
let isDone = false;
|
|
6
|
+
let observer = null;
|
|
7
|
+
let mutations = null;
|
|
8
|
+
let timer = 0;
|
|
9
|
+
const finish = () => {
|
|
10
|
+
if (isDone)
|
|
11
|
+
return;
|
|
12
|
+
isDone = true;
|
|
13
|
+
window.clearTimeout(timer);
|
|
14
|
+
observer?.disconnect();
|
|
15
|
+
mutations?.disconnect();
|
|
16
|
+
for (const name of PsScrollRestorationConfig.inputEvents)
|
|
17
|
+
window.removeEventListener(name, onInput, true);
|
|
18
|
+
onDone();
|
|
19
|
+
};
|
|
20
|
+
const onInput = () => finish();
|
|
21
|
+
const attempt = () => {
|
|
22
|
+
if (isDone)
|
|
23
|
+
return;
|
|
24
|
+
if (PsScrollTargetService.maxTop(element) >= top - PsScrollRestorationConfig.tolerancePx) {
|
|
25
|
+
PsScrollTargetService.scrollTo(element, top);
|
|
26
|
+
finish();
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
for (const name of PsScrollRestorationConfig.inputEvents)
|
|
30
|
+
window.addEventListener(name, onInput, { capture: true, passive: true });
|
|
31
|
+
timer = window.setTimeout(() => {
|
|
32
|
+
if (isDone)
|
|
33
|
+
return;
|
|
34
|
+
PsScrollTargetService.scrollTo(element, top);
|
|
35
|
+
finish();
|
|
36
|
+
}, PsScrollRestorationConfig.restoreTimeoutMs);
|
|
37
|
+
attempt();
|
|
38
|
+
if (!isDone && typeof MutationObserver !== 'undefined') {
|
|
39
|
+
mutations = new MutationObserver(attempt);
|
|
40
|
+
mutations.observe(element === null ? document.body : element, {
|
|
41
|
+
childList: true,
|
|
42
|
+
subtree: true,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (!isDone && typeof ResizeObserver !== 'undefined') {
|
|
46
|
+
observer = new ResizeObserver(attempt);
|
|
47
|
+
for (const node of PsScrollTargetService.observed(element))
|
|
48
|
+
observer.observe(node);
|
|
49
|
+
}
|
|
50
|
+
return finish;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { PsScrollRestorationConfig } from '../constants/ps-scroll-restoration-config.js';
|
|
2
|
+
export class PsScrollStorageService {
|
|
3
|
+
static read(key) {
|
|
4
|
+
const offsets = PsScrollStorageService.memory();
|
|
5
|
+
return Object.prototype.hasOwnProperty.call(offsets, key) ? offsets[key] : undefined;
|
|
6
|
+
}
|
|
7
|
+
static write(key, top) {
|
|
8
|
+
const offsets = PsScrollStorageService.memory();
|
|
9
|
+
delete offsets[key];
|
|
10
|
+
offsets[key] = top;
|
|
11
|
+
const keys = Object.keys(offsets);
|
|
12
|
+
const overflow = keys.length - PsScrollRestorationConfig.maxEntries;
|
|
13
|
+
for (const oldest of keys.slice(0, overflow > 0 ? overflow : 0))
|
|
14
|
+
delete offsets[oldest];
|
|
15
|
+
if (PsScrollStorageService.timer !== 0 || typeof window === 'undefined')
|
|
16
|
+
return;
|
|
17
|
+
PsScrollStorageService.timer = window.setTimeout(PsScrollStorageService.flush, PsScrollRestorationConfig.persistThrottleMs);
|
|
18
|
+
}
|
|
19
|
+
static flush() {
|
|
20
|
+
if (typeof window === 'undefined')
|
|
21
|
+
return;
|
|
22
|
+
window.clearTimeout(PsScrollStorageService.timer);
|
|
23
|
+
PsScrollStorageService.timer = 0;
|
|
24
|
+
if (PsScrollStorageService.offsets === null)
|
|
25
|
+
return;
|
|
26
|
+
try {
|
|
27
|
+
window.sessionStorage.setItem(PsScrollRestorationConfig.storageKey, JSON.stringify(PsScrollStorageService.offsets));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
static reset() {
|
|
34
|
+
if (typeof window !== 'undefined')
|
|
35
|
+
window.clearTimeout(PsScrollStorageService.timer);
|
|
36
|
+
PsScrollStorageService.timer = 0;
|
|
37
|
+
PsScrollStorageService.offsets = null;
|
|
38
|
+
}
|
|
39
|
+
static memory() {
|
|
40
|
+
if (PsScrollStorageService.offsets === null)
|
|
41
|
+
PsScrollStorageService.offsets = PsScrollStorageService.load();
|
|
42
|
+
return PsScrollStorageService.offsets;
|
|
43
|
+
}
|
|
44
|
+
static load() {
|
|
45
|
+
if (typeof window === 'undefined')
|
|
46
|
+
return {};
|
|
47
|
+
try {
|
|
48
|
+
const raw = window.sessionStorage.getItem(PsScrollRestorationConfig.storageKey);
|
|
49
|
+
if (raw === null)
|
|
50
|
+
return {};
|
|
51
|
+
const parsed = JSON.parse(raw);
|
|
52
|
+
return PsScrollStorageService.isOffsets(parsed) ? parsed : {};
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
static isOffsets(value) {
|
|
59
|
+
return (typeof value === 'object' &&
|
|
60
|
+
value !== null &&
|
|
61
|
+
!Array.isArray(value) &&
|
|
62
|
+
Object.values(value).every((entry) => typeof entry === 'number' && Number.isFinite(entry)));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
PsScrollStorageService.offsets = null;
|
|
66
|
+
PsScrollStorageService.timer = 0;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { PsScrollRestorationConfig } from '../constants/ps-scroll-restoration-config.js';
|
|
2
|
+
export class PsScrollTargetService {
|
|
3
|
+
static storageKey(element, restoreKey) {
|
|
4
|
+
return `${element === null ? PsScrollRestorationConfig.windowScope : PsScrollRestorationConfig.elementScope}${restoreKey}`;
|
|
5
|
+
}
|
|
6
|
+
static top(element) {
|
|
7
|
+
return element === null ? window.scrollY : element.scrollTop;
|
|
8
|
+
}
|
|
9
|
+
static maxTop(element) {
|
|
10
|
+
if (element !== null)
|
|
11
|
+
return element.scrollHeight - element.clientHeight;
|
|
12
|
+
return document.documentElement.scrollHeight - window.innerHeight;
|
|
13
|
+
}
|
|
14
|
+
static scrollTo(element, top) {
|
|
15
|
+
if (element === null) {
|
|
16
|
+
window.scrollTo({ behavior: 'instant', left: 0, top });
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
element.scrollTo({ behavior: 'instant', left: 0, top });
|
|
20
|
+
}
|
|
21
|
+
static eventTarget(element) {
|
|
22
|
+
return element === null ? window : element;
|
|
23
|
+
}
|
|
24
|
+
static observed(element) {
|
|
25
|
+
if (element === null)
|
|
26
|
+
return [document.documentElement, document.body];
|
|
27
|
+
return [element, ...Array.from(element.children)];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PsTextLinkGap } from '../constants/ps-text-link-gap.js';
|
|
1
2
|
import { PsTextLinkPatternSource } from '../constants/ps-text-link-pattern-source.js';
|
|
2
3
|
import { PsTextLinkProtocols } from '../constants/ps-text-link-protocols.js';
|
|
3
4
|
import { PsTextLinkTrailingCharacters } from '../constants/ps-text-link-trailing-characters.js';
|
|
@@ -30,8 +31,16 @@ export class PsTextLinkService {
|
|
|
30
31
|
this.pushText(segments, raw);
|
|
31
32
|
return;
|
|
32
33
|
}
|
|
34
|
+
this.separate(segments);
|
|
33
35
|
segments.push({ href, text: label });
|
|
34
36
|
}
|
|
37
|
+
static separate(segments) {
|
|
38
|
+
const last = segments[segments.length - 1];
|
|
39
|
+
if (last !== undefined &&
|
|
40
|
+
last.href === null &&
|
|
41
|
+
!PsTextLinkGap.trailingSpacePattern.test(last.text))
|
|
42
|
+
last.text += PsTextLinkGap.text;
|
|
43
|
+
}
|
|
35
44
|
static pushBare(segments, raw) {
|
|
36
45
|
let end = raw.length;
|
|
37
46
|
while (end > 0 && PsTextLinkTrailingCharacters.includes(raw.charAt(end - 1)))
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.usePsBrowserLayoutEffect = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
exports.usePsBrowserLayoutEffect = typeof window === 'undefined' ? react_1.useEffect : react_1.useLayoutEffect;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.usePsScrollRestoration = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const ps_scroll_restore_service_1 = require("../services/ps-scroll-restore-service");
|
|
6
|
+
const ps_scroll_storage_service_1 = require("../services/ps-scroll-storage-service");
|
|
7
|
+
const ps_scroll_target_service_1 = require("../services/ps-scroll-target-service");
|
|
8
|
+
const use_ps_browser_layout_effect_1 = require("./use-ps-browser-layout-effect");
|
|
9
|
+
const usePsScrollRestoration = ({ elementRef, preventScrollReset, resetKey, restoreKey, }) => {
|
|
10
|
+
const keyRef = (0, react_1.useRef)(restoreKey);
|
|
11
|
+
const hasSavedRef = (0, react_1.useRef)(false);
|
|
12
|
+
const appliedKeyRef = (0, react_1.useRef)(undefined);
|
|
13
|
+
const appliedResetKeyRef = (0, react_1.useRef)(undefined);
|
|
14
|
+
const isRestoringRef = (0, react_1.useRef)(false);
|
|
15
|
+
const pendingRef = (0, react_1.useRef)(null);
|
|
16
|
+
const isEnabled = restoreKey !== undefined;
|
|
17
|
+
(0, react_1.useEffect)(() => {
|
|
18
|
+
if (!isEnabled ||
|
|
19
|
+
typeof window === 'undefined' ||
|
|
20
|
+
typeof history === 'undefined' ||
|
|
21
|
+
!('scrollRestoration' in history))
|
|
22
|
+
return;
|
|
23
|
+
const previous = history.scrollRestoration;
|
|
24
|
+
history.scrollRestoration = 'manual';
|
|
25
|
+
return () => {
|
|
26
|
+
history.scrollRestoration = previous;
|
|
27
|
+
};
|
|
28
|
+
}, [isEnabled]);
|
|
29
|
+
(0, use_ps_browser_layout_effect_1.usePsBrowserLayoutEffect)(() => {
|
|
30
|
+
keyRef.current = restoreKey;
|
|
31
|
+
if (typeof window === 'undefined')
|
|
32
|
+
return;
|
|
33
|
+
ps_scroll_storage_service_1.PsScrollStorageService.flush();
|
|
34
|
+
if (restoreKey === undefined)
|
|
35
|
+
return;
|
|
36
|
+
const element = elementRef === undefined ? null : elementRef.current;
|
|
37
|
+
const saved = ps_scroll_storage_service_1.PsScrollStorageService.read(ps_scroll_target_service_1.PsScrollTargetService.storageKey(element, restoreKey));
|
|
38
|
+
hasSavedRef.current = saved !== undefined;
|
|
39
|
+
if (saved === undefined)
|
|
40
|
+
return;
|
|
41
|
+
isRestoringRef.current = true;
|
|
42
|
+
const stop = ps_scroll_restore_service_1.PsScrollRestoreService.start(element, saved, () => {
|
|
43
|
+
isRestoringRef.current = false;
|
|
44
|
+
});
|
|
45
|
+
return () => {
|
|
46
|
+
stop();
|
|
47
|
+
isRestoringRef.current = false;
|
|
48
|
+
};
|
|
49
|
+
}, [restoreKey, elementRef]);
|
|
50
|
+
(0, use_ps_browser_layout_effect_1.usePsBrowserLayoutEffect)(() => {
|
|
51
|
+
const previousKey = appliedKeyRef.current;
|
|
52
|
+
const previousResetKey = appliedResetKeyRef.current;
|
|
53
|
+
appliedKeyRef.current = restoreKey;
|
|
54
|
+
appliedResetKeyRef.current = resetKey;
|
|
55
|
+
if (restoreKey === undefined || typeof window === 'undefined')
|
|
56
|
+
return;
|
|
57
|
+
const hasChanged = previousResetKey !== undefined && previousResetKey !== resetKey;
|
|
58
|
+
const isRestored = previousKey !== restoreKey && hasSavedRef.current;
|
|
59
|
+
if (!hasChanged || isRestored || preventScrollReset === true)
|
|
60
|
+
return;
|
|
61
|
+
ps_scroll_target_service_1.PsScrollTargetService.scrollTo(elementRef === undefined ? null : elementRef.current, 0);
|
|
62
|
+
}, [resetKey, restoreKey, elementRef, preventScrollReset]);
|
|
63
|
+
(0, react_1.useEffect)(() => {
|
|
64
|
+
if (!isEnabled || typeof window === 'undefined')
|
|
65
|
+
return;
|
|
66
|
+
const element = elementRef === undefined ? null : elementRef.current;
|
|
67
|
+
const target = ps_scroll_target_service_1.PsScrollTargetService.eventTarget(element);
|
|
68
|
+
let frame = 0;
|
|
69
|
+
const flushPending = () => {
|
|
70
|
+
const pending = pendingRef.current;
|
|
71
|
+
pendingRef.current = null;
|
|
72
|
+
if (pending !== null)
|
|
73
|
+
ps_scroll_storage_service_1.PsScrollStorageService.write(pending.key, pending.top);
|
|
74
|
+
};
|
|
75
|
+
const onScroll = () => {
|
|
76
|
+
const key = keyRef.current;
|
|
77
|
+
if (key === undefined || isRestoringRef.current)
|
|
78
|
+
return;
|
|
79
|
+
pendingRef.current = {
|
|
80
|
+
key: ps_scroll_target_service_1.PsScrollTargetService.storageKey(element, key),
|
|
81
|
+
top: ps_scroll_target_service_1.PsScrollTargetService.top(element),
|
|
82
|
+
};
|
|
83
|
+
if (frame !== 0)
|
|
84
|
+
return;
|
|
85
|
+
frame = window.requestAnimationFrame(() => {
|
|
86
|
+
frame = 0;
|
|
87
|
+
flushPending();
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
const onPageHide = () => {
|
|
91
|
+
const key = keyRef.current;
|
|
92
|
+
pendingRef.current = null;
|
|
93
|
+
if (key !== undefined && !isRestoringRef.current)
|
|
94
|
+
ps_scroll_storage_service_1.PsScrollStorageService.write(ps_scroll_target_service_1.PsScrollTargetService.storageKey(element, key), ps_scroll_target_service_1.PsScrollTargetService.top(element));
|
|
95
|
+
ps_scroll_storage_service_1.PsScrollStorageService.flush();
|
|
96
|
+
};
|
|
97
|
+
target.addEventListener('scroll', onScroll, { passive: true });
|
|
98
|
+
window.addEventListener('pagehide', onPageHide);
|
|
99
|
+
return () => {
|
|
100
|
+
target.removeEventListener('scroll', onScroll);
|
|
101
|
+
window.removeEventListener('pagehide', onPageHide);
|
|
102
|
+
window.cancelAnimationFrame(frame);
|
|
103
|
+
flushPending();
|
|
104
|
+
};
|
|
105
|
+
}, [isEnabled, elementRef]);
|
|
106
|
+
};
|
|
107
|
+
exports.usePsScrollRestoration = usePsScrollRestoration;
|
package/dist/index.d.ts
CHANGED
|
@@ -35,6 +35,13 @@ export * from './components/ps-list-tile';
|
|
|
35
35
|
export * from './components/ps-metric-card';
|
|
36
36
|
export * from './components/ps-pill';
|
|
37
37
|
export * from './components/ps-portable-text';
|
|
38
|
+
export * from './components/ps-portable-text-image';
|
|
39
|
+
export * from './components/ps-portable-text-message';
|
|
40
|
+
export * from './constants/ps-portable-text-variant';
|
|
41
|
+
export * from './constants/ps-portable-text-message-config';
|
|
42
|
+
export * from './models/ps-portable-text-image-data';
|
|
43
|
+
export * from './models/ps-portable-text-image-labels';
|
|
44
|
+
export * from './models/ps-portable-text-message-props';
|
|
38
45
|
export * from './components/ps-linked-text';
|
|
39
46
|
export * from './services/ps-text-link-service';
|
|
40
47
|
export * from './models/ps-text-link-segment-data';
|
|
@@ -185,3 +192,13 @@ export * from './components/ps-divider';
|
|
|
185
192
|
export * from './models/ps-divider-orientation';
|
|
186
193
|
export * from './components/ps-rating-display';
|
|
187
194
|
export * from './constants/ps-rating-display-size';
|
|
195
|
+
export * from './constants/ps-scroll-restoration-config';
|
|
196
|
+
export * from './models/ps-scroll-offsets-data';
|
|
197
|
+
export * from './models/ps-scroll-restoration-options';
|
|
198
|
+
export * from './services/ps-scroll-storage-service';
|
|
199
|
+
export * from './services/ps-scroll-target-service';
|
|
200
|
+
export * from './services/ps-scroll-restore-service';
|
|
201
|
+
export * from './hooks/use-ps-scroll-restoration';
|
|
202
|
+
export * from './components/ps-scroll-restoration';
|
|
203
|
+
export * from './hooks/use-ps-browser-layout-effect';
|
|
204
|
+
export * from './models/ps-scroll-pending-save-data';
|
package/dist/index.js
CHANGED
|
@@ -51,6 +51,13 @@ __exportStar(require("./components/ps-list-tile"), exports);
|
|
|
51
51
|
__exportStar(require("./components/ps-metric-card"), exports);
|
|
52
52
|
__exportStar(require("./components/ps-pill"), exports);
|
|
53
53
|
__exportStar(require("./components/ps-portable-text"), exports);
|
|
54
|
+
__exportStar(require("./components/ps-portable-text-image"), exports);
|
|
55
|
+
__exportStar(require("./components/ps-portable-text-message"), exports);
|
|
56
|
+
__exportStar(require("./constants/ps-portable-text-variant"), exports);
|
|
57
|
+
__exportStar(require("./constants/ps-portable-text-message-config"), exports);
|
|
58
|
+
__exportStar(require("./models/ps-portable-text-image-data"), exports);
|
|
59
|
+
__exportStar(require("./models/ps-portable-text-image-labels"), exports);
|
|
60
|
+
__exportStar(require("./models/ps-portable-text-message-props"), exports);
|
|
54
61
|
__exportStar(require("./components/ps-linked-text"), exports);
|
|
55
62
|
__exportStar(require("./services/ps-text-link-service"), exports);
|
|
56
63
|
__exportStar(require("./models/ps-text-link-segment-data"), exports);
|
|
@@ -201,3 +208,13 @@ __exportStar(require("./components/ps-divider"), exports);
|
|
|
201
208
|
__exportStar(require("./models/ps-divider-orientation"), exports);
|
|
202
209
|
__exportStar(require("./components/ps-rating-display"), exports);
|
|
203
210
|
__exportStar(require("./constants/ps-rating-display-size"), exports);
|
|
211
|
+
__exportStar(require("./constants/ps-scroll-restoration-config"), exports);
|
|
212
|
+
__exportStar(require("./models/ps-scroll-offsets-data"), exports);
|
|
213
|
+
__exportStar(require("./models/ps-scroll-restoration-options"), exports);
|
|
214
|
+
__exportStar(require("./services/ps-scroll-storage-service"), exports);
|
|
215
|
+
__exportStar(require("./services/ps-scroll-target-service"), exports);
|
|
216
|
+
__exportStar(require("./services/ps-scroll-restore-service"), exports);
|
|
217
|
+
__exportStar(require("./hooks/use-ps-scroll-restoration"), exports);
|
|
218
|
+
__exportStar(require("./components/ps-scroll-restoration"), exports);
|
|
219
|
+
__exportStar(require("./hooks/use-ps-browser-layout-effect"), exports);
|
|
220
|
+
__exportStar(require("./models/ps-scroll-pending-save-data"), exports);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CSSProperties, ElementType, ReactNode } from 'react';
|
|
2
|
+
import type { TypedObject } from '@portabletext/types';
|
|
3
|
+
import type { PsPortableTextImageLabels } from './ps-portable-text-image-labels';
|
|
4
|
+
export interface PsPortableTextMessageProps {
|
|
5
|
+
containerClasses: string;
|
|
6
|
+
imageLabels?: Partial<PsPortableTextImageLabels>;
|
|
7
|
+
isImagesShown: boolean;
|
|
8
|
+
containerStyles: CSSProperties;
|
|
9
|
+
linkClassName?: string;
|
|
10
|
+
LinkComponent: ElementType<{
|
|
11
|
+
'aria-label'?: string;
|
|
12
|
+
'children'?: ReactNode;
|
|
13
|
+
'className'?: string;
|
|
14
|
+
'href': string;
|
|
15
|
+
'rel'?: string;
|
|
16
|
+
'target'?: string;
|
|
17
|
+
}>;
|
|
18
|
+
text: TypedObject[];
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type PsScrollOffsetsData = Record<string, number>;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { TypedObject } from '@portabletext/types';
|
|
2
2
|
export declare class PsPortableTextLinkService {
|
|
3
|
+
static isSafeLink(href: string): boolean;
|
|
4
|
+
static hasImages(blocks: TypedObject[]): boolean;
|
|
5
|
+
static isSafeImage(src: string): boolean;
|
|
3
6
|
static linkify(blocks: TypedObject[]): TypedObject[];
|
|
4
7
|
private static isBlock;
|
|
5
8
|
private static isSpan;
|
|
@@ -2,8 +2,27 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PsPortableTextLinkService = void 0;
|
|
4
4
|
const ps_portable_text_auto_link_type_1 = require("../constants/ps-portable-text-auto-link-type");
|
|
5
|
+
const ps_portable_text_message_config_1 = require("../constants/ps-portable-text-message-config");
|
|
6
|
+
const ps_text_link_protocols_1 = require("../constants/ps-text-link-protocols");
|
|
5
7
|
const ps_text_link_service_1 = require("./ps-text-link-service");
|
|
6
8
|
class PsPortableTextLinkService {
|
|
9
|
+
static isSafeLink(href) {
|
|
10
|
+
if (href.startsWith(ps_portable_text_message_config_1.PsPortableTextMessageConfig.relativePrefix) &&
|
|
11
|
+
!href.startsWith(ps_portable_text_message_config_1.PsPortableTextMessageConfig.protocolRelativePrefix))
|
|
12
|
+
return true;
|
|
13
|
+
return URL.canParse(href) && ps_text_link_protocols_1.PsTextLinkProtocols.includes(new URL(href).protocol);
|
|
14
|
+
}
|
|
15
|
+
static hasImages(blocks) {
|
|
16
|
+
return blocks.some((block) => block._type === ps_portable_text_message_config_1.PsPortableTextMessageConfig.imageType &&
|
|
17
|
+
'src' in block &&
|
|
18
|
+
typeof block.src === 'string' &&
|
|
19
|
+
PsPortableTextLinkService.isSafeImage(block.src));
|
|
20
|
+
}
|
|
21
|
+
static isSafeImage(src) {
|
|
22
|
+
return (ps_portable_text_message_config_1.PsPortableTextMessageConfig.imageProtocolPattern.test(src) &&
|
|
23
|
+
URL.canParse(src) &&
|
|
24
|
+
ps_text_link_protocols_1.PsTextLinkProtocols.includes(new URL(src).protocol));
|
|
25
|
+
}
|
|
7
26
|
static linkify(blocks) {
|
|
8
27
|
return blocks.map((block) => (this.isBlock(block) ? this.linkifyBlock(block) : block));
|
|
9
28
|
}
|