UNPKG

@baleada/vue-features

Version:

User interface features composed from Baleada Composition functions and the Vue 3 Composition API

1,534 lines (1,500 loc) 221 kB
import { pipe, reverse, join, some, unique, findIndex, at, find, includes, toLength, map, min, max, filter, every } from 'lazy-collections'; import { isRef, computed, unref, nextTick, useId, onMounted, watch, shallowRef, onBeforeUpdate, watchEffect, ref, provide, inject, onScopeDispose, getCurrentInstance } from 'vue'; import { createEntries, createClip, Listenable, createKeycomboMatch, createDeepEqual, createList, createComputedStyle, createConcat, createFocusable, Navigateable, createMap, Pickable, createFilter, createPointerhover, createOmit, createKeys, createKeypress, createPointerpress, createReduce, createSlice, createResults, createAssociativeArrayHas, createAssociativeArrayValue, createAssociativeArraySet, createDeepMerge, createClamp } from '@baleada/logic'; import { useNavigateable, useStoreable, useDelayable, useCompleteable, usePickable, useListenable } from '@baleada/vue-composition'; class Plane extends Array { constructor(...initial) { super(...initial); } get({ row, column }) { return this[row]?.[column]; } set({ row, column }, value) { (this[row] ?? (this[row] = []))[column] = value; } *points() { for (let row = 0; row < this.length; row++) { for (let column = 0; column < this[0].length; column++) { yield { row, column, point: this.get({ row, column }) }; } } } } function narrowElement(extendable) { if (isRef(extendable)) return extendable; if (typeof extendable === "function") return computed(extendable); return extendable.root.element; } function narrowListenOptions(options) { if (!options) { return {}; } if ("observer" in options && "root" in options.observer) { return { observer: { ...options.observer, root: unref(options.observer.root) } }; } return options; } function narrowReactivePlane(rendered) { if (isRef(rendered)) { if (rendered.value instanceof Plane) { return computed(() => rendered.value); } if (Array.isArray(rendered.value)) { return computed(() => new Plane(rendered.value)); } return computed(() => new Plane( rendered.value ? [rendered.value] : [] )); } if (rendered instanceof Plane) { return computed(() => rendered); } if (Array.isArray(rendered)) { return computed(() => new Plane(rendered)); } return computed(() => new Plane([rendered])); } function narrowTransitionOption(elementOrListOrPlane, optionOrOptionCreator) { return typeof optionOrOptionCreator === "function" ? optionOrOptionCreator(elementOrListOrPlane) : optionOrOptionCreator; } function narrowWatchSources(rawWatchSources) { return rawWatchSources === void 0 ? [] : Array.isArray(rawWatchSources) ? rawWatchSources : [rawWatchSources]; } function toRenderedKind(rendered) { if (isRef(rendered)) { if (rendered.value instanceof Plane) return "plane"; if (Array.isArray(rendered.value)) return "list"; return "element"; } if (rendered instanceof Plane) return "plane"; if (Array.isArray(rendered)) return "list"; return "element"; } function toEntries(object) { return createEntries()(object); } function toInputEffectNames({ previousString, newString, lastRecordedString, previousSelection, newSelection }) { const change = { operation: newString.length - previousString.length > 0 && "add" || newString.length - previousString.length < 0 && "remove" || "replace", quantity: Math.abs(newString.length - previousString.length) > 1 ? "multiple" : "single", previousStatus: lastRecordedString === previousString ? "recorded" : "unrecorded", newLastCharacter: /\s/.test(newString[newSelection.start - 1]) ? "whitespace" : "not whitespace", previousLastCharacter: /\s/.test(previousString[previousSelection.start - 1]) ? "whitespace" : "not whitespace" }; if (change.operation === "replace" && change.previousStatus === "recorded") { return ["recordNew"]; } if (change.operation === "replace" && change.previousStatus === "unrecorded") { return ["recordPrevious", "recordNew"]; } if (change.operation === "add" && change.quantity === "single" && change.newLastCharacter === "not whitespace" && change.previousStatus === "recorded") { return ["sync"]; } if (change.operation === "add" && change.quantity === "single" && change.newLastCharacter === "not whitespace" && change.previousStatus === "unrecorded") { if (lastRecordedString.length > previousString.length) { return ["recordPrevious", "recordNew"]; } return ["sync"]; } if (change.operation === "add" && change.quantity === "single" && change.newLastCharacter === "whitespace" && change.previousStatus === "recorded") { return ["recordNew"]; } if (change.operation === "add" && change.quantity === "single" && change.newLastCharacter === "whitespace" && change.previousStatus === "unrecorded") { return ["recordPrevious", "recordNew"]; } if (change.operation === "add" && change.quantity === "multiple" && change.previousStatus === "recorded") { return ["recordNew"]; } if (change.operation === "add" && change.quantity === "multiple" && change.previousStatus === "unrecorded") { return ["recordPrevious", "recordNew"]; } if (change.operation === "remove" && change.quantity === "single" && change.previousLastCharacter === "not whitespace" && change.previousStatus === "recorded") { return ["sync"]; } if (change.operation === "remove" && change.quantity === "single" && change.previousLastCharacter === "not whitespace" && change.previousStatus === "unrecorded") { if (lastRecordedString.length > previousString.length) { return ["sync"]; } return ["recordPrevious", "sync"]; } if (change.operation === "remove" && change.quantity === "single" && change.previousLastCharacter === "whitespace" && change.previousStatus === "recorded") { return ["recordNew"]; } if (change.operation === "remove" && change.quantity === "single" && change.previousLastCharacter === "whitespace" && change.previousStatus === "unrecorded") { return ["recordPrevious", "recordNew"]; } if (change.operation === "remove" && change.quantity === "multiple" && change.previousStatus === "recorded") { return ["recordNew"]; } if (change.operation === "remove" && change.quantity === "multiple" && change.previousStatus === "unrecorded") { return ["recordPrevious", "recordNew"]; } } function toSymmetricalCompletion({ punctuation, segment }) { return segment.startsWith(punctuation) && segment.endsWith(punctuation) ? segment.slice(punctuation.length, segment.length - punctuation.length) : `${punctuation}${segment}${punctuation}`; } function toMappedCompletion({ punctuation, segment }) { const lines = segment.split("\n"), isCompleted = lines.every((line, index) => { if (typeof punctuation === "function") { return line.startsWith(punctuation(index)); } return line.startsWith(punctuation); }); if (isCompleted) { return lines.map((line, index) => { if (typeof punctuation === "function") { return line.slice(punctuation(index).length); } return line.slice(punctuation.length); }).join("\n"); } return lines.map((line, index) => { if (typeof punctuation === "function") { return `${punctuation(index)}${line}`; } return `${punctuation}${line}`; }).join("\n"); } function toMirroredCompletion({ punctuation, segment }) { const mirrored = pipe( reverse(), join("") )(punctuation.split("")); return segment.startsWith(punctuation) && segment.endsWith(mirrored) ? segment.slice(punctuation.length, segment.length - mirrored.length) : `${punctuation}${segment}${mirrored}`; } function toHeadingCompletion({ level, segment }) { const hashes = (() => { let hashes2 = ""; for (let i = 0; i < level; i++) { hashes2 += "#"; } return hashes2; })(); return segment.startsWith(`${hashes} `) ? segment.slice(`${hashes} `.length) : `${hashes} ${toWithoutHeading(segment)}`; } const toWithoutHeading = createClip(/^#+\s+/); function toHorizontalRuleCompletion({ character, segment }) { return `${segment}${segment.length > 0 ? "\n" : ""}${character}${character}${character} `; } function show(elementOrListOrPlane, condition, options = {}) { const originalStyles = /* @__PURE__ */ new WeakMap(), cancels = /* @__PURE__ */ new WeakMap(), statuses = /* @__PURE__ */ new WeakMap(), { transition = {} } = options, affordanceElementKind = toRenderedKind(elementOrListOrPlane), { appear = {}, enter = {}, leave = {} } = transition, transitionTypes = toTransitionTypes({ appear, enter, leave }); onRenderedBind( elementOrListOrPlane, (element, value, row, column) => { const didCancel = cancels.get(element)?.(); if (!originalStyles.get(element)) { originalStyles.set(element, { display: null // transitionProperty: style.transitionProperty, // transitionDuration: style.transitionDuration, // transitionTimingFunction: style.transitionTimingFunction, // transitionDelay: style.transitionDelay, }); } const originalStyle = originalStyles.get(element); if (didCancel) { cancels.set(element, void 0); if (value) { if (element.style.display === originalStyle.display) { return; } element.style.display = originalStyle.display; return; } element.style.display = "none"; return; } if (!value) { if (element.style.display === "none") { return; } if (statuses.get(element) !== "appeared") { element.style.display = "none"; return; } if (transitionTypes.leave === "none") { element.style.display = "none"; return; } if (transitionTypes.leave === "css") { const cancel = transitionCss(element, { ...leave, start: (addFrom) => { leave.start?.(); addFrom(); }, end: (removeTo) => { element.style.display = "none"; removeTo(); leave.end?.(); }, cancel: () => { leave.cancel?.(); } }); cancels.set(element, cancel); return; } if (transitionTypes.leave === "js") { const cancel = transitionJs( affordanceElementKind, { column, row, before: leave.before, start: () => { }, active: leave.active, end: (status) => { if (status === "canceled") { return; } element.style.display = "none"; }, after: leave.after, cancel: leave.cancel } ); cancels.set(element, cancel); return; } } if (value) { let enterEffect = function() { if (element.style.display === originalStyle.display) { return; } if (transitionTypes.enter === "none") { element.style.display = originalStyle.display; return; } if (transitionTypes.enter === "css") { const cancel = transitionCss(element, { ...enter, start: (addFrom) => { enter.start?.(); addFrom(); element.style.display = originalStyle.display; }, end: (removeTo) => { removeTo(); enter.end?.(); }, cancel: () => { enter.cancel?.(); } }); cancels.set(element, cancel); return; } if (transitionTypes.enter === "js") { const cancel = transitionJs( affordanceElementKind, { row, column, before: enter.before, start: () => element.style.display = originalStyle.display, active: enter.active, end: () => { }, after: enter.after, cancel: enter.cancel } ); cancels.set(element, cancel); return; } }; if (statuses.get(element) !== "appeared") { statuses.set(element, "appeared"); if (element.style.display === originalStyle.display) { return; } if (transitionTypes.appear === "none") { element.style.display = originalStyle.display; return; } if (transitionTypes.appear === "css") { const cancel = transitionCss(element, { ...appear, start: (addFrom) => { appear.start?.(); addFrom(); element.style.display = originalStyle.display; }, end: (removeTo) => { removeTo(); appear.end?.(); }, cancel: () => { appear.cancel?.(); } }); cancels.set(element, cancel); return; } if (transitionTypes.appear === "js") { const hooks = transition?.appear === true && transition?.enter || !transition?.appear && {} || transition?.appear; const cancel = transitionJs( affordanceElementKind, { row, column, before: hooks?.before, start: () => element.style.display = originalStyle.display, active: hooks?.active, end: () => { }, after: hooks?.after, cancel: hooks?.cancel } ); cancels.set(element, cancel); return; } if (transitionTypes.appear === "enter") { enterEffect(); return; } } enterEffect(); } }, () => { }, narrowBindValue(condition), narrowWatchSourceOrSources(condition) ); } function defineTransition(transition) { return transition; } function transitionJs(affordanceElementKind, config) { let status = "ready"; const { row, column } = config, { before, start, active, end, after, cancel } = (() => { if (affordanceElementKind === "plane") { return { before: () => config.before?.({ row, column }), start: () => config.start?.(), active: () => config.active?.({ row, column }, done), end: () => config.end?.(status), after: () => config.after?.({ row, column }), cancel: () => config.cancel?.({ row, column }) }; } if (affordanceElementKind === "list") { return { before: () => config.before?.(row), start: () => config.start?.(), active: () => config.active?.(row, done), end: () => config.end?.(status), after: () => config.after?.(row), cancel: () => config.cancel?.(row) }; } return { before: () => config.before?.(), start: () => config.start?.(), active: () => config.active?.(done), end: () => config.end?.(status), after: () => config.after?.(), cancel: () => config.cancel?.() }; })(), done = () => { end(); if (status === "canceled") return; after(); status = "transitioned"; }; before(); start(); status = "transitioning"; if (config.active) { active(); } else { done(); } return () => { if (status === "transitioned") return false; status = "canceled"; requestAnimationFrame(() => { cancel(); done(); }); return true; }; } function transitionCss(element, config) { let status = "ready"; const from = config.from.split(" ") || [], active = config.active.split(" ") || [], to = config.to.split(" ") || [], transitionend = new Listenable("transitionend"), transitioncancel = new Listenable("transitioncancel"), end = () => { status = "transitioned"; transitionend.stop(); transitioncancel.stop(); requestAnimationFrame(() => { const removeTo = () => element.classList.remove(...active, ...to); config.end(removeTo); }); }, cancel = () => { transitioncancel.stop(); transitionend.stop(); requestAnimationFrame(() => { element.style.transitionProperty = ""; config.cancel(); }); }; transitionend.listen((event) => { if (event.target !== element) return; end(); }); transitioncancel.listen((event) => { if (event.target !== element) return; cancel(); }); const addFrom = () => element.classList.add(...from); config.start(addFrom); status = "transitioning"; requestAnimationFrame(() => { element.classList.add(...active); requestAnimationFrame(() => { element.classList.remove(...from); element.classList.add(...to); if (predicateInstantTransition(element)) end(); }); }); return () => { if (status === "transitioned") return false; status = "canceled"; element.style.transitionProperty = "none"; element.classList.remove(...from, ...active, ...to); return true; }; } function predicateInstantTransition(element) { return toComputedStyle(element).transitionDuration === "0s"; } function toTransitionTypes({ appear = {}, enter = {}, leave = {} }) { const enterType = (() => { if ("from" in enter) return "css"; if (some((key) => key in enter)(["before", "active", "after", "cancel"])) return "js"; return "none"; })(), leaveType = (() => { if ("from" in leave) return "css"; if (some((key) => key in leave)(["before", "active", "after", "cancel"])) return "js"; return "none"; })(), appearType = (() => { if (appear === true) return "enter"; if ("from" in appear) return "css"; if (some((key) => key in appear)(["before", "active", "after", "cancel"])) return "js"; return "none"; })(); return { appear: appearType, enter: enterType, leave: leaveType }; } function toTransitionWithEffects(effects, options) { const transitionOption = options?.transition ?? {}, transitionTypes = toTransitionTypes(transitionOption); const [enter, leave] = ["enter", "leave"].map((stage) => { if (transitionTypes[stage] === "none") { return effects[stage]?.none || {}; } if (transitionTypes[stage] === "js") { return { before: (...args) => { transitionOption[stage]?.before?.(...args); effects[stage]?.js?.before?.(...args); }, active: (...args) => { const withoutDone = args.slice(0, args.length - 1), performTransitionEffect = () => { effects[stage]?.js?.active?.(...args); }; if (transitionOption[stage]?.active) transitionOption[stage]?.active?.(...withoutDone, performTransitionEffect); else performTransitionEffect(); }, after: (...args) => { transitionOption[stage]?.after?.(...args); effects[stage]?.js?.after?.(...args); }, cancel: (...args) => { transitionOption[stage]?.cancel?.(...args); effects[stage]?.js?.cancel?.(...args); } }; } return { ...transitionOption[stage], start: () => { transitionOption[stage].start?.(); effects[stage]?.css?.start?.(); }, end: () => { transitionOption[stage].end?.(); effects[stage]?.css?.end?.(); }, cancel: () => { transitionOption[stage].cancel?.(); effects[stage]?.css?.cancel?.(); } }; }), appear = (() => { if (transitionOption.appear === true) return enter; if (transitionTypes.appear === "none") { return effects.appear?.none || {}; } if (transitionTypes.appear === "js") { return { before: (...args) => { transitionOption.appear?.before?.(...args); effects.appear?.js?.before?.(...args); }, active: (...args) => { const withoutDone = args.slice(0, args.length - 1), performTransitionEffect = () => { effects.appear?.js?.active?.(...args); }; if (transitionOption.appear?.active) transitionOption.appear?.active?.(...withoutDone, performTransitionEffect); else performTransitionEffect(); }, after: (...args) => { transitionOption.appear?.after?.(...args); effects.appear?.js?.after?.(...args); }, cancel: (...args) => { transitionOption.appear?.cancel?.(...args); effects.appear?.js?.cancel?.(...args); } }; } return { ...transitionOption.appear, start: () => { transitionOption.appear.start?.(); effects.appear?.css?.start?.(); }, end: () => { transitionOption.appear.end?.(); effects.appear?.css?.end?.(); }, cancel: () => { transitionOption.appear.cancel?.(); effects.appear?.css?.cancel?.(); } }; })(); return { appear, enter, leave }; } function toTransitionWithFocus({ focusAfterEnter, focusAfterLeave }, options) { const appearAndEnterJsEffects = { after: () => nextTick(focusAfterEnter), cancel: () => nextTick(focusAfterLeave), active: (...args) => { const done = args[args.length - 1]; done(); } }, appearAndEnterEffects = { none: appearAndEnterJsEffects, js: appearAndEnterJsEffects, css: { end: () => nextTick(focusAfterEnter), cancel: () => nextTick(focusAfterLeave) } }, leaveJsEffects = { after: () => nextTick(focusAfterLeave), cancel: () => nextTick(focusAfterEnter), active: (...args) => { const done = args[args.length - 1]; done(); } }; return toTransitionWithEffects( { appear: appearAndEnterEffects, enter: appearAndEnterEffects, leave: { none: leaveJsEffects, js: leaveJsEffects, css: { end: () => nextTick(focusAfterLeave), cancel: () => nextTick(focusAfterEnter) } } }, options ); } const predicateUp = createKeycomboMatch("up"); const predicateRight = createKeycomboMatch("right"); const predicateDown = createKeycomboMatch("down"); const predicateLeft = createKeycomboMatch("left"); const predicateArrow = (event) => predicateUp(event) || predicateRight(event) || predicateDown(event) || predicateLeft(event); createKeycomboMatch("alt"); const predicateCmd = createKeycomboMatch("cmd"); createKeycomboMatch("ctrl"); createKeycomboMatch("shift"); createKeycomboMatch("backspace"); const predicateEnter = createKeycomboMatch("enter"); const predicateEsc = createKeycomboMatch("esc"); createKeycomboMatch("tab"); const predicateHome = createKeycomboMatch("home"); const predicateEnd = createKeycomboMatch("end"); const predicateSpace = createKeycomboMatch("space"); function predicateNullish(value) { return value === void 0 || value === null; } const defaultOptions$s = { predicateEqual: (currentPoint, previousPoint) => currentPoint === previousPoint }; function toPlaneOrder(currentPoints, previousPoints, options = {}) { const { predicateEqual } = { ...defaultOptions$s, ...options }; for (const { row, column, point: currentPoint } of currentPoints.points()) { const previousPoint = previousPoints.get({ row, column }); if (predicateNullish(currentPoint) || predicateNullish(previousPoint)) continue; if (!predicateEqual(currentPoint, previousPoint)) return "changed"; } return "none"; } function toPlaneStatus(currentPlane, previousPlane, options = {}) { if (currentPlane && !previousPlane) return { rowWidth: "lengthened", columnHeight: "lengthened", order: "changed" }; const rowWidth = (() => { if (!currentPlane.length) return "n/a"; if (currentPlane[0].length > (previousPlane[0]?.length || 0)) return "lengthened"; if (currentPlane[0].length < (previousPlane[0]?.length || 0)) return "shortened"; return "none"; })(), columnHeight = (() => { if (!currentPlane[0]?.length) return "n/a"; if (currentPlane.length > previousPlane.length) return "lengthened"; if (currentPlane.length < previousPlane.length) return "shortened"; return "none"; })(), order = toPlaneOrder( currentPlane, previousPlane, options ); return { rowWidth, columnHeight, order }; } function predicateRenderedWatchSourcesChanged(current, previous) { if (current.length > 1) { for (let i = 1; i < current.length; i++) { if (!createDeepEqual(previous?.[i])(current[i])) return true; } } const currentPlane = current[0], previousPlane = previous?.[0], { rowWidth, columnHeight, order } = toPlaneStatus(currentPlane, previousPlane); return ![rowWidth, columnHeight, order].every((status) => status === "none"); } const defaultAbilityMeta = { ability: "enabled" }; function toAbilityBindValues(elementOrListOrPlaneApi) { if (elementOrListOrPlaneApi.meta.value instanceof Plane) { return { disabled: { get: ({ row, column }) => elementOrListOrPlaneApi.meta.value?.get({ row, column })?.ability === "disabled" ? true : void 0, watchSource: elementOrListOrPlaneApi.meta }, ariaDisabled: { get: ({ row, column }) => elementOrListOrPlaneApi.meta.value?.get({ row, column })?.ability === "disabled" ? true : void 0, watchSource: elementOrListOrPlaneApi.meta }, tabindex: { get: ({ row, column }) => elementOrListOrPlaneApi.meta.value?.get({ row, column })?.ability === "enabled" ? 0 : -1, watchSource: elementOrListOrPlaneApi.meta } }; } if (Array.isArray(elementOrListOrPlaneApi.meta.value)) { return { disabled: { get: (index) => elementOrListOrPlaneApi.meta.value?.[index]?.ability === "disabled" ? true : void 0, watchSource: elementOrListOrPlaneApi.meta }, ariaDisabled: { get: (index) => elementOrListOrPlaneApi.meta.value?.[index]?.ability === "disabled" ? true : void 0, watchSource: elementOrListOrPlaneApi.meta }, tabindex: { get: (index) => elementOrListOrPlaneApi.meta.value?.[index]?.ability === "enabled" ? 0 : -1, watchSource: elementOrListOrPlaneApi.meta } }; } return { disabled: { get: () => elementOrListOrPlaneApi.meta.value.ability === "disabled" ? true : void 0, watchSource: elementOrListOrPlaneApi.meta }, ariaDisabled: { get: () => elementOrListOrPlaneApi.meta.value.ability === "disabled" ? true : void 0, watchSource: elementOrListOrPlaneApi.meta }, tabindex: { get: () => elementOrListOrPlaneApi.meta.value.ability === "enabled" ? 0 : -1, watchSource: elementOrListOrPlaneApi.meta } }; } const defaultValidityMeta = { validity: "valid" }; function toValidityBindValues(elementApi) { return { ariaInvalid: { get: () => elementApi.meta.value.validity === "invalid" ? true : void 0, watchSource: elementApi.meta } }; } const defaultLabelMeta = { label: void 0, labelledBy: void 0, description: void 0, describedBy: void 0, errorMessage: void 0, details: void 0 }; function toLabelBindValues(elementOrListOrPlaneApi) { if (elementOrListOrPlaneApi.meta.value instanceof Plane) { return { ariaLabel: { get: ({ row, column }) => elementOrListOrPlaneApi.meta.value?.get({ row, column })?.label || void 0, watchSource: elementOrListOrPlaneApi.meta }, ariaLabelledbys: { get: ({ row, column }) => toListValue( elementOrListOrPlaneApi.meta.value?.get({ row, column })?.labelledBy ), watchSource: elementOrListOrPlaneApi.meta }, ariaDescription: { get: ({ row, column }) => elementOrListOrPlaneApi.meta.value?.get({ row, column })?.description || void 0, watchSource: elementOrListOrPlaneApi.meta }, ariaDescribedbys: { get: ({ row, column }) => toListValue( [ toListValue( elementOrListOrPlaneApi.meta.value?.get({ row, column })?.describedBy ), toListValue( elementOrListOrPlaneApi.meta.value?.get({ row, column })?.errorMessage ) ] ), watchSource: elementOrListOrPlaneApi.meta }, ariaDetails: { get: ({ row, column }) => toListValue( elementOrListOrPlaneApi.meta.value?.get({ row, column })?.details ), watchSource: elementOrListOrPlaneApi.meta } }; } if (Array.isArray(elementOrListOrPlaneApi.meta.value)) { return { ariaLabel: { get: (index) => elementOrListOrPlaneApi.meta.value?.[index]?.label || void 0, watchSource: elementOrListOrPlaneApi.meta }, ariaLabelledbys: { get: (index) => toListValue( elementOrListOrPlaneApi.meta.value?.[index]?.labelledBy ), watchSource: elementOrListOrPlaneApi.meta }, ariaDescription: { get: (index) => elementOrListOrPlaneApi.meta.value?.[index]?.description || void 0, watchSource: elementOrListOrPlaneApi.meta }, ariaDescribedbys: { get: (index) => toListValue( [ toListValue( elementOrListOrPlaneApi.meta.value?.[index]?.describedBy ), toListValue( elementOrListOrPlaneApi.meta.value?.[index]?.errorMessage ) ] ), watchSource: elementOrListOrPlaneApi.meta }, ariaDetails: { get: (index) => toListValue( elementOrListOrPlaneApi.meta.value?.[index]?.details ), watchSource: elementOrListOrPlaneApi.meta } }; } return { ariaLabel: { get: () => elementOrListOrPlaneApi.meta.value.label || void 0, watchSource: elementOrListOrPlaneApi.meta }, ariaLabelledbys: { get: () => toListValue( elementOrListOrPlaneApi.meta.value.labelledBy ), watchSource: elementOrListOrPlaneApi.meta }, ariaDescription: { get: () => elementOrListOrPlaneApi.meta.value.description || void 0, watchSource: elementOrListOrPlaneApi.meta }, ariaDescribedbys: { get: () => toListValue( [ toListValue( elementOrListOrPlaneApi.meta.value.describedBy ), toListValue( elementOrListOrPlaneApi.meta.value.errorMessage ) ] ), watchSource: elementOrListOrPlaneApi.meta }, ariaDetails: { get: () => toListValue( elementOrListOrPlaneApi.meta.value.details ), watchSource: elementOrListOrPlaneApi.meta } }; } function toListValue(idOrList) { return Array.isArray(idOrList) ? toList(...idOrList) || void 0 : idOrList; } const toList = createList(); const toTokenList = join(" "); const toComputedStyle = createComputedStyle(); function toLastPointeroutTarget(sequence) { let lastPointerout, index = sequence.length - 1; while (index >= 0) { const event = sequence[index]; if (event.type === "pointerout") { lastPointerout = event; break; } index--; } return lastPointerout?.target; } function getId(instance) { return instance ? (instance.appContext.config.idPrefix || "v") + "-" + instance.ids[0] + instance.ids[1]++ : useId(); } const defaultOptions$r = { predicateRenderedWatchSourcesChanged: () => true, watchSources: [] }; function onPlaneRendered(plane, options = {}) { const { predicateRenderedWatchSourcesChanged, planeEffect, beforeItemEffects, itemEffect, afterItemEffects, watchSources } = { ...defaultOptions$r, ...options }, withGuards = (current, previous) => { if (!current[0].length || !predicateRenderedWatchSourcesChanged(current, previous)) return; planeEffect?.(current, previous); if (itemEffect) { beforeItemEffects?.(); for (let row = 0; row < plane.value.length; row++) { for (let column = 0; column < plane.value[row].length; column++) { const rendered = plane.value.get({ row, column }); itemEffect(rendered, { row, column }); } } afterItemEffects?.(); } }; onMounted(() => { if (!plane.value[0].length) return; withGuards( [plane.value, ...watchSources.map((watchSource) => { if (isRef(watchSource)) { return watchSource.value; } if (typeof watchSource === "function") { return watchSource(); } return watchSource; })], [void 0, ...watchSources.map(() => void 0)] ); }); return watch( [plane, ...watchSources], withGuards, { flush: "post" } ); } function onRendered(element, options = {}) { const plane = narrowReactivePlane(element), { predicateRenderedWatchSourcesChanged, effect } = { ...defaultOptions$r, ...options }; return onPlaneRendered( plane, { ...options, predicateRenderedWatchSourcesChanged: (current, previous) => predicateRenderedWatchSourcesChanged( [current[0]?.get({ row: 0, column: 0 }), ...current.slice(1)], [previous[0]?.get({ row: 0, column: 0 }), ...previous.slice(1)] ), ...effect && { itemEffect: (rendered) => effect(rendered) } } ); } function onRenderedBind(elementOrListOrPlane, assign, remove, value, watchSources) { const renderedKind = toRenderedKind(elementOrListOrPlane), elements = narrowReactivePlane(elementOrListOrPlane), narrowedWatchSources = narrowWatchSources(watchSources); if (isRef(value)) { return onPlaneRendered( elements, { predicateRenderedWatchSourcesChanged, itemEffect: (element, { row, column }) => { if (!element) return; if (value.value === void 0) { remove(element); return; } assign(element, value.value, column, row); }, watchSources: [value, ...narrowedWatchSources] } ); } if (typeof value === "function") { const get = value; return onPlaneRendered( elements, { predicateRenderedWatchSourcesChanged, itemEffect: (element, { row, column }) => { if (!element) return; const value2 = renderedKind === "plane" ? get({ row, column }) : renderedKind === "list" ? get(column) : get(); if (value2 === void 0) { remove(element); return; } assign(element, value2, column, row); }, watchSources: narrowedWatchSources // `get` is not used a watch source because it often needs arguments } ); } return onPlaneRendered( elements, { predicateRenderedWatchSourcesChanged, itemEffect: (element, { row, column }) => { if (!element) return; if (value === void 0) { remove(element); return; } assign(element, value, column, row); }, watchSources: narrowedWatchSources } ); } function bindAttributeOrProperty(elementOrListOrPlane, key, value, watchSources) { const narrowedKey = narrowKey(key); return onRenderedBind( elementOrListOrPlane, (element, value2) => { if (shouldPerformPropertyEffect({ element, key: narrowedKey, value: value2 })) { propertyEffect({ element, property: narrowedKey, value: value2 }); return; } attributeEffect({ element, attribute: narrowedKey, value: value2 }); }, (element) => { if (shouldPerformPropertyEffect({ element, key: narrowedKey, value: void 0 })) { return; } element.removeAttribute(narrowedKey); }, value, watchSources ); } function narrowKey(rawKey) { switch (rawKey) { case "for": return "htmlFor"; case "allowfullscreen": return "allowFullscreen"; case "formnovalidate": return "formNoValidate"; case "ismap": return "isMap"; case "nomodule": return "noModule"; case "novalidate": return "noValidate"; case "readonly": return "readOnly"; default: return /^(aria|data)[A-Z]/.test(rawKey) ? `${rawKey.slice(0, 4)}-${rawKey.slice(4).toLowerCase()}` : rawKey; } } function shouldPerformPropertyEffect({ element, key, value }) { if (key === "spellcheck" || key === "draggable") { return false; } if (key === "form" && typeof value === "string") { return false; } if (key === "list" && element.tagName === "INPUT") { return false; } return key in element; } function propertyEffect({ element, property, value }) { if (property === "value" && element.tagName !== "PROGRESS") { const narrowedValue = value == null ? "" : value; if (element.value === narrowedValue) { return; } element.value = narrowedValue; return; } if (typeof value === "string" && value === "" || value == null) { const type = typeof element[property]; switch (type) { case "boolean": if (typeof value === "string" && value === "") { element[property] = true; return; } break; case "string": if (value == null) { element[property] = ""; element.removeAttribute(property); return; } break; case "number": element[property] = 0; element.removeAttribute(property); return; } } element[property] = value; } const xlinkNS = "http://www.w3.org/1999/xlink"; function attributeEffect({ element, attribute, value }) { if (element instanceof SVGElement && attribute.startsWith("xlink:")) { if (value == null) { element.removeAttributeNS(xlinkNS, attribute.slice(6, attribute.length)); } element.setAttributeNS(xlinkNS, attribute, value); return; } if (attribute === "itemscope") { if (value == null || typeof value === "boolean" && value === false) { element.removeAttribute(attribute); return; } if (element.getAttribute(attribute) === "") { return; } element.setAttribute(attribute, ""); return; } if (element.getAttribute(attribute) === value) { return; } element.setAttribute(attribute, value); } function bindList(elementOrListOrPlane, list, value, watchSources) { const cache = /* @__PURE__ */ new WeakMap(); return onRenderedBind( elementOrListOrPlane, (element, value2) => { if (list === "class" || list === "rel") { const domTokenList = element[`${list}List`]; if (domTokenList.contains(value2)) return; const cached = cache.get(element) || ""; domTokenList.remove(...toListStrings(cached)); domTokenList.add(...toListStrings(value2)); cache.set(element, value2); return; } const attribute = toAttribute(list); pipe( () => element.getAttribute(attribute), toListStrings, createConcat(value2.split(" ")), unique(), join(" "), (ids) => element.setAttribute(attribute, ids) )(); }, () => { }, value, watchSources ); } function toListStrings(value) { return (value || "").split(" ").filter((string) => string); } const re = /aria-?(\w+)s$/; function toAttribute(list) { return `aria-${list.match(re)[1]}`.toLowerCase(); } function bindStyle(elementOrListOrPlane, property, value, watchSources) { return onRenderedBind( elementOrListOrPlane, (element, value2) => { if (element.style[property] === value2) { return; } element.style[property] = value2; }, (element) => { element.style[property] = ""; }, value, watchSources ); } function ariaHiddenFocusManage({ root, list, selectedItems }) { on( root, { // When focus leaves the selected item, make sure it can't move into an unselected item. focusout: (event) => { if (list.value[selectedItems.newest].contains(event.relatedTarget) || !some((item) => item.contains(event.target))(list.value)) return; const relatedTargetIndex = findIndex( (item) => item.contains(event.relatedTarget) )(list.value); if (relatedTargetIndex === -1) return; const relativeIndex = relatedTargetIndex - selectedItems.newest; if (relativeIndex <= 0) { createFocusable("previous")(list.value[0])?.focus(); return; } pipe( at(-1), (item) => createFocusable("last")(item) || item, createFocusable("next"), (el) => el?.focus() )(list.value); }, /** * When focus moves into an unselected item, move it to the selected item. * * WARNING: This function will not work if unselected items _do_ contain focusable * elements but the selected item does _not_ contain focusable elements. We don't * currently have this use case, but if we ever do, we can adjust this function. */ focusin: (event) => { if (list.value[selectedItems.newest].contains(event.target) || !some((item) => item.contains(event.target))(list.value)) return; pipe( at(selectedItems.newest), createFocusable("first", { predicatesElement: true }), (el) => el?.focus() )(list.value); } } ); } function popupList({ controllerApis, popupApi, popup, getEscShouldClose, receivesFocus }) { const [primaryControllerApi] = controllerApis; for (const controllerApi of controllerApis) { bind( controllerApi.element, { ariaExpanded: computed(() => `${popup.is.opened()}`), ariaControls: computed(() => popup.is.opened() ? popupApi.id.value : void 0) } ); on( controllerApi.element, { focusout: (event) => { if (popup.is.closed() || some( (controllerApi2) => controllerApi2.element.value.contains(event.relatedTarget) )(controllerApis) || popupApi.element.value.contains(event.relatedTarget)) return; popup.close(); }, keydown: (event) => { if (popup.is.closed() && (predicateDown(event) || predicateUp(event))) { popup.open(); if (receivesFocus) return; primaryControllerApi.element.value.focus(); return; } if (popup.is.opened() && predicateEsc(event)) { popup.close(); return; } } } ); } if (receivesFocus) { on( popupApi.element, { keydown: (event) => { if (!predicateEsc(event) || !getEscShouldClose()) return; event.preventDefault(); popup.close(); if (popup.is.removed()) { primaryControllerApi.element.value.focus(); return; } const stop = watch( () => popup.is.removed(), (is) => { if (!is) return; stop(); primaryControllerApi.element.value.focus(); } ); } } ); on( popupApi.element, { focusout: (event) => { if (some( (controllerApi) => controllerApi.element.value.contains(event.relatedTarget) )(controllerApis) || popupApi.element.value.contains(event.relatedTarget)) return; if (event.relatedTarget === createFocusable("previous")(popupApi.element.value)) { event.preventDefault(); primaryControllerApi.element.value.focus(); return; } popup.close(); } } ); } } function createToNextEligible({ api }) { return ({ coordinates: { row, column }, toEligibility, loops, direction }) => { if (api.plane.value.length === 0 || api.plane.value[0].length === 0) return "none"; const { row: rowLimit, column: columnLimit } = (() => { if (!loops) return { row: api.plane.value.length - 1, column: api.plane.value[0].length - 1 }; switch (direction) { case "vertical": return { row: row < 1 ? api.plane.value.length - 1 : row - 1, column: row < 1 && column < 1 ? api.plane.value[0].length - 1 : Math.max(column, 0) }; case "horizontal": return { row: row < 1 && column < 1 ? api.plane.value.length - 1 : Math.max(row, 0), column: column < 1 ? api.plane.value[0].length - 1 : column - 1 }; } })(), r = new Navigateable(api.plane.value).navigate( direction === "horizontal" ? Math.max(row, 0) : row, { allow: "any" } ), c = new Navigateable(api.plane.value[0]).navigate( direction === "vertical" ? Math.max(column, 0) : column, { allow: "any" } ); let nextEligible = "none", didReachLimit = r.location === r.array.length - 1 && c.location === c.array.length - 1 && !loops; switch (direction) { case "vertical": while (nextEligible === "none" && !didReachLimit) { let didReachRowLimit = false; while (nextEligible === "none" && !didReachRowLimit) { r.next({ loops }); didReachRowLimit = r.location === rowLimit; const eligibility = toEligibility({ row: r.location, column: c.location }); if (eligibility === "eligible") { nextEligible = { row: r.location, column: c.location }; break; } } c.next({ loops }); didReachLimit = didReachRowLimit && c.location === columnLimit; r.navigate(-1, { allow: "any" }); } break; case "horizontal": while (nextEligible === "none" && !didReachLimit) { let didReachColumnLimit = false; while (nextEligible === "none" && !didReachColumnLimit) { c.next({ loops }); didReachColumnLimit = c.location === columnLimit; const eligibility = toEligibility({ row: r.location, column: c.location }); if (eligibility === "eligible") { nextEligible = { row: r.location, column: c.location }; break; } } r.next({ loops }); didReachLimit = didReachColumnLimit && r.location === rowLimit; c.navigate(-1, { allow: "any" }); } break; } return nextEligible; }; } function createToPreviousEligible({ api }) { return ({ coordinates: { row, column }, toEligibility, loops, direction }) => { if (api.plane.value.length === 0 || api.plane.value[0].length === 0) return "none"; const [rowLimit, columnLimit] = (() => { if (!loops) return [0, 0]; switch (direction) { case "vertical": return [ row > api.plane.value.length - 2 ? 0 : row + 1, row > api.plane.value.length - 2 && column > api.plane.value[0].length - 2 ? 0 : Math.min(column, api.plane.value[0].length - 1) ]; case "horizontal": return [ row > api.plane.value.length - 2 && column > api.plane.value[0].length - 2 ? 0 : Math.min(row, api.plane.value.length - 1), column > api.plane.value[0].length - 2 ? 0 : column + 1 ]; } })(), r = new Navigateable(api.plane.value).navigate( direction === "horizontal" ? Math.min(row, api.plane.value.length - 1) : row, { allow: "any" } ), c = new Navigateable(api.plane.value[0]).navigate( direction === "vertical" ? Math.min(column, api.plane.value[0].length - 1) : column, { allow: "any" } ); let previousEligible = "none", didReachLimit = r.location === 0 && c.location === 0 && !loops; switch (direction) { case "vertical": while (previousEligible === "none" && !didReachLimit) { let didReachRowLimit = false; while (previousEligible === "none" && !didReachRowLimit) { r.previous({ loops }); didReachRowLimit = r.location === rowLimit; const eligibility = toEligibility({ row: r.location, column: c.location }); if (eligibility === "eligible") { previousEligible = { row: r.location, column: c.location }; break; } } c.previous({ loops }); didReachLimit = didReachRowLimit && c.location === columnLimit; r.navigate(api.plane.value.length, { allow: "any" }); } break; case "horizontal": while (previousEligible === "none" && !didReachLimit) { let didReachColumnLimit = false; while (previousEligible === "none" && !didReachColumnLimit) { c.previous({ loops }); didReachColumnLimit = c.location === columnLimit; const eligibility = toEligibility({ row: r.location, column: c.location }); if (eligibility === "eligible") { previousEligible = { row: r.location, column: c.location }; break; } } r.previous({ loops }); didReachLimit = didReachColumnLimit &