@ryuzaki13/react-foundation-lib 2.2.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dom/index.js +2 -2
- package/dist/dom/index.js.map +1 -1
- package/dist/dom/useOverlayFocus.d.ts +3 -0
- package/dist/dom/useOverlayFocus.d.ts.map +1 -1
- package/dist/table-document/index.d.ts +7 -0
- package/dist/table-document/index.d.ts.map +1 -0
- package/dist/table-document/index.js +227 -0
- package/dist/table-document/index.js.map +1 -0
- package/dist/table-document/insertTableDocumentAxis.d.ts +7 -0
- package/dist/table-document/insertTableDocumentAxis.d.ts.map +1 -0
- package/dist/table-document/mergeTableDocumentCells.d.ts +6 -0
- package/dist/table-document/mergeTableDocumentCells.d.ts.map +1 -0
- package/dist/table-document/tableDocument.d.ts +41 -0
- package/dist/table-document/tableDocument.d.ts.map +1 -0
- package/dist/table-document/tableDocumentAxisGroups.d.ts +13 -0
- package/dist/table-document/tableDocumentAxisGroups.d.ts.map +1 -0
- package/dist/table-document/tableDocumentGeometry.d.ts +15 -0
- package/dist/table-document/tableDocumentGeometry.d.ts.map +1 -0
- package/dist/table-document/updateTableDocumentCell.d.ts +4 -0
- package/dist/table-document/updateTableDocumentCell.d.ts.map +1 -0
- package/package.json +5 -1
- package/src/dom/README.mdx +5 -2
- package/src/table-document/README.md +23 -0
package/dist/dom/index.js
CHANGED
|
@@ -89,7 +89,7 @@ function p({ active: n, trapFocus: i = !1, restoreFocus: a = !0, initialFocus: o
|
|
|
89
89
|
return;
|
|
90
90
|
}
|
|
91
91
|
let n = m(e);
|
|
92
|
-
n instanceof HTMLElement && n.isConnected && n.focus(), i && e.addEventListener("keydown", o);
|
|
92
|
+
n instanceof HTMLElement && n.isConnected && n.focus({ preventScroll: !0 }), i && e.addEventListener("keydown", o);
|
|
93
93
|
}, o = (t) => {
|
|
94
94
|
if (!i || t.key !== "Tab" || !e) return;
|
|
95
95
|
let n = p(e);
|
|
@@ -107,7 +107,7 @@ function p({ active: n, trapFocus: i = !1, restoreFocus: a = !0, initialFocus: o
|
|
|
107
107
|
return r(), () => {
|
|
108
108
|
if (window.cancelAnimationFrame(t), i && e && e.removeEventListener("keydown", o), !a) return;
|
|
109
109
|
let n = h();
|
|
110
|
-
n?.isConnected && n.focus();
|
|
110
|
+
n?.isConnected && n.focus({ preventScroll: !0 });
|
|
111
111
|
};
|
|
112
112
|
}, [
|
|
113
113
|
n,
|
package/dist/dom/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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 */\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();\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();\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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"}
|
|
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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"}
|
|
@@ -9,6 +9,9 @@ export interface UseOverlayFocusOptions<T extends HTMLElement> {
|
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* Возвращает ref overlay-контейнера и управляет начальными/возвратными переходами фокуса.
|
|
12
|
+
* Открытие и закрытие не прокручивают документ: портал может получить фокус
|
|
13
|
+
* до завершения позиционирования. Пользовательский Tab по-прежнему раскрывает
|
|
14
|
+
* выбранный элемент в прокручиваемом содержимом overlay.
|
|
12
15
|
*/
|
|
13
16
|
export declare function useOverlayFocus<T extends HTMLElement>({ active, trapFocus, restoreFocus, initialFocus, restoreFocusTarget, containerRef }: UseOverlayFocusOptions<T>): RefObject<T | null>;
|
|
14
17
|
//# sourceMappingURL=useOverlayFocus.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useOverlayFocus.d.ts","sourceRoot":"","sources":["../../src/dom/useOverlayFocus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAqC,MAAM,OAAO,CAAC;AAKrE,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,WAAW;IAC5D,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,KAAK,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACzF,kBAAkB,CAAC,EAAE,MAAM,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC;IAC1D,YAAY,CAAC,EAAE,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;CACnC;AAED
|
|
1
|
+
{"version":3,"file":"useOverlayFocus.d.ts","sourceRoot":"","sources":["../../src/dom/useOverlayFocus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAqC,MAAM,OAAO,CAAC;AAKrE,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,WAAW;IAC5D,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,KAAK,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACzF,kBAAkB,CAAC,EAAE,MAAM,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC;IAC1D,YAAY,CAAC,EAAE,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;CACnC;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,WAAW,EAAE,EACtD,MAAM,EACN,SAAiB,EACjB,YAAmB,EACnB,YAAqB,EACrB,kBAAkB,EAClB,YAAY,EACZ,EAAE,sBAAsB,CAAC,CAAC,CAAC,uBAyH3B"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from "./tableDocument";
|
|
2
|
+
export * from "./tableDocumentGeometry";
|
|
3
|
+
export * from "./mergeTableDocumentCells";
|
|
4
|
+
export * from "./insertTableDocumentAxis";
|
|
5
|
+
export * from "./updateTableDocumentCell";
|
|
6
|
+
export * from "./tableDocumentAxisGroups";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/table-document/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { n as e } from "../chunks/array-D-_yMMUK.js";
|
|
2
|
+
//#region src/table-document/tableDocument.ts
|
|
3
|
+
var t = 2e4;
|
|
4
|
+
//#endregion
|
|
5
|
+
//#region src/table-document/tableDocumentGeometry.ts
|
|
6
|
+
function n(e, t) {
|
|
7
|
+
return e.row < t.row + t.rowSpan && t.row < e.row + e.rowSpan && e.column < t.column + t.columnSpan && t.column < e.column + e.columnSpan;
|
|
8
|
+
}
|
|
9
|
+
function r(e) {
|
|
10
|
+
let t = e.rows.length, n = e.columns.length;
|
|
11
|
+
if (e.version !== 1 || !t || !n || t * n > 2e4) throw Error("Недопустимый размер таблицы.");
|
|
12
|
+
if (!Number.isInteger(e.headerRowCount) || e.headerRowCount < 0 || e.headerRowCount > t) throw Error("Некорректное число строк заголовка.");
|
|
13
|
+
let r = /* @__PURE__ */ new Set(), a = (e) => {
|
|
14
|
+
if (!e || r.has(e)) throw Error("Идентификаторы строк, столбцов и ячеек должны быть уникальными.");
|
|
15
|
+
r.add(e);
|
|
16
|
+
};
|
|
17
|
+
for (let t of e.columns) if (a(t.id), t.width !== null && (!Number.isFinite(t.width) || t.width <= 0)) throw Error("Некорректная ширина столбца.");
|
|
18
|
+
for (let t of e.rows) {
|
|
19
|
+
if (a(t.id), t.cells.length !== n) throw Error("Все строки должны иметь одинаковое число ячеек.");
|
|
20
|
+
t.cells.forEach((e) => a(e.id));
|
|
21
|
+
}
|
|
22
|
+
let o = /* @__PURE__ */ new Set();
|
|
23
|
+
for (let t of e.merges) {
|
|
24
|
+
if (i(e, t), t.rowSpan * t.columnSpan === 1) throw Error("Объединение должно содержать несколько ячеек.");
|
|
25
|
+
if (t.row < e.headerRowCount && t.row + t.rowSpan > e.headerRowCount) throw Error("Нельзя объединять заголовок с основной частью таблицы.");
|
|
26
|
+
for (let e = t.row; e < t.row + t.rowSpan; e++) for (let r = t.column; r < t.column + t.columnSpan; r++) {
|
|
27
|
+
let t = e * n + r;
|
|
28
|
+
if (o.has(t)) throw Error("Объединённые области пересекаются.");
|
|
29
|
+
o.add(t);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function i(e, t) {
|
|
34
|
+
if (![
|
|
35
|
+
t.row,
|
|
36
|
+
t.column,
|
|
37
|
+
t.rowSpan,
|
|
38
|
+
t.columnSpan
|
|
39
|
+
].every(Number.isInteger) || t.row < 0 || t.column < 0 || t.rowSpan < 1 || t.columnSpan < 1 || t.row + t.rowSpan > e.rows.length || t.column + t.columnSpan > e.columns.length) throw Error("Выделение выходит за границы таблицы.");
|
|
40
|
+
}
|
|
41
|
+
function a(e, t) {
|
|
42
|
+
let r = {
|
|
43
|
+
...t,
|
|
44
|
+
rowSpan: 1,
|
|
45
|
+
columnSpan: 1
|
|
46
|
+
};
|
|
47
|
+
return i(e, r), e.merges.find((e) => n(e, r)) ?? r;
|
|
48
|
+
}
|
|
49
|
+
function o(e, t, r) {
|
|
50
|
+
let a = {
|
|
51
|
+
row: Math.min(t.row, r.row),
|
|
52
|
+
column: Math.min(t.column, r.column),
|
|
53
|
+
rowSpan: Math.abs(t.row - r.row) + 1,
|
|
54
|
+
columnSpan: Math.abs(t.column - r.column) + 1
|
|
55
|
+
};
|
|
56
|
+
i(e, a);
|
|
57
|
+
let o = !0;
|
|
58
|
+
for (; o;) {
|
|
59
|
+
o = !1;
|
|
60
|
+
for (let t of e.merges) {
|
|
61
|
+
if (!n(a, t)) continue;
|
|
62
|
+
let e = Math.min(a.row, t.row), r = Math.min(a.column, t.column), i = Math.max(a.row + a.rowSpan, t.row + t.rowSpan) - e, s = Math.max(a.column + a.columnSpan, t.column + t.columnSpan) - r;
|
|
63
|
+
(e !== a.row || r !== a.column || i !== a.rowSpan || s !== a.columnSpan) && (a = {
|
|
64
|
+
row: e,
|
|
65
|
+
column: r,
|
|
66
|
+
rowSpan: i,
|
|
67
|
+
columnSpan: s
|
|
68
|
+
}, o = !0);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return a;
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/table-document/mergeTableDocumentCells.ts
|
|
75
|
+
function s(e, t, i) {
|
|
76
|
+
r(e);
|
|
77
|
+
let a = o(e, t, i);
|
|
78
|
+
if (a.rowSpan * a.columnSpan === 1) return e;
|
|
79
|
+
let s = e.merges.filter((e) => n(e, a));
|
|
80
|
+
if (s.length === 1 && a.row === s[0].row && a.column === s[0].column && a.rowSpan === s[0].rowSpan && a.columnSpan === s[0].columnSpan) return e;
|
|
81
|
+
let c = {
|
|
82
|
+
...e,
|
|
83
|
+
merges: [...e.merges.filter((e) => !n(e, a)), a]
|
|
84
|
+
};
|
|
85
|
+
return r(c), c;
|
|
86
|
+
}
|
|
87
|
+
function c(e, t) {
|
|
88
|
+
r(e);
|
|
89
|
+
let n = a(e, t);
|
|
90
|
+
return n.rowSpan * n.columnSpan === 1 ? e : {
|
|
91
|
+
...e,
|
|
92
|
+
merges: e.merges.filter((e) => e !== n)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/table-document/insertTableDocumentAxis.ts
|
|
97
|
+
function l(e, t, n, i, a, o = "body") {
|
|
98
|
+
r(e);
|
|
99
|
+
let s = t === "row" ? e.rows.length : e.columns.length;
|
|
100
|
+
if (!Number.isInteger(n) || n < 0 || n > s) throw Error("Некорректная позиция вставки.");
|
|
101
|
+
if (a.length !== (t === "row" ? e.columns.length : e.rows.length)) throw Error("Неверное число новых ячеек.");
|
|
102
|
+
if (t === "row" && (o === "header" ? n > e.headerRowCount : n < e.headerRowCount)) throw Error("Строка вставляется вне выбранной части таблицы.");
|
|
103
|
+
let c = t === "row" ? "rowSpan" : "columnSpan", l = e.merges.map((e) => n <= e[t] ? {
|
|
104
|
+
...e,
|
|
105
|
+
[t]: e[t] + 1
|
|
106
|
+
} : n < e[t] + e[c] ? {
|
|
107
|
+
...e,
|
|
108
|
+
[c]: e[c] + 1
|
|
109
|
+
} : e), u = t === "row" ? {
|
|
110
|
+
...e,
|
|
111
|
+
merges: l,
|
|
112
|
+
headerRowCount: e.headerRowCount + +(o === "header"),
|
|
113
|
+
rows: [
|
|
114
|
+
...e.rows.slice(0, n),
|
|
115
|
+
{
|
|
116
|
+
id: i,
|
|
117
|
+
cells: a
|
|
118
|
+
},
|
|
119
|
+
...e.rows.slice(n)
|
|
120
|
+
]
|
|
121
|
+
} : {
|
|
122
|
+
...e,
|
|
123
|
+
merges: l,
|
|
124
|
+
columns: [
|
|
125
|
+
...e.columns.slice(0, n),
|
|
126
|
+
{
|
|
127
|
+
id: i,
|
|
128
|
+
width: null
|
|
129
|
+
},
|
|
130
|
+
...e.columns.slice(n)
|
|
131
|
+
],
|
|
132
|
+
rows: e.rows.map((e, t) => ({
|
|
133
|
+
...e,
|
|
134
|
+
cells: [
|
|
135
|
+
...e.cells.slice(0, n),
|
|
136
|
+
a[t],
|
|
137
|
+
...e.cells.slice(n)
|
|
138
|
+
]
|
|
139
|
+
}))
|
|
140
|
+
};
|
|
141
|
+
return r(u), u;
|
|
142
|
+
}
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/table-document/updateTableDocumentCell.ts
|
|
145
|
+
function u(e, t, n) {
|
|
146
|
+
return i(e, {
|
|
147
|
+
...t,
|
|
148
|
+
rowSpan: 1,
|
|
149
|
+
columnSpan: 1
|
|
150
|
+
}), Object.is(e.rows[t.row].cells[t.column].value, n) ? e : {
|
|
151
|
+
...e,
|
|
152
|
+
rows: e.rows.map((e, r) => r === t.row ? {
|
|
153
|
+
...e,
|
|
154
|
+
cells: e.cells.map((e, r) => r === t.column ? {
|
|
155
|
+
...e,
|
|
156
|
+
value: n
|
|
157
|
+
} : e)
|
|
158
|
+
} : e)
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/table-document/tableDocumentAxisGroups.ts
|
|
163
|
+
function d(e, t) {
|
|
164
|
+
r(e);
|
|
165
|
+
let n = t === "row" ? e.rows.length : e.columns.length, i = /* @__PURE__ */ new Set();
|
|
166
|
+
for (let n of e.merges) {
|
|
167
|
+
let e = t === "row" ? n.rowSpan : n.columnSpan;
|
|
168
|
+
for (let r = 1; r < e; r++) i.add(n[t] + r);
|
|
169
|
+
}
|
|
170
|
+
let a = [], o = 0;
|
|
171
|
+
for (let e = 1; e <= n; e++) (e === n || !i.has(e)) && (a.push({
|
|
172
|
+
start: o,
|
|
173
|
+
count: e - o
|
|
174
|
+
}), o = e);
|
|
175
|
+
return a;
|
|
176
|
+
}
|
|
177
|
+
function f(e, t, n) {
|
|
178
|
+
let i = d(e, t).find((e) => n >= e.start && n < e.start + e.count);
|
|
179
|
+
if (!i) throw Error("Группа не найдена.");
|
|
180
|
+
let a = t === "row" ? e.rows.length : e.columns.length;
|
|
181
|
+
if (i.count === a) throw Error("В таблице должна остаться хотя бы одна строка и один столбец.");
|
|
182
|
+
let o = i.start + i.count, s = e.merges.filter((e) => e[t] < i.start || e[t] >= o).map((e) => e[t] < o ? e : {
|
|
183
|
+
...e,
|
|
184
|
+
[t]: e[t] - i.count
|
|
185
|
+
}), c = t === "row" ? {
|
|
186
|
+
...e,
|
|
187
|
+
merges: s,
|
|
188
|
+
rows: e.rows.filter((e, t) => t < i.start || t >= o),
|
|
189
|
+
headerRowCount: e.headerRowCount - (i.start < e.headerRowCount ? i.count : 0)
|
|
190
|
+
} : {
|
|
191
|
+
...e,
|
|
192
|
+
merges: s,
|
|
193
|
+
columns: e.columns.filter((e, t) => t < i.start || t >= o),
|
|
194
|
+
rows: e.rows.map((e) => ({
|
|
195
|
+
...e,
|
|
196
|
+
cells: e.cells.filter((e, t) => t < i.start || t >= o)
|
|
197
|
+
}))
|
|
198
|
+
};
|
|
199
|
+
return r(c), c;
|
|
200
|
+
}
|
|
201
|
+
function p(t, n, i, a) {
|
|
202
|
+
let o = d(t, n), s = o.findIndex((e) => i >= e.start && i < e.start + e.count);
|
|
203
|
+
if (s < 0) throw Error("Группа не найдена.");
|
|
204
|
+
let c = s + a;
|
|
205
|
+
if (c < 0 || c >= o.length || n === "row" && o[s].start < t.headerRowCount != o[c].start < t.headerRowCount) return t;
|
|
206
|
+
let l = e(o, s, a === -1 ? "up" : "down").flatMap((e) => Array.from({ length: e.count }, (t, n) => e.start + n)), u = new Map(l.map((e, t) => [e, t])), f = t.merges.map((e) => ({
|
|
207
|
+
...e,
|
|
208
|
+
[n]: u.get(e[n]) ?? e[n]
|
|
209
|
+
})), p = n === "row" ? {
|
|
210
|
+
...t,
|
|
211
|
+
merges: f,
|
|
212
|
+
rows: l.map((e) => t.rows[e])
|
|
213
|
+
} : {
|
|
214
|
+
...t,
|
|
215
|
+
merges: f,
|
|
216
|
+
columns: l.map((e) => t.columns[e]),
|
|
217
|
+
rows: t.rows.map((e) => ({
|
|
218
|
+
...e,
|
|
219
|
+
cells: l.map((t) => e.cells[t])
|
|
220
|
+
}))
|
|
221
|
+
};
|
|
222
|
+
return r(p), p;
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
225
|
+
export { t as TABLE_DOCUMENT_MAX_CELLS, r as assertTableDocument, i as assertTableDocumentRange, f as deleteTableDocumentAxisGroup, o as expandTableDocumentRange, d as getTableDocumentAxisGroups, a as getTableDocumentCellRange, l as insertTableDocumentAxis, s as mergeTableDocumentCells, p as moveTableDocumentAxisGroup, c as splitTableDocumentCell, n as tableDocumentRangesIntersect, u as updateTableDocumentCell };
|
|
226
|
+
|
|
227
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/table-document/tableDocument.ts","../../src/table-document/tableDocumentGeometry.ts","../../src/table-document/mergeTableDocumentCells.ts","../../src/table-document/insertTableDocumentAxis.ts","../../src/table-document/updateTableDocumentCell.ts","../../src/table-document/tableDocumentAxisGroups.ts"],"sourcesContent":["/** Координаты относятся к полной сетке, включая скрытые объединением ячейки. */\nexport type TableDocumentPosition = { readonly row: number; readonly column: number };\n\n/** Прямоугольная область; spans всегда положительные целые числа. */\nexport type TableDocumentRange = TableDocumentPosition & { readonly rowSpan: number; readonly columnSpan: number };\n\n/** Значение принадлежит приложению; ядро никогда не преобразует и не объединяет содержимое. */\nexport type TableDocumentCell<T> = { readonly id: string; readonly value: T };\nexport type TableDocumentRow<T> = { readonly id: string; readonly cells: readonly TableDocumentCell<T>[] };\nexport type TableDocumentColumn = { readonly id: string; readonly width: number | null };\n\n/**\n * Документ редактора, независимый от TanStack и DOM. Полная матрица хранит все\n * значения; merges содержит только непересекающиеся видимые объединения.\n * Это исключает устаревшие spans у скрытых ячеек после повторного merge/split.\n */\nexport type TableDocument<T> = {\n\treadonly version: 1;\n\treadonly caption: string;\n\treadonly columns: readonly TableDocumentColumn[];\n\treadonly rows: readonly TableDocumentRow<T>[];\n\treadonly merges: readonly TableDocumentRange[];\n\treadonly headerRowCount: number;\n\treadonly hideHeaders: boolean;\n\treadonly showRowNumbers: boolean;\n};\n\n/** Лимит защищает проверки геометрии и UI от случайно огромной сетки. */\nexport const TABLE_DOCUMENT_MAX_CELLS = 20_000;\n","import { TABLE_DOCUMENT_MAX_CELLS, type TableDocument, type TableDocumentPosition, type TableDocumentRange } from \"./tableDocument\";\n\n/** Проверяет пересечение двух включительных прямоугольников полной сетки. */\nexport function tableDocumentRangesIntersect(a: TableDocumentRange, b: TableDocumentRange): boolean {\n\treturn (\n\t\ta.row < b.row + b.rowSpan && b.row < a.row + a.rowSpan && a.column < b.column + b.columnSpan && b.column < a.column + a.columnSpan\n\t);\n}\n\n/** Fail-closed инварианты для команд и внешней runtime-schema приложения. */\nexport function assertTableDocument<T>(document: TableDocument<T>): void {\n\tconst height = document.rows.length;\n\tconst width = document.columns.length;\n\tif (document.version !== 1 || !height || !width || height * width > TABLE_DOCUMENT_MAX_CELLS)\n\t\tthrow new Error(\"Недопустимый размер таблицы.\");\n\tif (!Number.isInteger(document.headerRowCount) || document.headerRowCount < 0 || document.headerRowCount > height)\n\t\tthrow new Error(\"Некорректное число строк заголовка.\");\n\tconst identities = new Set<string>();\n\tconst checkId = (id: string) => {\n\t\tif (!id || identities.has(id)) throw new Error(\"Идентификаторы строк, столбцов и ячеек должны быть уникальными.\");\n\t\tidentities.add(id);\n\t};\n\tfor (const column of document.columns) {\n\t\tcheckId(column.id);\n\t\tif (column.width !== null && (!Number.isFinite(column.width) || column.width <= 0)) throw new Error(\"Некорректная ширина столбца.\");\n\t}\n\tfor (const row of document.rows) {\n\t\tcheckId(row.id);\n\t\tif (row.cells.length !== width) throw new Error(\"Все строки должны иметь одинаковое число ячеек.\");\n\t\trow.cells.forEach((cell) => checkId(cell.id));\n\t}\n\tconst occupied = new Set<number>();\n\tfor (const merge of document.merges) {\n\t\tassertTableDocumentRange(document, merge);\n\t\tif (merge.rowSpan * merge.columnSpan === 1) throw new Error(\"Объединение должно содержать несколько ячеек.\");\n\t\tif (merge.row < document.headerRowCount && merge.row + merge.rowSpan > document.headerRowCount)\n\t\t\tthrow new Error(\"Нельзя объединять заголовок с основной частью таблицы.\");\n\t\tfor (let r = merge.row; r < merge.row + merge.rowSpan; r++) {\n\t\t\tfor (let c = merge.column; c < merge.column + merge.columnSpan; c++) {\n\t\t\t\tconst key = r * width + c;\n\t\t\t\tif (occupied.has(key)) throw new Error(\"Объединённые области пересекаются.\");\n\t\t\t\toccupied.add(key);\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** Проверяет границы до выполнения команды, не исправляя ошибочный ввод молча. */\nexport function assertTableDocumentRange<T>(document: TableDocument<T>, range: TableDocumentRange): void {\n\tif (\n\t\t![range.row, range.column, range.rowSpan, range.columnSpan].every(Number.isInteger) ||\n\t\trange.row < 0 ||\n\t\trange.column < 0 ||\n\t\trange.rowSpan < 1 ||\n\t\trange.columnSpan < 1 ||\n\t\trange.row + range.rowSpan > document.rows.length ||\n\t\trange.column + range.columnSpan > document.columns.length\n\t)\n\t\tthrow new Error(\"Выделение выходит за границы таблицы.\");\n}\n\n/** Возвращает видимый anchor для любой позиции, включая покрытую объединением. */\nexport function getTableDocumentCellRange<T>(document: TableDocument<T>, position: TableDocumentPosition): TableDocumentRange {\n\tconst point = { ...position, rowSpan: 1, columnSpan: 1 };\n\tassertTableDocumentRange(document, point);\n\treturn document.merges.find((merge) => tableDocumentRangesIntersect(merge, point)) ?? point;\n}\n\n/**\n * Замыкает выделение до неподвижной точки. Повторный обход обязателен: новая\n * строка прямоугольника может пересечь ещё одно объединение вне исходной области.\n */\nexport function expandTableDocumentRange<T>(\n\tdocument: TableDocument<T>,\n\tstart: TableDocumentPosition,\n\tend: TableDocumentPosition\n): TableDocumentRange {\n\tlet range = {\n\t\trow: Math.min(start.row, end.row),\n\t\tcolumn: Math.min(start.column, end.column),\n\t\trowSpan: Math.abs(start.row - end.row) + 1,\n\t\tcolumnSpan: Math.abs(start.column - end.column) + 1\n\t};\n\tassertTableDocumentRange(document, range);\n\tlet changed = true;\n\twhile (changed) {\n\t\tchanged = false;\n\t\tfor (const merge of document.merges) {\n\t\t\tif (!tableDocumentRangesIntersect(range, merge)) continue;\n\t\t\tconst row = Math.min(range.row, merge.row);\n\t\t\tconst column = Math.min(range.column, merge.column);\n\t\t\tconst rowSpan = Math.max(range.row + range.rowSpan, merge.row + merge.rowSpan) - row;\n\t\t\tconst columnSpan = Math.max(range.column + range.columnSpan, merge.column + merge.columnSpan) - column;\n\t\t\tif (row !== range.row || column !== range.column || rowSpan !== range.rowSpan || columnSpan !== range.columnSpan) {\n\t\t\t\trange = { row, column, rowSpan, columnSpan };\n\t\t\t\tchanged = true;\n\t\t\t}\n\t\t}\n\t}\n\treturn range;\n}\n","import { type TableDocument, type TableDocumentPosition } from \"./tableDocument\";\nimport {\n\tassertTableDocument,\n\texpandTableDocumentRange,\n\tgetTableDocumentCellRange,\n\ttableDocumentRangesIntersect\n} from \"./tableDocumentGeometry\";\n\n/** Объединяет геометрию; исходные значения и identities всех ячеек остаются неизменными. */\nexport function mergeTableDocumentCells<T>(\n\tdocument: TableDocument<T>,\n\tstart: TableDocumentPosition,\n\tend: TableDocumentPosition\n): TableDocument<T> {\n\tassertTableDocument(document);\n\tconst range = expandTableDocumentRange(document, start, end);\n\tif (range.rowSpan * range.columnSpan === 1) return document;\n\tconst intersected = document.merges.filter((merge) => tableDocumentRangesIntersect(merge, range));\n\tif (\n\t\tintersected.length === 1 &&\n\t\trange.row === intersected[0].row &&\n\t\trange.column === intersected[0].column &&\n\t\trange.rowSpan === intersected[0].rowSpan &&\n\t\trange.columnSpan === intersected[0].columnSpan\n\t)\n\t\treturn document;\n\tconst result = { ...document, merges: [...document.merges.filter((merge) => !tableDocumentRangesIntersect(merge, range)), range] };\n\tassertTableDocument(result);\n\treturn result;\n}\n\n/** Разъединяет область целиком; скрытые значения становятся видны без восстановления из копий. */\nexport function splitTableDocumentCell<T>(document: TableDocument<T>, position: TableDocumentPosition): TableDocument<T> {\n\tassertTableDocument(document);\n\tconst range = getTableDocumentCellRange(document, position);\n\tif (range.rowSpan * range.columnSpan === 1) return document;\n\treturn { ...document, merges: document.merges.filter((merge) => merge !== range) };\n}\n","import { type TableDocument, type TableDocumentCell } from \"./tableDocument\";\nimport { assertTableDocument } from \"./tableDocumentGeometry\";\n\n/**\n * Вставка внутрь объединения увеличивает его span; вставка перед anchor сдвигает\n * область. ID и значения создаются вызывающим кодом до изменения React state.\n */\nexport function insertTableDocumentAxis<T>(\n\tdocument: TableDocument<T>,\n\taxis: \"row\" | \"column\",\n\tindex: number,\n\tid: string,\n\tcells: readonly TableDocumentCell<T>[],\n\tsection: \"header\" | \"body\" = \"body\"\n): TableDocument<T> {\n\tassertTableDocument(document);\n\tconst count = axis === \"row\" ? document.rows.length : document.columns.length;\n\tif (!Number.isInteger(index) || index < 0 || index > count) throw new Error(\"Некорректная позиция вставки.\");\n\tif (cells.length !== (axis === \"row\" ? document.columns.length : document.rows.length)) throw new Error(\"Неверное число новых ячеек.\");\n\tif (axis === \"row\" && (section === \"header\" ? index > document.headerRowCount : index < document.headerRowCount))\n\t\tthrow new Error(\"Строка вставляется вне выбранной части таблицы.\");\n\tconst spanKey = axis === \"row\" ? \"rowSpan\" : \"columnSpan\";\n\tconst merges = document.merges.map((merge) =>\n\t\tindex <= merge[axis]\n\t\t\t? { ...merge, [axis]: merge[axis] + 1 }\n\t\t\t: index < merge[axis] + merge[spanKey]\n\t\t\t\t? { ...merge, [spanKey]: merge[spanKey] + 1 }\n\t\t\t\t: merge\n\t);\n\tconst result: TableDocument<T> =\n\t\taxis === \"row\"\n\t\t\t? {\n\t\t\t\t\t...document,\n\t\t\t\t\tmerges,\n\t\t\t\t\theaderRowCount: document.headerRowCount + (section === \"header\" ? 1 : 0),\n\t\t\t\t\trows: [...document.rows.slice(0, index), { id, cells }, ...document.rows.slice(index)]\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\t...document,\n\t\t\t\t\tmerges,\n\t\t\t\t\tcolumns: [...document.columns.slice(0, index), { id, width: null }, ...document.columns.slice(index)],\n\t\t\t\t\trows: document.rows.map((row, r) => ({\n\t\t\t\t\t\t...row,\n\t\t\t\t\t\tcells: [...row.cells.slice(0, index), cells[r], ...row.cells.slice(index)]\n\t\t\t\t\t}))\n\t\t\t\t};\n\tassertTableDocument(result);\n\treturn result;\n}\n","import { type TableDocument, type TableDocumentPosition } from \"./tableDocument\";\nimport { assertTableDocumentRange } from \"./tableDocumentGeometry\";\n\n/** Меняет ровно одну логическую ячейку; преобразование rich text/документов выполняет приложение. */\nexport function updateTableDocumentCell<T>(document: TableDocument<T>, position: TableDocumentPosition, value: T): TableDocument<T> {\n\tassertTableDocumentRange(document, { ...position, rowSpan: 1, columnSpan: 1 });\n\tif (Object.is(document.rows[position.row].cells[position.column].value, value)) return document;\n\treturn {\n\t\t...document,\n\t\trows: document.rows.map((row, r) =>\n\t\t\tr !== position.row ? row : { ...row, cells: row.cells.map((cell, c) => (c !== position.column ? cell : { ...cell, value })) }\n\t\t)\n\t};\n}\n","import { moveArrayItemByIndex } from \"../array\";\n\nimport { type TableDocument } from \"./tableDocument\";\nimport { assertTableDocument } from \"./tableDocumentGeometry\";\n\n/** Неразрывная группа строк/столбцов; применима к reorder и подтверждению удаления. */\nexport type TableDocumentAxisGroup = { readonly start: number; readonly count: number };\n\n/** Находит безопасные границы без случайных ID и без повторного учёта скрытых spans. */\nexport function getTableDocumentAxisGroups<T>(document: TableDocument<T>, axis: \"row\" | \"column\"): readonly TableDocumentAxisGroup[] {\n\tassertTableDocument(document);\n\tconst length = axis === \"row\" ? document.rows.length : document.columns.length;\n\tconst blocked = new Set<number>();\n\tfor (const merge of document.merges) {\n\t\tconst span = axis === \"row\" ? merge.rowSpan : merge.columnSpan;\n\t\tfor (let offset = 1; offset < span; offset++) blocked.add(merge[axis] + offset);\n\t}\n\tconst groups: TableDocumentAxisGroup[] = [];\n\tlet start = 0;\n\tfor (let i = 1; i <= length; i++) {\n\t\tif (i === length || !blocked.has(i)) {\n\t\t\tgroups.push({ start, count: i - start });\n\t\t\tstart = i;\n\t\t}\n\t}\n\treturn groups;\n}\n\n/** Удаляет только явно выбранную неразрывную группу; UI обязан показать её размер до подтверждения. */\nexport function deleteTableDocumentAxisGroup<T>(document: TableDocument<T>, axis: \"row\" | \"column\", index: number): TableDocument<T> {\n\tconst group = getTableDocumentAxisGroups(document, axis).find((item) => index >= item.start && index < item.start + item.count);\n\tif (!group) throw new Error(\"Группа не найдена.\");\n\tconst length = axis === \"row\" ? document.rows.length : document.columns.length;\n\tif (group.count === length) throw new Error(\"В таблице должна остаться хотя бы одна строка и один столбец.\");\n\tconst end = group.start + group.count;\n\tconst merges = document.merges\n\t\t.filter((merge) => merge[axis] < group.start || merge[axis] >= end)\n\t\t.map((merge) => (merge[axis] < end ? merge : { ...merge, [axis]: merge[axis] - group.count }));\n\tconst result: TableDocument<T> =\n\t\taxis === \"row\"\n\t\t\t? {\n\t\t\t\t\t...document,\n\t\t\t\t\tmerges,\n\t\t\t\t\trows: document.rows.filter((_, r) => r < group.start || r >= end),\n\t\t\t\t\theaderRowCount: document.headerRowCount - (group.start < document.headerRowCount ? group.count : 0)\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\t...document,\n\t\t\t\t\tmerges,\n\t\t\t\t\tcolumns: document.columns.filter((_, c) => c < group.start || c >= end),\n\t\t\t\t\trows: document.rows.map((row) => ({ ...row, cells: row.cells.filter((_, c) => c < group.start || c >= end) }))\n\t\t\t\t};\n\tassertTableDocument(result);\n\treturn result;\n}\n\n/** Меняет местами соседние неразрывные группы, сохраняя порядок внутри объединений. */\nexport function moveTableDocumentAxisGroup<T>(\n\tdocument: TableDocument<T>,\n\taxis: \"row\" | \"column\",\n\tindex: number,\n\tdirection: -1 | 1\n): TableDocument<T> {\n\tconst groups = getTableDocumentAxisGroups(document, axis);\n\tconst current = groups.findIndex((item) => index >= item.start && index < item.start + item.count);\n\tif (current < 0) throw new Error(\"Группа не найдена.\");\n\tconst neighbor = current + direction;\n\tif (neighbor < 0 || neighbor >= groups.length) return document;\n\tif (axis === \"row\" && groups[current].start < document.headerRowCount !== groups[neighbor].start < document.headerRowCount)\n\t\treturn document;\n\tconst ordered = moveArrayItemByIndex(groups, current, direction === -1 ? \"up\" : \"down\");\n\tconst indices = ordered.flatMap((group) => Array.from({ length: group.count }, (_, offset) => group.start + offset));\n\tconst inverse = new Map(indices.map((oldIndex, newIndex) => [oldIndex, newIndex]));\n\tconst merges = document.merges.map((merge) => ({ ...merge, [axis]: inverse.get(merge[axis]) ?? merge[axis] }));\n\tconst result =\n\t\taxis === \"row\"\n\t\t\t? { ...document, merges, rows: indices.map((i) => document.rows[i]) }\n\t\t\t: {\n\t\t\t\t\t...document,\n\t\t\t\t\tmerges,\n\t\t\t\t\tcolumns: indices.map((i) => document.columns[i]),\n\t\t\t\t\trows: document.rows.map((row) => ({ ...row, cells: indices.map((i) => row.cells[i]) }))\n\t\t\t\t};\n\tassertTableDocument(result);\n\treturn result;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type TableDocument, type TableDocumentCell } from "./tableDocument";
|
|
2
|
+
/**
|
|
3
|
+
* Вставка внутрь объединения увеличивает его span; вставка перед anchor сдвигает
|
|
4
|
+
* область. ID и значения создаются вызывающим кодом до изменения React state.
|
|
5
|
+
*/
|
|
6
|
+
export declare function insertTableDocumentAxis<T>(document: TableDocument<T>, axis: "row" | "column", index: number, id: string, cells: readonly TableDocumentCell<T>[], section?: "header" | "body"): TableDocument<T>;
|
|
7
|
+
//# sourceMappingURL=insertTableDocumentAxis.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"insertTableDocumentAxis.d.ts","sourceRoot":"","sources":["../../src/table-document/insertTableDocumentAxis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAG7E;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EACxC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAC1B,IAAI,EAAE,KAAK,GAAG,QAAQ,EACtB,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,SAAS,iBAAiB,CAAC,CAAC,CAAC,EAAE,EACtC,OAAO,GAAE,QAAQ,GAAG,MAAe,GACjC,aAAa,CAAC,CAAC,CAAC,CAkClB"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type TableDocument, type TableDocumentPosition } from "./tableDocument";
|
|
2
|
+
/** Объединяет геометрию; исходные значения и identities всех ячеек остаются неизменными. */
|
|
3
|
+
export declare function mergeTableDocumentCells<T>(document: TableDocument<T>, start: TableDocumentPosition, end: TableDocumentPosition): TableDocument<T>;
|
|
4
|
+
/** Разъединяет область целиком; скрытые значения становятся видны без восстановления из копий. */
|
|
5
|
+
export declare function splitTableDocumentCell<T>(document: TableDocument<T>, position: TableDocumentPosition): TableDocument<T>;
|
|
6
|
+
//# sourceMappingURL=mergeTableDocumentCells.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mergeTableDocumentCells.d.ts","sourceRoot":"","sources":["../../src/table-document/mergeTableDocumentCells.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAQjF,4FAA4F;AAC5F,wBAAgB,uBAAuB,CAAC,CAAC,EACxC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAC1B,KAAK,EAAE,qBAAqB,EAC5B,GAAG,EAAE,qBAAqB,GACxB,aAAa,CAAC,CAAC,CAAC,CAgBlB;AAED,kGAAkG;AAClG,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,qBAAqB,GAAG,aAAa,CAAC,CAAC,CAAC,CAKvH"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Координаты относятся к полной сетке, включая скрытые объединением ячейки. */
|
|
2
|
+
export type TableDocumentPosition = {
|
|
3
|
+
readonly row: number;
|
|
4
|
+
readonly column: number;
|
|
5
|
+
};
|
|
6
|
+
/** Прямоугольная область; spans всегда положительные целые числа. */
|
|
7
|
+
export type TableDocumentRange = TableDocumentPosition & {
|
|
8
|
+
readonly rowSpan: number;
|
|
9
|
+
readonly columnSpan: number;
|
|
10
|
+
};
|
|
11
|
+
/** Значение принадлежит приложению; ядро никогда не преобразует и не объединяет содержимое. */
|
|
12
|
+
export type TableDocumentCell<T> = {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly value: T;
|
|
15
|
+
};
|
|
16
|
+
export type TableDocumentRow<T> = {
|
|
17
|
+
readonly id: string;
|
|
18
|
+
readonly cells: readonly TableDocumentCell<T>[];
|
|
19
|
+
};
|
|
20
|
+
export type TableDocumentColumn = {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly width: number | null;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Документ редактора, независимый от TanStack и DOM. Полная матрица хранит все
|
|
26
|
+
* значения; merges содержит только непересекающиеся видимые объединения.
|
|
27
|
+
* Это исключает устаревшие spans у скрытых ячеек после повторного merge/split.
|
|
28
|
+
*/
|
|
29
|
+
export type TableDocument<T> = {
|
|
30
|
+
readonly version: 1;
|
|
31
|
+
readonly caption: string;
|
|
32
|
+
readonly columns: readonly TableDocumentColumn[];
|
|
33
|
+
readonly rows: readonly TableDocumentRow<T>[];
|
|
34
|
+
readonly merges: readonly TableDocumentRange[];
|
|
35
|
+
readonly headerRowCount: number;
|
|
36
|
+
readonly hideHeaders: boolean;
|
|
37
|
+
readonly showRowNumbers: boolean;
|
|
38
|
+
};
|
|
39
|
+
/** Лимит защищает проверки геометрии и UI от случайно огромной сетки. */
|
|
40
|
+
export declare const TABLE_DOCUMENT_MAX_CELLS = 20000;
|
|
41
|
+
//# sourceMappingURL=tableDocument.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tableDocument.d.ts","sourceRoot":"","sources":["../../src/table-document/tableDocument.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,MAAM,MAAM,qBAAqB,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtF,qEAAqE;AACrE,MAAM,MAAM,kBAAkB,GAAG,qBAAqB,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnH,+FAA+F;AAC/F,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC;AAC9E,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAA;CAAE,CAAC;AAC3G,MAAM,MAAM,mBAAmB,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAEzF;;;;GAIG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAC9B,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACjD,QAAQ,CAAC,IAAI,EAAE,SAAS,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9C,QAAQ,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAC/C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,wBAAwB,QAAS,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type TableDocument } from "./tableDocument";
|
|
2
|
+
/** Неразрывная группа строк/столбцов; применима к reorder и подтверждению удаления. */
|
|
3
|
+
export type TableDocumentAxisGroup = {
|
|
4
|
+
readonly start: number;
|
|
5
|
+
readonly count: number;
|
|
6
|
+
};
|
|
7
|
+
/** Находит безопасные границы без случайных ID и без повторного учёта скрытых spans. */
|
|
8
|
+
export declare function getTableDocumentAxisGroups<T>(document: TableDocument<T>, axis: "row" | "column"): readonly TableDocumentAxisGroup[];
|
|
9
|
+
/** Удаляет только явно выбранную неразрывную группу; UI обязан показать её размер до подтверждения. */
|
|
10
|
+
export declare function deleteTableDocumentAxisGroup<T>(document: TableDocument<T>, axis: "row" | "column", index: number): TableDocument<T>;
|
|
11
|
+
/** Меняет местами соседние неразрывные группы, сохраняя порядок внутри объединений. */
|
|
12
|
+
export declare function moveTableDocumentAxisGroup<T>(document: TableDocument<T>, axis: "row" | "column", index: number, direction: -1 | 1): TableDocument<T>;
|
|
13
|
+
//# sourceMappingURL=tableDocumentAxisGroups.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tableDocumentAxisGroups.d.ts","sourceRoot":"","sources":["../../src/table-document/tableDocumentAxisGroups.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD,uFAAuF;AACvF,MAAM,MAAM,sBAAsB,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAExF,wFAAwF;AACxF,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,SAAS,sBAAsB,EAAE,CAiBnI;AAED,uGAAuG;AACvG,wBAAgB,4BAA4B,CAAC,CAAC,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,GAAG,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,CAyBnI;AAED,uFAAuF;AACvF,wBAAgB,0BAA0B,CAAC,CAAC,EAC3C,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAC1B,IAAI,EAAE,KAAK,GAAG,QAAQ,EACtB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,GACf,aAAa,CAAC,CAAC,CAAC,CAuBlB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type TableDocument, type TableDocumentPosition, type TableDocumentRange } from "./tableDocument";
|
|
2
|
+
/** Проверяет пересечение двух включительных прямоугольников полной сетки. */
|
|
3
|
+
export declare function tableDocumentRangesIntersect(a: TableDocumentRange, b: TableDocumentRange): boolean;
|
|
4
|
+
/** Fail-closed инварианты для команд и внешней runtime-schema приложения. */
|
|
5
|
+
export declare function assertTableDocument<T>(document: TableDocument<T>): void;
|
|
6
|
+
/** Проверяет границы до выполнения команды, не исправляя ошибочный ввод молча. */
|
|
7
|
+
export declare function assertTableDocumentRange<T>(document: TableDocument<T>, range: TableDocumentRange): void;
|
|
8
|
+
/** Возвращает видимый anchor для любой позиции, включая покрытую объединением. */
|
|
9
|
+
export declare function getTableDocumentCellRange<T>(document: TableDocument<T>, position: TableDocumentPosition): TableDocumentRange;
|
|
10
|
+
/**
|
|
11
|
+
* Замыкает выделение до неподвижной точки. Повторный обход обязателен: новая
|
|
12
|
+
* строка прямоугольника может пересечь ещё одно объединение вне исходной области.
|
|
13
|
+
*/
|
|
14
|
+
export declare function expandTableDocumentRange<T>(document: TableDocument<T>, start: TableDocumentPosition, end: TableDocumentPosition): TableDocumentRange;
|
|
15
|
+
//# sourceMappingURL=tableDocumentGeometry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tableDocumentGeometry.d.ts","sourceRoot":"","sources":["../../src/table-document/tableDocumentGeometry.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,aAAa,EAAE,KAAK,qBAAqB,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEpI,6EAA6E;AAC7E,wBAAgB,4BAA4B,CAAC,CAAC,EAAE,kBAAkB,EAAE,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAIlG;AAED,6EAA6E;AAC7E,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI,CAmCvE;AAED,kFAAkF;AAClF,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAWvG;AAED,kFAAkF;AAClF,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,qBAAqB,GAAG,kBAAkB,CAI5H;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EACzC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAC1B,KAAK,EAAE,qBAAqB,EAC5B,GAAG,EAAE,qBAAqB,GACxB,kBAAkB,CAwBpB"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type TableDocument, type TableDocumentPosition } from "./tableDocument";
|
|
2
|
+
/** Меняет ровно одну логическую ячейку; преобразование rich text/документов выполняет приложение. */
|
|
3
|
+
export declare function updateTableDocumentCell<T>(document: TableDocument<T>, position: TableDocumentPosition, value: T): TableDocument<T>;
|
|
4
|
+
//# sourceMappingURL=updateTableDocumentCell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updateTableDocumentCell.d.ts","sourceRoot":"","sources":["../../src/table-document/updateTableDocumentCell.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAGjF,qGAAqG;AACrG,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,qBAAqB,EAAE,KAAK,EAAE,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CASlI"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryuzaki13/react-foundation-lib",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Reusable React and TypeScript foundation utilities, hooks, and primitives.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ryuzaki13",
|
|
@@ -165,6 +165,10 @@
|
|
|
165
165
|
"types": "./dist/table/index.d.ts",
|
|
166
166
|
"import": "./dist/table/index.js"
|
|
167
167
|
},
|
|
168
|
+
"./table-document": {
|
|
169
|
+
"types": "./dist/table-document/index.d.ts",
|
|
170
|
+
"import": "./dist/table-document/index.js"
|
|
171
|
+
},
|
|
168
172
|
"./three-scene": {
|
|
169
173
|
"types": "./dist/three-scene/index.d.ts",
|
|
170
174
|
"import": "./dist/three-scene/index.js"
|
package/src/dom/README.mdx
CHANGED
|
@@ -1279,7 +1279,7 @@ console.log(overlayRef === existingRef); // true.
|
|
|
1279
1279
|
2. пытается прочитать `overlayRef.current`;
|
|
1280
1280
|
3. если ref ещё пустой, повторяет попытку через `requestAnimationFrame`;
|
|
1281
1281
|
4. когда container найден, вычисляет initial focus;
|
|
1282
|
-
5. вызывает `.focus()`, только если target — подключённый `HTMLElement`;
|
|
1282
|
+
5. вызывает `.focus({ preventScroll: true })`, только если target — подключённый `HTMLElement`;
|
|
1283
1283
|
6. при `trapFocus: true` добавляет `keydown` listener на container.
|
|
1284
1284
|
|
|
1285
1285
|
Если `active: true`, но container никогда не монтируется, requestAnimationFrame retry продолжается до cleanup. Всегда согласовывайте `active` с реальным mount overlay.
|
|
@@ -1421,7 +1421,10 @@ Container должен быть программно focusable.
|
|
|
1421
1421
|
restoreFocusTarget?.() ?? previousActiveElement;
|
|
1422
1422
|
```
|
|
1423
1423
|
|
|
1424
|
-
Focus возвращается, только если выбранный element всё ещё `isConnected
|
|
1424
|
+
Focus возвращается, только если выбранный element всё ещё `isConnected`,
|
|
1425
|
+
через `.focus({ preventScroll: true })`. Открытие и закрытие overlay не меняют
|
|
1426
|
+
прокрутку документа, в том числе пока портал ещё позиционируется. Переходы
|
|
1427
|
+
по Tab сохраняют обычную прокрутку к выбранному элементу внутри длинного overlay.
|
|
1425
1428
|
|
|
1426
1429
|
Типичный сценарий:
|
|
1427
1430
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Документ редактируемой таблицы
|
|
2
|
+
|
|
3
|
+
Public API: `@ryuzaki13/react-foundation-lib/table-document`.
|
|
4
|
+
|
|
5
|
+
Чистое ядро для конструктора таблиц. Полная прямоугольная матрица хранит значения
|
|
6
|
+
всех ячеек, а отдельный массив `merges` — непересекающиеся объединения. Merge
|
|
7
|
+
никогда не удаляет содержимое покрытых ячеек; split возвращает их видимость.
|
|
8
|
+
Значения generic: приложение владеет rich text, ссылками, DTO и сериализацией.
|
|
9
|
+
|
|
10
|
+
Команды возвращают новый документ без мутации исходного. Неизменённые строки
|
|
11
|
+
и значения сохраняются по ссылке, что позволяет строить историю undo/redo.
|
|
12
|
+
Выделение замыкается по всем пересечённым объединениям до неподвижной точки.
|
|
13
|
+
Заголовок не объединяется и не переставляется с body. Вставка внутри области
|
|
14
|
+
увеличивает span; удаление и перемещение работают с неразрывной группой.
|
|
15
|
+
Перед удалением UI должен показать фактический размер группы и подтвердить его.
|
|
16
|
+
|
|
17
|
+
`assertTableDocument` проверяет геометрию типизированного документа, но не
|
|
18
|
+
заменяет runtime-парсер неизвестного JSON на внешней границе приложения.
|
|
19
|
+
Лимит — 20 000 логических ячеек. ID должны быть непустыми и уникальными во всём
|
|
20
|
+
документе. Генерацию ID выполнять в обработчике события, не во время render.
|
|
21
|
+
|
|
22
|
+
Это новый additive API, существующий `/table` и его selection contracts не
|
|
23
|
+
изменяются. Миграцию базы потребителя пакет не выполняет.
|