import{n as e}from"./rolldown-runtime-C0FnF6B9.js";import{n as t}from"./iframe-GrlyTB2d.js";import{i as n,r}from"./react-qN2cStNd.js";import{l as i,m as a}from"./blocks-DkLPipTQ.js";function o(e){let t={code:`code`,h1:`h1`,h2:`h2`,p:`p`,pre:`pre`,table:`table`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,...n(),...e.components};return(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(i,{title:`Foundation Lib/Чистые utilities и contracts`}),`
`,(0,c.jsx)(t.h1,{id:`чистые-utilities-и-contracts`,children:`Чистые utilities и contracts`}),`
`,(0,c.jsx)(t.p,{children:`Эти API не создают global state и обычно не требуют React. Они полезны в entity/feature code, если задача остаётся предметно-независимой. Не переносите в них доменные правила только ради повторного использования.`}),`
`,(0,c.jsx)(t.h2,{id:`коллекции-порядок-и-ограниченная-история`,children:`Коллекции, порядок и ограниченная история`}),`
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { arrayGroupBy, arrayUniqueBy, moveArrayItem } from "@ryuzaki13/react-foundation-lib/array";
import { pushBoundedCopyStackItem } from "@ryuzaki13/react-foundation-lib/bounded-copy-stack";
`})}),`
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`API`}),(0,c.jsx)(t.th,{children:`Поведение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`arrayGroupBy`}),`, `,(0,c.jsx)(t.code,{children:`arrayGroupByToArray`}),`, `,(0,c.jsx)(t.code,{children:`arrayToMap`})]}),(0,c.jsxs)(t.td,{children:[`Группируют/индексируют по полю объекта. `,(0,c.jsx)(t.code,{children:`arrayToMap`}),` оставляет первое значение ключа.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`arrayUniqueBy`}),`, `,(0,c.jsx)(t.code,{children:`arrayDeduplicate`})]}),(0,c.jsx)(t.td,{children:`Immutable deduplication с сохранением первого встретившегося значения/порядка.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`filterAndDeduplicateIds`}),`, `,(0,c.jsx)(t.code,{children:`appendMissingIds`}),`, `,(0,c.jsx)(t.code,{children:`pickExistingMapValues`})]}),(0,c.jsx)(t.td,{children:`Helpers для stable id lists и ordered Map values.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`arraysEqual`})}),(0,c.jsxs)(t.td,{children:[`Строго сравнивает длину, порядок и значения по `,(0,c.jsx)(t.code,{children:`===`}),`.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`moveItem`}),`, `,(0,c.jsx)(t.code,{children:`moveArrayItem`}),`, `,(0,c.jsx)(t.code,{children:`moveArrayItemByIndex`})]}),(0,c.jsx)(t.td,{children:`Возвращают новую переставленную коллекцию; invalid indices не мутируют input.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`normalizeObjects`}),`, `,(0,c.jsx)(t.code,{children:`normalizeStringArray`}),`, `,(0,c.jsx)(t.code,{children:`addUnique`})]}),(0,c.jsx)(t.td,{children:`Explicit canonicalization для внешней/сохранённой структуры — не вызывайте её внутри literal draft setter.`})]})]})]}),`
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`pushBoundedCopyStackItem(stack, input, options?)`}),` добавляет/поднимает item по `,(0,c.jsx)(t.code,{children:`fingerprint`}),` и ограничивает размер (default `,(0,c.jsx)(t.code,{children:`DEFAULT_BOUNDED_COPY_STACK_SIZE = 3`}),`). `,(0,c.jsx)(t.code,{children:`getBoundedCopyStackCandidates`}),` фильтрует готовый stack predicate-ом. `,(0,c.jsx)(t.code,{children:`BoundedCopyStackItem`}),`, `,(0,c.jsx)(t.code,{children:`BoundedCopyStackPushInput`}),`, `,(0,c.jsx)(t.code,{children:`BoundedCopyStackOptions`}),` и `,(0,c.jsx)(t.code,{children:`BoundedCopyStackPushResult`}),` описывают его serializable contract.`]}),`
`,(0,c.jsx)(t.h2,{id:`значения-шкалы-и-календарные-сегменты`,children:`Значения, шкалы и календарные сегменты`}),`
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { parseDateSegmentMask, indexedSegmentsToDate } from "@ryuzaki13/react-foundation-lib/date-segments";
import { snapNumberScaleValue } from "@ryuzaki13/react-foundation-lib/number-scale";
import { resolveRangeOutputValue } from "@ryuzaki13/react-foundation-lib/range-output";
`})}),`
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Entry point`}),(0,c.jsx)(t.th,{children:`Основной API`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`date-segments`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`parseDateSegmentMask`}),`, `,(0,c.jsx)(t.code,{children:`isEditableDateSegment`}),`, `,(0,c.jsx)(t.code,{children:`dateToIndexedSegmentValues`}),`, `,(0,c.jsx)(t.code,{children:`areAllDateSegmentsEmpty`}),`, `,(0,c.jsx)(t.code,{children:`indexedSegmentsToDate`}),`. `,(0,c.jsx)(t.code,{children:`indexedSegmentsToDate`}),` строго отвергает невозможную дату/время; `,(0,c.jsx)(t.code,{children:`DateMaskSegment`}),` описывает editable/literal segments.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`number-scale`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`resolveNumberScaleBounds`}),`, `,(0,c.jsx)(t.code,{children:`clampNumberScaleValue`}),`, `,(0,c.jsx)(t.code,{children:`normalizeNumberScaleStep`}),`, `,(0,c.jsx)(t.code,{children:`prepareNumberScaleMarks`}),`, `,(0,c.jsx)(t.code,{children:`snapNumberScaleValueToStep`}),`, `,(0,c.jsx)(t.code,{children:`snapNumberScaleValueToMarks`}),`, `,(0,c.jsx)(t.code,{children:`snapNumberScaleValue`}),`, `,(0,c.jsx)(t.code,{children:`valueToNumberScalePercent`}),`, `,(0,c.jsx)(t.code,{children:`percentToNumberScaleValue`}),`, `,(0,c.jsx)(t.code,{children:`percentToSnappedNumberScaleValue`}),`, `,(0,c.jsx)(t.code,{children:`offsetNumberScaleValue`}),`.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`currency`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`resolveCurrencyModeFromODataParameters`}),` и `,(0,c.jsx)(t.code,{children:`resolveCurrencyAwareLabel`}),`; это label policy по wrapped OData parameters, не currency formatter.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`range-output`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`readRangeOutputValueFallback`}),` и `,(0,c.jsx)(t.code,{children:`resolveRangeOutputValue`}),`; подставляет fallback только для `,(0,c.jsx)(t.code,{children:`null`}),` открытой границы и не меняет исходный UI range.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`presets`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`normalizePresetIds`}),`, `,(0,c.jsx)(t.code,{children:`resolvePresetOptionsByIds`}),`, `,(0,c.jsx)(t.code,{children:`getPresetOption`}),`, `,(0,c.jsx)(t.code,{children:`PresetOption`}),`; помогает на save/restore boundary для известного набора IDs.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`string-comparison`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`compareStrings(left, right)`}),` использует fixed Russian numeric/case-insensitive collation с deterministic fallback.`]})]})]})]}),`
`,(0,c.jsxs)(t.p,{children:[`Для date/number UI formatting используйте `,(0,c.jsx)(t.code,{children:`formatters`}),`, а не `,(0,c.jsx)(t.code,{children:`number-scale`}),`/`,(0,c.jsx)(t.code,{children:`currency`}),`.`]}),`
`,(0,c.jsx)(t.h2,{id:`clone-строки-keyboard-и-trees`,children:`Clone, строки, keyboard и trees`}),`
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { cn, deepCloneWithoutFunctions, deepCopyWithoutFunctions, stableStringify } from "@ryuzaki13/react-foundation-lib/utils";
`})}),`
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`API`}),(0,c.jsx)(t.th,{children:`Контракт`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`cn(...args)`})}),(0,c.jsxs)(t.td,{children:[`Собирает className из строк и conditional object. `,(0,c.jsx)(t.code,{children:`classNames`}),` — deprecated alias; новый код использует `,(0,c.jsx)(t.code,{children:`cn`}),`.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`stableStringify(value)`})}),(0,c.jsx)(t.td,{children:`JSON-like stable serialization с сортировкой object keys — для key/comparison, не как универсальный persistence format.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`deepCopyWithoutFunctions(value, options?)`})}),(0,c.jsxs)(t.td,{children:[`Копирует Date, arrays и plain objects, поддерживает cycles; функции пропускает. Unsupported object бросает `,(0,c.jsx)(t.code,{children:`TypeError`}),`. Предназначен для serializable plain config.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`deepCloneWithoutFunctions(value, options?)`})}),(0,c.jsx)(t.td,{children:`Расширенный clone: Date, RegExp, Map, Set, cycles, sparse arrays и optional prototype. Функции пропускает.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`createLazyComponent`}),`, `,(0,c.jsx)(t.code,{children:`childrenCount`})]}),(0,c.jsx)(t.td,{children:`React-adjacent helpers для stable lazy component и correct children count.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`keyboardActivationKeys`}),`, `,(0,c.jsx)(t.code,{children:`isKeyboardActivationKey`}),`, `,(0,c.jsx)(t.code,{children:`handleKeyboardActivation`})]}),(0,c.jsx)(t.td,{children:`Keyboard activation contract для custom control.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`getRovingFocusTargetIndex`}),`, `,(0,c.jsx)(t.code,{children:`findFirstEnabledIndex`}),`, `,(0,c.jsx)(t.code,{children:`findLastEnabledIndex`}),`, `,(0,c.jsx)(t.code,{children:`findNextEnabledIndex`})]}),(0,c.jsx)(t.td,{children:`Pure navigation для listbox/select с disabled/wrap.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`deepCopyNodes`}),`, `,(0,c.jsx)(t.code,{children:`deepCopyTree`})]}),(0,c.jsx)(t.td,{children:`Копирование tree nodes с standard/custom children field.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`toBase64`}),`, `,(0,c.jsx)(t.code,{children:`encodeBase64`}),`, `,(0,c.jsx)(t.code,{children:`logError`})]}),(0,c.jsx)(t.td,{children:`Unicode-safe base64 и centralised error logging.`})]})]})]}),`
`,(0,c.jsxs)(t.p,{children:[`Clone не является нормализацией: он создаёт независимую структуру в пределах собственного contract и не должен применять trim, defaults, deduplication или reorder к пользовательскому draft. Для plain serializable config используйте `,(0,c.jsx)(t.code,{children:`deepCopyWithoutFunctions`}),`, а не `,(0,c.jsx)(t.code,{children:`structuredClone`}),`.`]}),`
`,(0,c.jsx)(t.h2,{id:`runtime-guards-и-базовые-типы`,children:`Runtime guards и базовые типы`}),`
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { asRecord, isRecord, isSafe, readPositiveInteger } from "@ryuzaki13/react-foundation-lib/validators";
import type { BaseType, RangeType, RowRecord, State } from "@ryuzaki13/react-foundation-lib/types";
`})}),`
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`API`}),(0,c.jsx)(t.th,{children:`Когда использовать`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`isSafe`})}),(0,c.jsxs)(t.td,{children:[`Убирает только `,(0,c.jsx)(t.code,{children:`null`}),`/`,(0,c.jsx)(t.code,{children:`undefined`}),` из union.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`isObject`}),`, `,(0,c.jsx)(t.code,{children:`isRecord`}),`, `,(0,c.jsx)(t.code,{children:`asRecord`}),`, `,(0,c.jsx)(t.code,{children:`isPlainObject`})]}),(0,c.jsx)(t.td,{children:`Narrow unknown external value до object/record; array не считается record.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`readPositiveInteger`})}),(0,c.jsxs)(t.td,{children:[`Возвращает positive integer или `,(0,c.jsx)(t.code,{children:`undefined`}),` без unsafe cast.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`validateUrl`})}),(0,c.jsx)(t.td,{children:`Возвращает validation result для строки URL.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`IMAGE_EXTENSIONS`}),`, `,(0,c.jsx)(t.code,{children:`isImageExtension`})]}),(0,c.jsx)(t.td,{children:`Проверка расширения без учёта регистра; не MIME/security validation.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`isDomReference`})}),(0,c.jsxs)(t.td,{children:[`Floating UI reference → DOM `,(0,c.jsx)(t.code,{children:`Element`}),` guard.`]})]})]})]}),`
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`State`}),`, `,(0,c.jsx)(t.code,{children:`AbapBoolean`}),`, `,(0,c.jsx)(t.code,{children:`Primitive`}),`, `,(0,c.jsx)(t.code,{children:`BaseType`}),`, `,(0,c.jsx)(t.code,{children:`ArrayType`}),`, `,(0,c.jsx)(t.code,{children:`ObjectType`}),`, `,(0,c.jsx)(t.code,{children:`InputType`}),`, `,(0,c.jsx)(t.code,{children:`RangeType`}),`, `,(0,c.jsx)(t.code,{children:`RowRecord`}),`, `,(0,c.jsx)(t.code,{children:`isBaseType`}),` — общие low-level contracts. Не используйте `,(0,c.jsx)(t.code,{children:`RowRecord`}),` как модель конкретной доменной сущности.`]}),`
`,(0,c.jsx)(t.h2,{id:`идентификаторы-http-xml-seo-и-form-errors`,children:`Идентификаторы, HTTP, XML, SEO и form errors`}),`
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Entry point`}),(0,c.jsx)(t.th,{children:`API`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`crypto`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`hashString`}),`, `,(0,c.jsx)(t.code,{children:`hashString128`}),`, `,(0,c.jsx)(t.code,{children:`hashString128Base64Url`}),`, `,(0,c.jsx)(t.code,{children:`stringToElementId`}),`, `,(0,c.jsx)(t.code,{children:`uuidv4`}),`. Hash служит для stable client key, не криптографической защиты.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`session-storage`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`getSessionStorageId(key, createId?)`}),` — scoped browser-tab id; не authentication token.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`http`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`createNoStoreHeaders(init?)`}),` создаёт Headers с no-store; `,(0,c.jsx)(t.code,{children:`createTextHttpResponse(options)`}),` создаёт text `,(0,c.jsx)(t.code,{children:`Response`}),`.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`xml`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`escapeXmlValue`}),`, `,(0,c.jsx)(t.code,{children:`buildXmlFields`}),`, `,(0,c.jsx)(t.code,{children:`getXmlTagText`}),` для небольших XML fragments. Не применяйте regex helper как универсальный XML parser для untrusted complex XML.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`seo`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`createSeoHead(options)`}),`, `,(0,c.jsx)(t.code,{children:`createRouteHead(type, path, params)`}),` создают `,(0,c.jsx)(t.code,{children:`meta`}),`, `,(0,c.jsx)(t.code,{children:`links`}),`, JSON-LD `,(0,c.jsx)(t.code,{children:`scripts`}),`; host router/head owner решает, как применить результат.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`form`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`readFirstFormError(errors?)`}),` выбирает первое readable error message из unknown error array.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`error`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`createMissingContextErrorMessage`}),`, `,(0,c.jsx)(t.code,{children:`createMissingContextError`}),` формируют единообразную ошибку отсутствующего React Provider.`]})]})]})]}),`
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`fingerprint`}),` и `,(0,c.jsx)(t.code,{children:`notifications`}),` описаны на странице Runtime/state, потому что имеют browser/global lifecycle.`]})]})}function s(e={}){let{wrapper:t}={...n(),...e.components};return t?(0,c.jsx)(t,{...e,children:(0,c.jsx)(o,{...e})}):o(e)}var c;function l(){return(l=e((()=>{c=t(),r(),a()})))()}l();export{s as default};