@ryuzaki13/react-foundation-lib 2.4.0 → 2.6.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/chunks/dom-RMvZy-Sx.js +187 -0
- package/dist/chunks/dom-RMvZy-Sx.js.map +1 -0
- package/dist/dom/index.d.ts +2 -0
- package/dist/dom/index.d.ts.map +1 -1
- package/dist/dom/index.js +2 -159
- package/dist/dom/scroll-anchor/captureScrollAnchor.d.ts +8 -0
- package/dist/dom/scroll-anchor/captureScrollAnchor.d.ts.map +1 -0
- package/dist/dom/scroll-anchor/scrollAnchorTypes.d.ts +8 -0
- package/dist/dom/scroll-anchor/scrollAnchorTypes.d.ts.map +1 -0
- package/dist/excel/index.js +1 -1
- package/dist/query-client/index.js +121 -86
- package/dist/query-client/index.js.map +1 -1
- package/dist/query-client/persistence.d.ts.map +1 -1
- package/dist/query-client/queryMeta.d.ts +13 -0
- package/dist/query-client/queryMeta.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/dom/README.mdx +80 -0
- package/src/query-client/README.mdx +31 -13
- package/dist/dom/index.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../src/query-client/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqC,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAChI,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,aAAa,EAAE,KAAK,oBAAoB,EAAE,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAOjH,eAAO,MAAM,8BAA8B,QAAqC,CAAC;AACjF,eAAO,MAAM,+BAA+B,QAA2B,CAAC;AAExE,KAAK,4BAA4B,GAAG;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,UAAU,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IAC1C,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,4FAA4F;IAC5F,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B,CAAC;AAIF;;;GAGG;AACH,MAAM,MAAM,6BAA6B,CAAC,aAAa,EAAE,OAAO,IAC7D;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GACrD;IAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GACnF;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AAE3D,MAAM,MAAM,qBAAqB,CAAC,aAAa,IAAI,YAAY,CAAC,aAAa,CAAC,GAAG;IAChF,oFAAoF;IACpF,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAC5E;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,CAAC,OAAO,EAC5B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,KAAK,6BAA6B,CAAC,aAAa,EAAE,OAAO,CAAC,KAC/F,OAAO,CAAC,OAAO,CAAC,CAAC;CACtB,CAAC;AAsDF,uGAAuG;AACvG,wBAAgB,+BAA+B,IAAI,4BAA4B,GAAG,IAAI,CAErF;AAUD,gFAAgF;AAChF,wBAAsB,4BAA4B,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGlF;AAED;;;;GAIG;AACH,wBAAsB,+BAA+B,CAAC,KAAK,EAAE,4BAA4B,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAW/G;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,WAE5D;
|
|
1
|
+
{"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../src/query-client/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqC,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAChI,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,aAAa,EAAE,KAAK,oBAAoB,EAAE,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAOjH,eAAO,MAAM,8BAA8B,QAAqC,CAAC;AACjF,eAAO,MAAM,+BAA+B,QAA2B,CAAC;AAExE,KAAK,4BAA4B,GAAG;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,UAAU,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IAC1C,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,4FAA4F;IAC5F,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B,CAAC;AAIF;;;GAGG;AACH,MAAM,MAAM,6BAA6B,CAAC,aAAa,EAAE,OAAO,IAC7D;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GACrD;IAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GACnF;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AAE3D,MAAM,MAAM,qBAAqB,CAAC,aAAa,IAAI,YAAY,CAAC,aAAa,CAAC,GAAG;IAChF,oFAAoF;IACpF,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAC5E;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,CAAC,OAAO,EAC5B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,KAAK,6BAA6B,CAAC,aAAa,EAAE,OAAO,CAAC,KAC/F,OAAO,CAAC,OAAO,CAAC,CAAC;CACtB,CAAC;AAsDF,uGAAuG;AACvG,wBAAgB,+BAA+B,IAAI,4BAA4B,GAAG,IAAI,CAErF;AAUD,gFAAgF;AAChF,wBAAsB,4BAA4B,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGlF;AAED;;;;GAIG;AACH,wBAAsB,+BAA+B,CAAC,KAAK,EAAE,4BAA4B,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAW/G;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,WAE5D;AAkCD,wBAAgB,2BAA2B,CAAC,aAAa,GAAG,OAAO,EAClE,OAAO,GAAE,4BAAiC,GACxC,qBAAqB,CAAC,aAAa,CAAC,GAAG,SAAS,CA4HlD;AAED,wBAAgB,yBAAyB;gBAgFrB,CAAC,EAAE,SAAS,SAAS,QAAQ,WACrC,aAAa,CAAC,CAAC,EAAE,SAAS,CAAC,WAC3B,oBAAoB,CAAC,SAAS,CAAC,SACjC,KAAK,GACV,OAAO,CAAC,CAAC,CAAC;;;+DA3V2B,CAAC;;qFAGzC,CAAF;2BAGoC,CAAC;cAqWpC"}
|
|
@@ -3,6 +3,17 @@ export type SessionQueryPersistencePolicy = {
|
|
|
3
3
|
readonly cacheName: string;
|
|
4
4
|
/** Максимальное число последних успешно полученных query в этом кэше. */
|
|
5
5
|
readonly maxEntries: number;
|
|
6
|
+
/**
|
|
7
|
+
* По умолчанию восстановленный snapshot сразу перепроверяется полным queryFn.
|
|
8
|
+
* `false` допустим только при наличии отдельной лёгкой проверки актуальности.
|
|
9
|
+
*/
|
|
10
|
+
readonly refetchOnRestore?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Поле верхнего уровня query data с неотрицательной целочисленной ревизией.
|
|
13
|
+
* При наличии поля IndexedDB атомарно отклоняет запоздавшую запись меньшей
|
|
14
|
+
* ревизии, поэтому параллельная вкладка не может откатить сохранённый snapshot.
|
|
15
|
+
*/
|
|
16
|
+
readonly monotonicRevisionField?: string;
|
|
6
17
|
};
|
|
7
18
|
/**
|
|
8
19
|
* Общие признаки query, которые управляют инфраструктурой клиентского кеша.
|
|
@@ -38,6 +49,8 @@ export declare const sessionScopedQueryMeta: {
|
|
|
38
49
|
export declare function createSessionQueryPersistenceMeta(policy: SessionQueryPersistencePolicy): {
|
|
39
50
|
readonly sessionScoped: true;
|
|
40
51
|
readonly sessionPersistence: {
|
|
52
|
+
readonly monotonicRevisionField?: string | undefined;
|
|
53
|
+
readonly refetchOnRestore?: boolean | undefined;
|
|
41
54
|
readonly cacheName: string;
|
|
42
55
|
readonly maxEntries: number;
|
|
43
56
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queryMeta.d.ts","sourceRoot":"","sources":["../../src/query-client/queryMeta.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,6BAA6B,GAAG;IAC3C,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"queryMeta.d.ts","sourceRoot":"","sources":["../../src/query-client/queryMeta.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,6BAA6B,GAAG;IAC3C,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACzC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,6BAA6B,CAAC;CAC5D,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE5B,OAAO,QAAQ,uBAAuB,CAAC;IACtC,UAAU,QAAQ;QACjB,SAAS,EAAE,YAAY,CAAC;KACxB;CACD;AAED,sEAAsE;AACtE,eAAO,MAAM,kBAAkB;;CAAiD,CAAC;AAEjF,gEAAgE;AAChE,eAAO,MAAM,sBAAsB;;CAAuD,CAAC;AAE3F;;;;GAIG;AACH,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,6BAA6B;;;;;;;;EAyBtF"}
|
package/package.json
CHANGED
package/src/dom/README.mdx
CHANGED
|
@@ -13,6 +13,7 @@ import { Meta } from "@storybook/addon-docs/blocks";
|
|
|
13
13
|
- установить начальный фокус, зациклить `Tab` и восстановить прежний фокус;
|
|
14
14
|
- узнать высоту элемента через `ResizeObserver`;
|
|
15
15
|
- узнать, пересекается ли элемент с viewport или другим root;
|
|
16
|
+
- сохранить позицию чтения при вставке элементов выше текущего;
|
|
16
17
|
- определить наличие touch/coarse-pointer возможностей.
|
|
17
18
|
|
|
18
19
|
Модуль является низкоуровневым browser foundation-слоем. Он не создаёт готовые кнопки, dialog, modal, popover, tooltip или download UI. Визуальный компонент по-прежнему отвечает за семантический HTML, ARIA, стили, анимацию, стек overlay, сообщения об ошибках и пользовательский сценарий.
|
|
@@ -27,6 +28,7 @@ import { Meta } from "@storybook/addon-docs/blocks";
|
|
|
27
28
|
- [Быстрый старт](#быстрый-старт)
|
|
28
29
|
- [Скачивание файлов](#скачивание-файлов)
|
|
29
30
|
- [`getOrCreatePortalRoot`](#getorcreateportalroot)
|
|
31
|
+
- [`captureScrollAnchor`](#capturescrollanchor)
|
|
30
32
|
- [`useClickOutside`](#useclickoutside)
|
|
31
33
|
- [`useEscapeDismiss`](#useescapedismiss)
|
|
32
34
|
- [`useElementHeightObserver`](#useelementheightobserver)
|
|
@@ -103,6 +105,7 @@ npm install react react-dom
|
|
|
103
105
|
|
|
104
106
|
```ts
|
|
105
107
|
import {
|
|
108
|
+
captureScrollAnchor,
|
|
106
109
|
downloadFileFromBlob,
|
|
107
110
|
downloadFileFromJson,
|
|
108
111
|
downloadFileFromObjectURL,
|
|
@@ -272,6 +275,7 @@ Focus определяет, какой элемент получает keyboard
|
|
|
272
275
|
| Скачать готовый `Blob` | `downloadFileFromBlob` |
|
|
273
276
|
| Сериализовать значение в readable JSON и скачать | `downloadFileFromJson` |
|
|
274
277
|
| Получить общий container для `createPortal` | `getOrCreatePortalRoot` |
|
|
278
|
+
| Сохранить позицию при вставке элементов сверху | `captureScrollAnchor` |
|
|
275
279
|
| Закрыть UI по `mousedown` вне одного элемента | `useClickOutside` |
|
|
276
280
|
| Считать trigger и portal content одной «внутренней» областью | `useClickOutside` с массивом refs |
|
|
277
281
|
| Закрыть активный overlay по `Escape` | `useEscapeDismiss` |
|
|
@@ -668,6 +672,81 @@ getOrCreatePortalRoot("dialog-root"); // null, если document отсутст
|
|
|
668
672
|
|
|
669
673
|
Не кешируйте этот `null` в server-loaded module и не ожидайте, что он автоматически станет HTMLElement на client. Portal target должен разрешаться на корректной browser lifecycle boundary.
|
|
670
674
|
|
|
675
|
+
## `captureScrollAnchor`
|
|
676
|
+
|
|
677
|
+
Одноразовая компенсация сдвига сохраняемого DOM-элемента в обычном
|
|
678
|
+
вертикальном scroll-контейнере. Например, при добавлении более ранних
|
|
679
|
+
элементов в начало списка. Не запускает запросы и не зависит от React,
|
|
680
|
+
Query, cursor или предметной модели.
|
|
681
|
+
|
|
682
|
+
```ts
|
|
683
|
+
declare function captureScrollAnchor(viewport: HTMLElement | null, anchor: HTMLElement | null): CapturedScrollAnchor | null;
|
|
684
|
+
|
|
685
|
+
type CapturedScrollAnchor = Readonly<{
|
|
686
|
+
restore: () => boolean;
|
|
687
|
+
release: () => void;
|
|
688
|
+
}>;
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
Host выбирает сохраняемый элемент, захватывает его **до изменения DOM**,
|
|
692
|
+
вызывает `restore()` **после DOM commit**, а при отмене, ошибке или unmount —
|
|
693
|
+
`release()`. Разрешение Promise загрузки не гарантирует DOM commit: React-host
|
|
694
|
+
применяет восстановление в layout effect соответствующего обновления, а не
|
|
695
|
+
сразу после `await fetchNextPage()`.
|
|
696
|
+
|
|
697
|
+
```ts
|
|
698
|
+
const pendingAnchor = captureScrollAnchor(viewportElement, retainedElement);
|
|
699
|
+
try {
|
|
700
|
+
// Здесь пример синхронного DOM commit, не React render.
|
|
701
|
+
listElement.prepend(olderElementsFragment);
|
|
702
|
+
pendingAnchor?.restore();
|
|
703
|
+
} finally {
|
|
704
|
+
pendingAnchor?.release();
|
|
705
|
+
}
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
Операция сохраняет положение anchor в координатах содержимого контейнера.
|
|
709
|
+
Разницу после commit прибавляет к **текущему** `scrollTop`: ручная прокрутка
|
|
710
|
+
во время ожидания не отменяется. Компенсация мгновенная даже при CSS
|
|
711
|
+
`scroll-behavior: smooth`; горизонтальная позиция не меняется. При нулевом
|
|
712
|
+
сдвиге запись прокрутки не выполняется. Допустимый диапазон ограничивает
|
|
713
|
+
сам браузер.
|
|
714
|
+
|
|
715
|
+
На время операции `overflow-anchor` контейнера устанавливается в
|
|
716
|
+
`none!important`, чтобы браузер не выполнил вторую компенсацию. При завершении
|
|
717
|
+
возвращаются исходные inline-значение и priority либо удаляется отсутствовавшее
|
|
718
|
+
свойство. Внешнее изменение этого свойства отменяет компенсацию и не
|
|
719
|
+
перезаписывается: native anchoring уже мог возобновиться. Браузер без
|
|
720
|
+
поддержки `overflow-anchor` не требует отключения native anchoring.
|
|
721
|
+
|
|
722
|
+
На контейнер допускается один активный захват: новый корректный вызов
|
|
723
|
+
отменяет предыдущий без прокрутки. Его старый handle становится неактивным
|
|
724
|
+
и не может освободить стиль нового захвата. Разные контейнеры независимы.
|
|
725
|
+
|
|
726
|
+
`null` возвращается для отсутствующих или отключённых от документа элементов,
|
|
727
|
+
самого viewport в качестве anchor и элемента вне viewport. `restore()`
|
|
728
|
+
возвращает `true`, если anchor сохранился и операция выполнена, включая нулевой
|
|
729
|
+
сдвиг; `false` — если операция уже завершена либо DOM больше не подходит.
|
|
730
|
+
`release()` и повторный `restore()` безопасны. Временный стиль освобождается
|
|
731
|
+
и при ошибке browser API; ошибка не подавляется.
|
|
732
|
+
После завершения host отбрасывает handle: пока он хранится, его closure
|
|
733
|
+
содержит ссылки на захваченные DOM-элементы.
|
|
734
|
+
|
|
735
|
+
Границы: anchor должен сохранять DOM identity и обычное положение в потоке
|
|
736
|
+
без промежуточного независимо прокручиваемого контейнера. Не использовать
|
|
737
|
+
для window-scroll, горизонтальной или reverse-flex ленты, sticky/fixed anchor,
|
|
738
|
+
CSS scroll-snap и преобразования масштаба/поворота. Изменения layout после
|
|
739
|
+
завершения операции, например поздние изображения, не отслеживаются. API не
|
|
740
|
+
создаёт listeners, observers, timers или React state. Его импорт SSR-safe;
|
|
741
|
+
во время render вызов с реальными DOM-элементами запрещён.
|
|
742
|
+
|
|
743
|
+
Реальная геометрия, native anchoring, CSS smooth и browser clamp проверяются
|
|
744
|
+
в [browser fixture](./scroll-anchor/scroll-anchor.browser.html). Для повторения
|
|
745
|
+
запустить `npx vite dev --host 127.0.0.1 --port 6011 --strictPort`, открыть
|
|
746
|
+
`http://127.0.0.1:6011/src/dom/scroll-anchor/scroll-anchor.browser.html` и нажать
|
|
747
|
+
«Запустить проверки». Результаты и измерения выводятся на странице; этот
|
|
748
|
+
dev-only сценарий дополняет unit-тесты, а не заменяет проверку React-host.
|
|
749
|
+
|
|
671
750
|
## `useClickOutside`
|
|
672
751
|
|
|
673
752
|
Вызывает handler на `mousedown`, если target находится вне одного ref или вне всех refs из массива.
|
|
@@ -2055,6 +2134,7 @@ Touch-capable laptop не обязательно mobile. Не связывайт
|
|
|
2055
2134
|
| `downloadFileFromBlob` | filename, `Blob` | `void` | Сам создаёт/revokes URL | Browser-only, ошибки синхронные. |
|
|
2056
2135
|
| `downloadFileFromJson` | filename, `unknown` | `void` | JSON с 2 spaces, UTF-8 MIME | Наследует ошибки и потери `JSON.stringify`. |
|
|
2057
2136
|
| `getOrCreatePortalRoot` | `id: string` | `HTMLElement \| null` | Создаёт `div` в body | DOM mutation; root не удаляется. |
|
|
2137
|
+
| `captureScrollAnchor` | viewport, сохраняемый элемент | `CapturedScrollAnchor \| null` | Один активный захват на viewport | Обычный вертикальный DOM flow; restore после commit. |
|
|
2058
2138
|
| `useClickOutside` | один ref/массив refs, handler | `void` | Global bubbling `mousedown` | Нет enabled/event arg/custom document. |
|
|
2059
2139
|
| `useEscapeDismiss` | options object | `void` | enabled=true, capture `keydown` | Не управляет stack/focus; defaultPrevented игнорируется. |
|
|
2060
2140
|
| `useElementHeightObserver` | element ref | `number` | Initial `0` | ContentRect, требует ResizeObserver. |
|
|
@@ -541,10 +541,7 @@ queryOptions({
|
|
|
541
541
|
Только для явно согласованного offline allowlist создайте bounded metadata:
|
|
542
542
|
|
|
543
543
|
```ts
|
|
544
|
-
import {
|
|
545
|
-
createSessionQueryPersistenceMeta,
|
|
546
|
-
setSessionQueryPersistenceScope
|
|
547
|
-
} from "@ryuzaki13/react-foundation-lib/query-client";
|
|
544
|
+
import { createSessionQueryPersistenceMeta, setSessionQueryPersistenceScope } from "@ryuzaki13/react-foundation-lib/query-client";
|
|
548
545
|
|
|
549
546
|
await setSessionQueryPersistenceScope({
|
|
550
547
|
id: session.id,
|
|
@@ -556,7 +553,8 @@ queryOptions({
|
|
|
556
553
|
queryFn: fetchWeek,
|
|
557
554
|
meta: createSessionQueryPersistenceMeta({
|
|
558
555
|
cacheName: "opened-weeks",
|
|
559
|
-
maxEntries: 24
|
|
556
|
+
maxEntries: 24,
|
|
557
|
+
refetchOnRestore: true
|
|
560
558
|
}),
|
|
561
559
|
networkMode: "offlineFirst"
|
|
562
560
|
});
|
|
@@ -566,6 +564,25 @@ Scope устанавливается отдельно от query contract, не
|
|
|
566
564
|
При `null`, истёкшем `expiresAt` или смене `id` старые session snapshots удаляются. `networkMode: "offlineFirst"` нужен, чтобы
|
|
567
565
|
TanStack вызвал persister до network pause. Cached data остаётся неавторитетным: write после reconnect обязан повторно получить серверный state.
|
|
568
566
|
|
|
567
|
+
Если consumer имеет отдельный лёгкий checker актуальности, полный запрос после
|
|
568
|
+
restore можно отключить. Для revisioned snapshot передайте поле верхнего уровня
|
|
569
|
+
с decimal string revision:
|
|
570
|
+
|
|
571
|
+
```ts
|
|
572
|
+
meta: createSessionQueryPersistenceMeta({
|
|
573
|
+
cacheName: "reference-data",
|
|
574
|
+
maxEntries: 16,
|
|
575
|
+
refetchOnRestore: false,
|
|
576
|
+
monotonicRevisionField: "revision"
|
|
577
|
+
});
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
`refetchOnRestore: false` без внешнего checker оставляет consumer ответственным
|
|
581
|
+
за бесконечно устаревшие данные. `monotonicRevisionField` не создаёт новые
|
|
582
|
+
storage keys: запись того же query key обновляется атомарно, а меньшая decimal
|
|
583
|
+
revision из запоздавшей вкладки не заменяет уже сохранённую большую. Если поле
|
|
584
|
+
отсутствует или не является строкой из цифр, запись использует обычную policy.
|
|
585
|
+
|
|
569
586
|
### Meta нельзя совмещать
|
|
570
587
|
|
|
571
588
|
Неверно:
|
|
@@ -824,7 +841,7 @@ const persister = createReactQueryPersister();
|
|
|
824
841
|
prefix: systemIdentifier,
|
|
825
842
|
buster: adapterBuster ?? buildTimeBuster,
|
|
826
843
|
maxAge: 90 * 24 * 60 * 60 * 1000,
|
|
827
|
-
refetchOnRestore: true,
|
|
844
|
+
refetchOnRestore: true, // либо session policy конкретного cache
|
|
828
845
|
filters: { predicate: shouldPersistQuery }
|
|
829
846
|
}
|
|
830
847
|
```
|
|
@@ -894,7 +911,8 @@ deserialize: (query) => query;
|
|
|
894
911
|
3. По `prefix-queryHash` читается IndexedDB.
|
|
895
912
|
4. Проверяются buster и возраст записи.
|
|
896
913
|
5. Подходящее state восстанавливается в Query cache.
|
|
897
|
-
6. Если восстановленная query stale, `refetchOnRestore
|
|
914
|
+
6. Если восстановленная query stale, effective `refetchOnRestore` запускает
|
|
915
|
+
обновление; session cache может отключить его при наличии внешнего checker.
|
|
898
916
|
7. Если записи нет/она протухла/не совпал buster, выполняется обычный `queryFn`.
|
|
899
917
|
8. Успешный результат планируется к записи в IndexedDB.
|
|
900
918
|
|
|
@@ -2389,12 +2407,12 @@ Profile tests подтверждают:
|
|
|
2389
2407
|
|
|
2390
2408
|
### Query metadata
|
|
2391
2409
|
|
|
2392
|
-
| Symbol
|
|
2393
|
-
|
|
|
2394
|
-
| `AppQueryMeta` | type | Public/session persistence и consumer metadata.
|
|
2395
|
-
| `SessionQueryPersistencePolicy` | type | Имя и bounded limit session cache.
|
|
2396
|
-
| `persistedQueryMeta` | const | Opt-in public persistence marker.
|
|
2397
|
-
| `sessionScopedQueryMeta` | const | Session reset marker без browser persistence.
|
|
2410
|
+
| Symbol | Вид | Назначение |
|
|
2411
|
+
| ----------------------------------- | -------- | -------------------------------------------------------- |
|
|
2412
|
+
| `AppQueryMeta` | type | Public/session persistence и consumer metadata. |
|
|
2413
|
+
| `SessionQueryPersistencePolicy` | type | Имя и bounded limit session cache. |
|
|
2414
|
+
| `persistedQueryMeta` | const | Opt-in public persistence marker. |
|
|
2415
|
+
| `sessionScopedQueryMeta` | const | Session reset marker без browser persistence. |
|
|
2398
2416
|
| `createSessionQueryPersistenceMeta` | function | Явный opt-in bounded persistence текущей server session. |
|
|
2399
2417
|
|
|
2400
2418
|
### Persistence project adapter
|
package/dist/dom/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/dom/downloadFile.ts","../../src/dom/getOrCreatePortalRoot.ts","../../src/dom/useClickOutside.ts","../../src/dom/useElementHeightObserver.ts","../../src/dom/useEscapeDismiss.ts","../../src/dom/useOverlayFocus.ts","../../src/dom/useFocusTrap.ts","../../src/dom/useIntersectionObserver.ts","../../src/dom/useIsTouchDevice.ts"],"sourcesContent":["export function downloadFileFromObjectURL(filename: string, objectUrl: string) {\n\tconst link = document.createElement(\"a\");\n\n\tlink.href = objectUrl;\n\tlink.download = filename;\n\tlink.click();\n\n\twindow.setTimeout(() => {\n\t\twindow.URL.revokeObjectURL(objectUrl);\n\t}, 0);\n}\n\nexport function downloadFileFromBlob(filename: string, blob: Blob) {\n\tconst objectUrl = window.URL.createObjectURL(blob);\n\tdownloadFileFromObjectURL(filename, objectUrl);\n}\n\nexport function downloadFileFromJson(filename: string, payload: unknown) {\n\tconst blob = new Blob([JSON.stringify(payload, null, 2)], { type: \"application/json;charset=utf-8\" });\n\tdownloadFileFromBlob(filename, blob);\n}\n","export function getOrCreatePortalRoot(id: string): HTMLElement | null {\n\tif (typeof document === \"undefined\") {\n\t\treturn null;\n\t}\n\n\tconst existingElement = document.getElementById(id);\n\tif (existingElement instanceof HTMLElement) {\n\t\treturn existingElement;\n\t}\n\n\tconst portalRoot = document.createElement(\"div\");\n\tportalRoot.id = id;\n\tdocument.body.append(portalRoot);\n\n\treturn portalRoot;\n}\n","import { RefObject, useEffect } from \"react\";\n\nexport function useClickOutside(ref: RefObject<HTMLElement | null> | RefObject<HTMLElement | null>[], handler: () => void) {\n\tuseEffect(() => {\n\t\tconst handleClickOutside = (event: MouseEvent) => {\n\t\t\tif (Array.isArray(ref)) {\n\t\t\t\tlet isOutside = true;\n\t\t\t\tfor (const r of ref) {\n\t\t\t\t\tif (r.current && r.current.contains(event.target as Node)) {\n\t\t\t\t\t\tisOutside = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (isOutside) {\n\t\t\t\t\thandler();\n\t\t\t\t}\n\t\t\t} else if (ref.current && !ref.current.contains(event.target as Node)) {\n\t\t\t\thandler();\n\t\t\t}\n\t\t};\n\n\t\tdocument.addEventListener(\"mousedown\", handleClickOutside);\n\t\treturn () => document.removeEventListener(\"mousedown\", handleClickOutside);\n\t}, [ref, handler]);\n}\n","import { type RefObject, useLayoutEffect, useState } from \"react\";\n\nexport function useElementHeightObserver<TElement extends HTMLElement>(elementRef: RefObject<TElement | null>) {\n\tconst [height, setHeight] = useState(0);\n\n\tuseLayoutEffect(() => {\n\t\tconst element = elementRef.current;\n\t\tif (!element) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst observer = new ResizeObserver((entries) => {\n\t\t\tconst nextHeight = entries[0]?.contentRect.height ?? element.getBoundingClientRect().height;\n\t\t\tsetHeight(nextHeight);\n\t\t});\n\t\tobserver.observe(element);\n\n\t\treturn () => observer.disconnect();\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps, react-hooks/refs\n\t}, [elementRef.current]);\n\n\treturn height;\n}\n","import { RefObject, useEffect, useEffectEvent } from \"react\";\n\ninterface UseEscapeDismissOptions {\n\tactive: boolean;\n\tonDismiss: () => void;\n\tenabled?: boolean;\n\tdocumentTarget?: Document | null;\n\tcontainerRef?: RefObject<HTMLElement | null>;\n}\n\n/**\n * Подписывает overlay на закрытие по клавише Escape.\n */\nexport function useEscapeDismiss({ active, onDismiss, enabled = true, documentTarget, containerRef }: UseEscapeDismissOptions) {\n\tconst dismiss = useEffectEvent(() => onDismiss());\n\n\tuseEffect(() => {\n\t\tif (!active || !enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst targetDocument = documentTarget ?? (typeof document === \"undefined\" ? null : document);\n\t\tif (!targetDocument) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst handleKeyDown = (event: KeyboardEvent) => {\n\t\t\tif (event.key !== \"Escape\" /*|| event.defaultPrevented*/) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst activeElement = targetDocument.activeElement;\n\t\t\tconst container = containerRef?.current;\n\t\t\tif (container && activeElement instanceof Node && activeElement !== container && !container.contains(activeElement)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tevent.preventDefault();\n\t\t\tdismiss();\n\t\t};\n\n\t\ttargetDocument.addEventListener(\"keydown\", handleKeyDown, true);\n\t\treturn () => targetDocument.removeEventListener(\"keydown\", handleKeyDown, true);\n\t}, [active, enabled, documentTarget, containerRef]);\n}\n","import { RefObject, useEffect, useEffectEvent, useRef } from \"react\";\n\nconst focusableElementsSelector =\n\t'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"]):not([disabled])';\n\nexport interface UseOverlayFocusOptions<T extends HTMLElement> {\n\tactive: boolean;\n\ttrapFocus?: boolean;\n\trestoreFocus?: boolean;\n\tinitialFocus?: \"auto\" | \"container\" | ((container: T) => HTMLElement | null | undefined);\n\trestoreFocusTarget?: () => HTMLElement | null | undefined;\n\tcontainerRef?: RefObject<T | null>;\n}\n\n/**\n * Возвращает ref overlay-контейнера и управляет начальными/возвратными переходами фокуса.\n * Открытие и закрытие не прокручивают документ: портал может получить фокус\n * до завершения позиционирования. Пользовательский Tab по-прежнему раскрывает\n * выбранный элемент в прокручиваемом содержимом overlay.\n */\nexport function useOverlayFocus<T extends HTMLElement>({\n\tactive,\n\ttrapFocus = false,\n\trestoreFocus = true,\n\tinitialFocus = \"auto\",\n\trestoreFocusTarget,\n\tcontainerRef\n}: UseOverlayFocusOptions<T>) {\n\tconst internalOverlayRef = useRef<T | null>(null);\n\tconst overlayRef = containerRef ?? internalOverlayRef;\n\tconst previousActiveElementRef = useRef<HTMLElement | null>(null);\n\n\t/**\n\t * Собирает фокусируемые элементы overlay в актуальном DOM-порядке.\n\t */\n\tconst getFocusableElements = useEffectEvent((container: T) =>\n\t\tArray.from(container.querySelectorAll<HTMLElement>(focusableElementsSelector)).filter(\n\t\t\t(element) => !element.hasAttribute(\"aria-hidden\")\n\t\t)\n\t);\n\n\t/**\n\t * Вычисляет стартовый фокус при открытии overlay.\n\t */\n\tconst resolveInitialFocus = useEffectEvent((container: T) => {\n\t\tif (initialFocus === \"container\") {\n\t\t\treturn container;\n\t\t}\n\n\t\tif (typeof initialFocus === \"function\") {\n\t\t\treturn initialFocus(container) ?? container;\n\t\t}\n\n\t\tconst focusableElements = getFocusableElements(container);\n\t\treturn (\n\t\t\tfocusableElements.find((element) => element.hasAttribute(\"autofocus\") || element.hasAttribute(\"autoFocus\")) ??\n\t\t\tfocusableElements[0] ??\n\t\t\tcontainer\n\t\t);\n\t});\n\n\t/**\n\t * Вычисляет элемент, которому нужно вернуть фокус после закрытия overlay.\n\t */\n\tconst resolveRestoreFocusTarget = useEffectEvent(() => restoreFocusTarget?.() ?? previousActiveElementRef.current);\n\n\tuseEffect(() => {\n\t\tif (!active) {\n\t\t\treturn;\n\t\t}\n\n\t\tpreviousActiveElementRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n\t\tlet overlayElement: T | null = null;\n\t\tlet rafId = 0;\n\n\t\t/**\n\t\t * Цикл откладывает инициализацию, пока ref контейнера не будет привязан к DOM.\n\t\t */\n\t\tconst attachOverlay = () => {\n\t\t\toverlayElement = overlayRef.current;\n\t\t\tif (!overlayElement) {\n\t\t\t\trafId = window.requestAnimationFrame(attachOverlay);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst focusTarget = resolveInitialFocus(overlayElement);\n\t\t\tif (focusTarget instanceof HTMLElement && focusTarget.isConnected) {\n\t\t\t\tfocusTarget.focus({ preventScroll: true });\n\t\t\t}\n\n\t\t\tif (trapFocus) {\n\t\t\t\toverlayElement.addEventListener(\"keydown\", handleTabKey);\n\t\t\t}\n\t\t};\n\n\t\t/**\n\t\t * Зацикливает Tab внутри overlay, пока он открыт как модальный слой.\n\t\t */\n\t\tconst handleTabKey = (event: KeyboardEvent) => {\n\t\t\tif (!trapFocus || event.key !== \"Tab\" || !overlayElement) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst focusableElements = getFocusableElements(overlayElement);\n\t\t\tif (focusableElements.length === 0) {\n\t\t\t\tevent.preventDefault();\n\t\t\t\toverlayElement.focus();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst firstElement = focusableElements[0];\n\t\t\tconst lastElement = focusableElements[focusableElements.length - 1];\n\t\t\tconst activeElement = document.activeElement;\n\n\t\t\tif (event.shiftKey) {\n\t\t\t\tif (activeElement === firstElement || activeElement === overlayElement) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tlastElement.focus();\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (activeElement === lastElement) {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tfirstElement.focus();\n\t\t\t}\n\t\t};\n\n\t\tattachOverlay();\n\n\t\treturn () => {\n\t\t\twindow.cancelAnimationFrame(rafId);\n\t\t\tif (trapFocus && overlayElement) {\n\t\t\t\toverlayElement.removeEventListener(\"keydown\", handleTabKey);\n\t\t\t}\n\n\t\t\tif (!restoreFocus) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst restoreTarget = resolveRestoreFocusTarget();\n\t\t\tif (restoreTarget?.isConnected) {\n\t\t\t\trestoreTarget.focus({ preventScroll: true });\n\t\t\t}\n\t\t};\n\t}, [active, trapFocus, restoreFocus, overlayRef]);\n\n\treturn overlayRef;\n}\n","import { useOverlayFocus } from \"./useOverlayFocus\";\n\n/**\n * Совместимый wrapper для overlay с фокус-ловушкой.\n */\nexport const useFocusTrap = (isOpen: boolean) => {\n\treturn useOverlayFocus<HTMLDivElement>({ active: isOpen, trapFocus: true, restoreFocus: true, initialFocus: \"auto\" });\n};\n","import { useEffect, useState } from \"react\";\n\nexport function useIntersectionObserver(ref: React.RefObject<Element | null>, options?: IntersectionObserverInit) {\n\tconst [isIntersecting, setIsIntersecting] = useState(false);\n\n\tuseEffect(() => {\n\t\tconst el = ref.current;\n\t\tif (!el) return;\n\n\t\tconst observer = new IntersectionObserver(([entry]) => {\n\t\t\tsetIsIntersecting(entry.isIntersecting);\n\t\t}, options);\n\n\t\tobserver.observe(el);\n\n\t\treturn () => {\n\t\t\tif (el) {\n\t\t\t\tobserver.unobserve(el);\n\t\t\t}\n\t\t\tobserver.disconnect();\n\t\t};\n\t}, [options, ref]); // не забыть, что ref.current может меняться\n\n\treturn { isIntersecting };\n}\n","import { useEffect, useState } from \"react\";\n\nexport const useIsTouchDevice = () => {\n\tconst [isTouchDevice, setIsTouchDevice] = useState(false);\n\n\tuseEffect(() => {\n\t\tconst checkTouchDevice = () => {\n\t\t\tconst isTouch = window.matchMedia(\"(pointer: coarse)\").matches || \"ontouchstart\" in window || navigator.maxTouchPoints > 0;\n\t\t\tsetIsTouchDevice(isTouch);\n\t\t};\n\n\t\tcheckTouchDevice();\n\n\t\t// Обработчик для изменений в типе устройства\n\t\twindow.addEventListener(\"resize\", checkTouchDevice);\n\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"resize\", checkTouchDevice);\n\t\t};\n\t}, []);\n\n\treturn isTouchDevice;\n};\n"],"mappings":"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"}
|