@alfakit/core 0.1.13 → 0.2.2

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cms.d.ts +10 -1
  3. package/dist/cms.d.ts.map +1 -1
  4. package/dist/cms.js +30 -1
  5. package/dist/cms.js.map +1 -1
  6. package/dist/form.d.ts +10 -1
  7. package/dist/form.d.ts.map +1 -1
  8. package/dist/form.js +26 -4
  9. package/dist/form.js.map +1 -1
  10. package/dist/i18n.d.ts +9 -0
  11. package/dist/i18n.d.ts.map +1 -1
  12. package/dist/i18n.js +14 -1
  13. package/dist/i18n.js.map +1 -1
  14. package/dist/index.d.ts +7 -6
  15. package/dist/index.js +5 -4
  16. package/dist/kanban.d.ts.map +1 -1
  17. package/dist/kanban.js +25 -1
  18. package/dist/kanban.js.map +1 -1
  19. package/dist/prefetch.js +12 -1
  20. package/dist/prefetch.js.map +1 -1
  21. package/dist/realtime.d.ts +3 -3
  22. package/dist/realtime.js +3 -3
  23. package/dist/realtime.js.map +1 -1
  24. package/dist/schema/cms.d.ts +23 -0
  25. package/dist/schema/cms.d.ts.map +1 -1
  26. package/dist/schema/components.d.ts +2 -0
  27. package/dist/schema/components.d.ts.map +1 -1
  28. package/dist/schema/components.js.map +1 -1
  29. package/dist/schema/index.d.ts +3 -3
  30. package/dist/schema/panel.d.ts +14 -0
  31. package/dist/schema/panel.d.ts.map +1 -1
  32. package/dist/schema/panel.js.map +1 -1
  33. package/dist/starter-kit-document.d.ts +32 -0
  34. package/dist/starter-kit-document.d.ts.map +1 -0
  35. package/dist/starter-kit-document.js +57 -0
  36. package/dist/starter-kit-document.js.map +1 -0
  37. package/dist/utils/container-breakpoints.d.ts +9 -0
  38. package/dist/utils/container-breakpoints.d.ts.map +1 -1
  39. package/dist/utils/container-breakpoints.js +63 -1
  40. package/dist/utils/container-breakpoints.js.map +1 -1
  41. package/package.json +1 -1
  42. package/themes.css +92 -0
@@ -1 +1 @@
1
- {"version":3,"file":"kanban.js","names":[],"sources":["../src/kanban.ts"],"sourcesContent":["import type { ActionContext, ActionRunner } from './actions'\nimport { FormStore } from './form'\nimport { HttpError } from './http'\nimport type { Translator } from './i18n'\nimport { PollScheduler } from './poll'\nimport type { ActionSchema } from './schema/actions'\nimport type {\n KanbanCardSchema,\n KanbanColumnSchema,\n KanbanFetchResult,\n KanbanLaneSchema,\n KanbanMoveResult,\n KanbanSchema,\n} from './schema/kanban'\nimport type { NotificationSchema } from './schema/notifications'\nimport { createStore, type Store } from './store'\n\n/**\n * The framework-agnostic half of the Kanban board (alfasix/alfakit-kanban): pure\n * operations on the columns, a `KanbanController` holding the working copy, the picked-up\n * card, the search and the requests, and `attachKanbanPointer()`, which drives mouse, pen\n * and touch dragging over the DOM every renderer emits. Renderers only draw the state.\n *\n * Moves are optimistic: the card moves at once and returns when the server refuses. The\n * server checks every move again (record, policy, column, transition, WIP limit, lane,\n * tenant); the checks here only keep the UI from offering moves it would refuse.\n */\n\n/** The registry key of the board component. */\nexport const KANBAN_REGISTRY_KEY = 'kanban'\n\n/** The lane key of cards without a swimlane value (`Swimlane::NONE`). */\nexport const KANBAN_NO_LANE = '__none'\n\n/** Where a card is, or should go: a column, a lane (null on boards without lanes) and a position in that lane. */\nexport interface KanbanTarget {\n column: string\n lane: string | null\n /** Position among the lane's cards in the column, from 0. */\n index: number\n}\n\n/** Why a move is not offered. */\nexport type KanbanRefusal = 'locked' | 'transition' | 'wip' | 'lane'\n\nexport interface KanbanLocation {\n column: KanbanColumnSchema\n columnIndex: number\n card: KanbanCardSchema\n /** Position in the column's card list. */\n index: number\n lane: string | null\n /** Position among the lane's cards in the column. */\n laneIndex: number\n laneCount: number\n}\n\n/** The cards of one lane of a column (every card when `lane` is null). */\nexport function kanbanCardsIn(column: KanbanColumnSchema, lane: string | null): KanbanCardSchema[] {\n return lane === null\n ? column.cards\n : column.cards.filter((card) => (card.lane ?? KANBAN_NO_LANE) === lane)\n}\n\nexport function locateKanbanCard(\n columns: KanbanColumnSchema[],\n key: string,\n): KanbanLocation | null {\n for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) {\n const column = columns[columnIndex]!\n const index = column.cards.findIndex((card) => card.key === key)\n\n if (index >= 0) {\n const card = column.cards[index]!\n const lane = card.lane ?? null\n const laneCards = kanbanCardsIn(column, lane)\n\n return {\n column,\n columnIndex,\n card,\n index,\n lane,\n laneIndex: laneCards.indexOf(card),\n laneCount: laneCards.length,\n }\n }\n }\n\n return null\n}\n\n/** Where a card is now. */\nexport function kanbanCardPosition(\n columns: KanbanColumnSchema[],\n key: string,\n): KanbanTarget | null {\n const found = locateKanbanCard(columns, key)\n\n return found ? { column: found.column.key, lane: found.lane, index: found.laneIndex } : null\n}\n\nfunction sameTarget(a: KanbanTarget, b: KanbanTarget): boolean {\n return a.column === b.column && a.lane === b.lane && a.index === b.index\n}\n\n/**\n * The columns after moving a card to `target` (counts follow the card), or null when the\n * card or column doesn't exist.\n */\nexport function moveKanbanCard(\n columns: KanbanColumnSchema[],\n key: string,\n target: KanbanTarget,\n): KanbanColumnSchema[] | null {\n const from = locateKanbanCard(columns, key)\n const toIndex = columns.findIndex((column) => column.key === target.column)\n\n if (!from || toIndex < 0) {\n return null\n }\n\n const next = columns.map((column) => ({ ...column, cards: [...column.cards] }))\n const card: KanbanCardSchema =\n target.lane === null || target.lane === from.lane\n ? from.card\n : { ...from.card, lane: target.lane }\n\n next[from.columnIndex]!.cards.splice(from.index, 1)\n\n const destination = next[toIndex]!\n const laneCards = kanbanCardsIn(destination, target.lane)\n let at: number\n\n if (laneCards.length === 0) {\n at = target.lane === null ? 0 : destination.cards.length\n } else if (target.index >= laneCards.length) {\n at = destination.cards.indexOf(laneCards[laneCards.length - 1]!) + 1\n } else {\n at = destination.cards.indexOf(laneCards[Math.max(0, target.index)]!)\n }\n\n destination.cards.splice(at, 0, card)\n\n if (from.columnIndex !== toIndex) {\n const source = next[from.columnIndex]!\n source.count = Math.max(0, source.count - 1)\n source.total = Math.max(0, source.total - 1)\n destination.count += 1\n destination.total += 1\n }\n\n return next\n}\n\n/**\n * Why the board would refuse moving a card from `origin` (where it was picked up) to\n * `target`, or null when the move is offered. `columns` is the working copy, which may\n * already show the card elsewhere.\n */\nexport function kanbanRefusal(\n board: Pick<KanbanSchema, 'movable' | 'transitions' | 'laneMovable'>,\n columns: KanbanColumnSchema[],\n key: string,\n origin: KanbanTarget,\n target: KanbanTarget,\n): KanbanRefusal | null {\n const found = locateKanbanCard(columns, key)\n\n if (!board.movable || !found || found.card.movable === false) {\n return 'locked'\n }\n\n if (target.column !== origin.column) {\n const allowed = board.transitions?.[origin.column]\n\n if (allowed && !allowed.includes(target.column)) {\n return 'transition'\n }\n\n const column = columns.find((candidate) => candidate.key === target.column)\n\n if (column?.wipStrict && column.wipLimit !== undefined) {\n const held = column.count - (found.column.key === column.key ? 1 : 0)\n\n if (held >= column.wipLimit) {\n return 'wip'\n }\n }\n }\n\n if (target.lane !== origin.lane && !board.laneMovable) {\n return 'lane'\n }\n\n return null\n}\n\n/**\n * The move as the server's move action reads it: the target column and lane, and the\n * neighbour the card follows (or precedes) in its column.\n */\nexport function kanbanMovePayload(\n board: Pick<KanbanSchema, 'orderable' | 'lanes'>,\n columns: KanbanColumnSchema[],\n key: string,\n): Record<string, unknown> {\n const found = locateKanbanCard(columns, key)\n\n if (!found) {\n return {}\n }\n\n const payload: Record<string, unknown> = { column: found.column.key }\n\n if (board.lanes && found.lane !== null) {\n payload.lane = found.lane\n }\n\n if (!board.orderable) {\n return payload\n }\n\n const after = found.column.cards[found.index - 1]\n const before = found.column.cards[found.index + 1]\n\n if (after) payload.after = after.key\n else if (before) payload.before = before.key\n else payload.position = 'end'\n\n return payload\n}\n\n/** The initials of a name, for avatars without an image. */\nexport function kanbanInitials(name: string): string {\n const parts = name.trim().split(/\\s+/).filter(Boolean)\n const letters = parts.length > 1 ? [parts[0]!, parts[parts.length - 1]!] : parts.slice(0, 1)\n\n return letters\n .map((part) => Array.from(part)[0] ?? '')\n .join('')\n .toUpperCase()\n}\n\n/** `count` against `wipLimit`: `over`, `full` or null. */\nexport function kanbanWipState(column: KanbanColumnSchema): 'over' | 'full' | null {\n if (column.wipLimit === undefined) return null\n if (column.count > column.wipLimit) return 'over'\n\n return column.count === column.wipLimit ? 'full' : null\n}\n\n/** How many filters of a filters form value hold something. */\nexport function kanbanActiveFilters(values: Record<string, unknown> | null | undefined): number {\n const filled = (value: unknown): boolean =>\n Array.isArray(value)\n ? value.length > 0\n : value !== null && value !== undefined && value !== '' && value !== false\n\n return Object.values(values ?? {}).filter(\n (state) => state !== null && typeof state === 'object' && Object.values(state).some(filled),\n ).length\n}\n\n/**\n * The form store of the board's filters (null without filters). Searchable selects load\n * their options from the schema the server names (pages only).\n */\nexport function createKanbanFiltersStore(\n board: Pick<KanbanSchema, 'filters' | 'actionComponent'>,\n token: string,\n optionsUrl?: string,\n): FormStore | null {\n if (!board.filters) return null\n\n return new FormStore(\n {\n components: board.filters.components,\n state: board.filters.state,\n columns: board.filters.columns,\n },\n {\n token,\n component: board.filters.component ?? `${board.actionComponent}.kanbanFilters`,\n refreshUrl: undefined,\n optionsUrl: board.filters.component ? optionsUrl : undefined,\n },\n )\n}\n\nexport interface KanbanPicked {\n key: string\n /** Where the card was picked up (it returns there on cancel or a refused move). */\n origin: KanbanTarget\n /** Where it is shown now. */\n target: KanbanTarget\n via: 'keyboard' | 'pointer'\n}\n\nexport interface KanbanState {\n /** The working copy of the columns. */\n columns: KanbanColumnSchema[]\n collapsed: ReadonlySet<string>\n collapsedLanes: ReadonlySet<string>\n picked: KanbanPicked | null\n /** The column the picked card may not enter, and why (for styling a refused target). */\n refused: { column: string; reason: KanbanRefusal } | null\n /** Cards whose move is being saved. */\n saving: ReadonlySet<string>\n search: string\n /** The filters form values the board was last asked for; null for the defaults. */\n filters: Record<string, unknown> | null\n loading: boolean\n /** For a polite live region. */\n announcement: string\n}\n\nexport interface KanbanEnvironment {\n runner: Pick<ActionRunner, 'executeJson' | 'trigger'>\n token: string\n t: Translator['t']\n notify(notifications: NotificationSchema[]): void\n error(message: string): void\n /** Follow a card's URL (an Inertia visit). */\n visit?(url: string): void\n}\n\nexport interface KanbanKeyResult {\n handled: boolean\n /** The card to focus next. */\n focus?: string\n /** Open the card (Enter). */\n open?: boolean\n}\n\ntype Direction = 'up' | 'down' | 'previous' | 'next'\n\n/**\n * The state of one rendered board. `setBoard()` takes a fresh schema from the server\n * (after an action ran or a poll); collapsed columns and the search are kept.\n */\nexport class KanbanController {\n public readonly store: Store<KanbanState>\n\n private searchTimer: ReturnType<typeof setTimeout> | null = null\n private pendingBoard: KanbanSchema | null = null\n private refreshAfterMove = false\n private fetchId = 0\n\n constructor(\n private board: KanbanSchema,\n private env: KanbanEnvironment,\n ) {\n this.store = createStore<KanbanState>({\n columns: board.columns,\n collapsed: new Set(board.columns.filter((column) => column.collapsed).map((c) => c.key)),\n collapsedLanes: new Set(\n (board.lanes ?? []).filter((lane) => lane.collapsed).map((l) => l.key),\n ),\n picked: null,\n refused: null,\n saving: new Set(),\n search: board.search ?? '',\n filters: null,\n loading: false,\n announcement: '',\n })\n }\n\n get state(): KanbanState {\n return this.store.get()\n }\n\n get schema(): KanbanSchema {\n return this.board\n }\n\n /** The renderer's latest runner, token and translator. */\n setEnvironment(env: KanbanEnvironment): void {\n this.env = env\n }\n\n /** The rows to draw: the board's lanes, or one row (null) without swimlanes. */\n lanes(): Array<KanbanLaneSchema | null> {\n return this.board.lanes && this.board.lanes.length > 0 ? this.board.lanes : [null]\n }\n\n /** The schema changed on the server (an action ran): show it, keeping the user's view. */\n setBoard(board: KanbanSchema): void {\n if (this.state.picked || this.state.saving.size > 0) {\n this.pendingBoard = board\n return\n }\n\n // The server draws the default view; ask again for the user's search and filters.\n if (this.isQueried()) {\n this.board = { ...board, columns: this.board.columns, lanes: this.board.lanes }\n void this.refresh()\n return\n }\n\n this.applyBoard(board)\n }\n\n private isQueried(): boolean {\n return this.state.search !== (this.board.search ?? '') || this.state.filters !== null\n }\n\n private applyBoard(board: KanbanSchema): void {\n const known = new Set(this.board.columns.map((column) => column.key))\n const collapsed = new Set(\n [...this.state.collapsed].filter((key) => board.columns.some((c) => c.key === key)),\n )\n\n for (const column of board.columns) {\n if (!known.has(column.key) && column.collapsed) collapsed.add(column.key)\n }\n\n this.board = board\n this.store.update({ columns: board.columns, collapsed })\n }\n\n // ------------------------------------------------------------- Columns and lanes\n\n isCollapsed(column: string): boolean {\n return this.state.collapsed.has(column)\n }\n\n toggleColumn(column: string, collapsed = !this.isCollapsed(column)): void {\n const next = new Set(this.state.collapsed)\n\n if (collapsed) next.add(column)\n else next.delete(column)\n\n this.store.update({ collapsed: next })\n }\n\n toggleLane(lane: string, collapsed = !this.state.collapsedLanes.has(lane)): void {\n const next = new Set(this.state.collapsedLanes)\n\n if (collapsed) next.add(lane)\n else next.delete(lane)\n\n this.store.update({ collapsedLanes: next })\n }\n\n // ------------------------------------------------------------- Moving\n\n canPick(key: string): boolean {\n const found = locateKanbanCard(this.state.columns, key)\n\n return Boolean(this.board.movable && found && found.card.movable !== false)\n }\n\n /** Pick a card up (Space, or the start of a drag). */\n pick(key: string, via: KanbanPicked['via'] = 'keyboard'): boolean {\n const origin = kanbanCardPosition(this.state.columns, key)\n\n if (!origin || !this.canPick(key) || this.state.saving.has(key)) {\n return false\n }\n\n this.store.update({\n picked: { key, origin, target: origin, via },\n refused: null,\n announcement: this.describe(\n 'kanban.picked_up',\n key,\n ':title picked up in :column, position :position of :count.',\n ),\n })\n\n return true\n }\n\n /** Why the picked card may not go to `target`, or null. */\n refusal(target: KanbanTarget): KanbanRefusal | null {\n const picked = this.state.picked\n\n if (!picked) return 'locked'\n\n return kanbanRefusal(this.board, this.state.columns, picked.key, picked.origin, target)\n }\n\n /** Show the picked card at `target` (a preview until it is dropped). */\n hover(target: KanbanTarget): boolean {\n const picked = this.state.picked\n\n if (!picked) return false\n if (sameTarget(picked.target, target)) return true\n\n const reason = this.refusal(target)\n\n if (reason) {\n this.store.update({\n refused: { column: target.column, reason },\n announcement:\n picked.via === 'keyboard'\n ? this.refusedMessage(picked.key, target.column)\n : this.state.announcement,\n })\n\n return false\n }\n\n const columns = moveKanbanCard(this.state.columns, picked.key, target)\n\n if (!columns) return false\n\n const position = kanbanCardPosition(columns, picked.key) ?? target\n\n this.store.update({\n columns,\n picked: { ...picked, target: position },\n refused: null,\n })\n\n if (picked.via === 'keyboard') {\n this.store.update({\n announcement: this.describe(\n 'kanban.moved',\n picked.key,\n ':title is in :column, position :position of :count.',\n ),\n })\n }\n\n return true\n }\n\n /** Put the picked card back where it was picked up. */\n cancel(): void {\n const picked = this.state.picked\n\n if (!picked) return\n\n const columns =\n moveKanbanCard(this.state.columns, picked.key, picked.origin) ?? this.state.columns\n const column = columns.find((candidate) => candidate.key === picked.origin.column)\n\n this.store.update({\n columns,\n picked: null,\n refused: null,\n announcement: this.env.t(\n 'kanban.cancelled',\n { title: this.title(picked.key), column: column?.label ?? '' },\n 'Move cancelled. :title is back in :column.',\n ),\n })\n this.flushPending()\n }\n\n /** Drop the picked card where it is shown and save the move. */\n async drop(): Promise<boolean> {\n const picked = this.state.picked\n\n if (!picked) return false\n\n if (sameTarget(picked.origin, picked.target)) {\n this.store.update({\n picked: null,\n refused: null,\n announcement: this.describe(\n 'kanban.dropped',\n picked.key,\n ':title dropped in :column, position :position of :count.',\n ),\n })\n this.flushPending()\n\n return false\n }\n\n const saving = new Set(this.state.saving).add(picked.key)\n\n this.store.update({\n picked: null,\n refused: null,\n saving,\n announcement: this.describe(\n 'kanban.dropped',\n picked.key,\n ':title dropped in :column, position :position of :count.',\n ),\n })\n\n return this.save(picked.key, picked.origin)\n }\n\n /** Move a card in one step (the keyboard, or a drop the renderer worked out itself). */\n async moveTo(key: string, target: KanbanTarget): Promise<boolean> {\n if (!this.pick(key, 'pointer')) return false\n if (!this.hover(target)) {\n this.cancel()\n return false\n }\n\n return this.drop()\n }\n\n private async save(key: string, origin: KanbanTarget): Promise<boolean> {\n const action: ActionSchema = { type: 'Action', name: this.board.moveAction, hasAction: true }\n const context: ActionContext = {\n token: this.env.token,\n component: this.board.actionComponent,\n record: key,\n arguments: {\n [this.board.argumentsKey]: kanbanMovePayload(this.board, this.state.columns, key),\n },\n }\n\n try {\n const result = await this.env.runner.executeJson<KanbanMoveResult>(action, context)\n\n if (result && result.ok === false) {\n throw new Error('refused')\n }\n\n this.applySaved(key, result)\n this.env.notify(result?.notifications ?? [])\n\n return true\n } catch (error) {\n const columns = moveKanbanCard(this.state.columns, key, origin) ?? this.state.columns\n const message =\n error instanceof HttpError && error.status === 422\n ? // A `ValidationException` from `beforeCardMoved()` under any key (`move`).\n (error.errors[this.board.argumentsKey]?.[0] ??\n Object.values(error.errors).find((messages) => messages?.[0])?.[0])\n : error instanceof HttpError && error.status === 403\n ? this.env.t('kanban.forbidden', {}, 'You are not allowed to move this card.')\n : undefined\n const text = message ?? this.env.t('kanban.move_failed', {}, 'The card could not be moved.')\n\n this.store.update({ columns, announcement: text })\n this.env.error(text)\n\n // The board changed under us: show the server's version.\n if (error instanceof HttpError && error.status === 422) this.refreshAfterMove = true\n\n return false\n } finally {\n const saving = new Set(this.state.saving)\n saving.delete(key)\n this.store.update({ saving })\n this.flushPending()\n }\n }\n\n private applySaved(key: string, result: KanbanMoveResult | null | undefined): void {\n const fresh = result?.card\n const counts = result?.counts ?? {}\n const columns = this.state.columns.map((column) => {\n const count = counts[column.key]\n const index = fresh ? column.cards.findIndex((card) => card.key === key) : -1\n\n if (count === undefined && index < 0) return column\n\n const cards =\n index >= 0 && fresh\n ? column.cards.map((card, at) =>\n at === index ? { ...fresh, lane: fresh.lane ?? card.lane } : card,\n )\n : column.cards\n\n return { ...column, cards, ...(count !== undefined ? { count } : {}) }\n })\n\n this.store.update({ columns })\n }\n\n private flushPending(): void {\n if (this.state.picked || this.state.saving.size > 0) return\n\n const board = this.pendingBoard\n this.pendingBoard = null\n\n if (board) {\n this.setBoard(board)\n } else if (this.refreshAfterMove) {\n this.refreshAfterMove = false\n void this.refresh()\n }\n }\n\n /** Move the picked card one step: within its lane, to the next lane or column. */\n step(direction: Direction): boolean {\n const picked = this.state.picked\n\n if (!picked) return false\n\n const { column, lane, index } = picked.target\n const columns = this.state.columns\n const current = columns.find((candidate) => candidate.key === column)\n const count = current ? kanbanCardsIn(current, lane).length : 0\n const lanes = this.lanes().map((row) => row?.key ?? null)\n const laneIndex = lanes.indexOf(lane)\n\n if (direction === 'up' || direction === 'down') {\n const within = direction === 'up' ? index - 1 : index + 1\n\n if (this.board.orderable && within >= 0 && within < count) {\n return this.hover({ column, lane, index: within })\n }\n\n const otherLane = lanes[laneIndex + (direction === 'up' ? -1 : 1)]\n\n if (otherLane === undefined || laneIndex < 0) return false\n\n const target = columns.find((candidate) => candidate.key === column)!\n const size = kanbanCardsIn(target, otherLane).length\n\n return this.hover({ column, lane: otherLane, index: direction === 'up' ? size : 0 })\n }\n\n const start = columns.findIndex((candidate) => candidate.key === column)\n const delta = direction === 'previous' ? -1 : 1\n let first: string | null = null\n\n for (let at = start + delta; at >= 0 && at < columns.length; at += delta) {\n const candidate = columns[at]!\n\n if (this.isCollapsed(candidate.key)) continue\n\n first ??= candidate.key\n\n const size = kanbanCardsIn(candidate, lane).length\n const target = { column: candidate.key, lane, index: Math.min(index, size) }\n\n if (!this.refusal(target)) {\n return this.hover(target)\n }\n }\n\n if (first) {\n this.store.update({ announcement: this.refusedMessage(picked.key, first) })\n }\n\n return false\n }\n\n /**\n * Keyboard handling for a focused card. Space picks a card up and drops it, arrow keys\n * move the focus or the picked card (mirrored right to left), Escape cancels, Enter\n * opens the card.\n */\n handleKey(\n key: string,\n event: { key: string },\n direction: 'ltr' | 'rtl' = 'ltr',\n ): KanbanKeyResult {\n const next = direction === 'rtl' ? 'ArrowLeft' : 'ArrowRight'\n const previous = direction === 'rtl' ? 'ArrowRight' : 'ArrowLeft'\n const picked = this.state.picked\n\n if (picked && picked.key === key) {\n switch (event.key) {\n case ' ':\n case 'Spacebar':\n case 'Enter':\n void this.drop()\n return { handled: true, focus: key }\n case 'Escape':\n this.cancel()\n return { handled: true, focus: key }\n case 'ArrowUp':\n this.step('up')\n return { handled: true, focus: key }\n case 'ArrowDown':\n this.step('down')\n return { handled: true, focus: key }\n case next:\n this.step('next')\n return { handled: true, focus: key }\n case previous:\n this.step('previous')\n return { handled: true, focus: key }\n case 'Tab':\n this.cancel()\n return { handled: false }\n default:\n return { handled: true, focus: key }\n }\n }\n\n switch (event.key) {\n case ' ':\n case 'Spacebar':\n if (!this.pick(key)) {\n this.store.update({\n announcement: this.env.t(\n 'kanban.not_allowed',\n {\n title: this.title(key),\n column: locateKanbanCard(this.state.columns, key)?.column.label ?? '',\n },\n ':title cannot be moved to :column.',\n ),\n })\n }\n return { handled: true, focus: key }\n case 'Enter':\n return { handled: true, open: true }\n case 'ArrowUp':\n case 'ArrowDown':\n case 'Home':\n case 'End':\n return { handled: true, focus: this.focusWithin(key, event.key) }\n case next:\n case previous:\n return { handled: true, focus: this.focusAcross(key, event.key === next ? 1 : -1) }\n default:\n return { handled: false }\n }\n }\n\n private focusWithin(key: string, keyName: string): string {\n const found = locateKanbanCard(this.state.columns, key)\n\n if (!found) return key\n\n const cards = kanbanCardsIn(found.column, found.lane)\n\n switch (keyName) {\n case 'Home':\n return cards[0]?.key ?? key\n case 'End':\n return cards[cards.length - 1]?.key ?? key\n case 'ArrowUp':\n return cards[found.laneIndex - 1]?.key ?? key\n default:\n return cards[found.laneIndex + 1]?.key ?? key\n }\n }\n\n private focusAcross(key: string, delta: number): string {\n const found = locateKanbanCard(this.state.columns, key)\n\n if (!found) return key\n\n const columns = this.state.columns\n\n for (let at = found.columnIndex + delta; at >= 0 && at < columns.length; at += delta) {\n const column = columns[at]!\n\n if (this.isCollapsed(column.key)) continue\n\n const cards = kanbanCardsIn(column, found.lane)\n\n if (cards.length > 0) {\n return cards[Math.min(found.laneIndex, cards.length - 1)]!.key\n }\n }\n\n return key\n }\n\n // ------------------------------------------------------------- Actions\n\n /** Open a card: its URL, else the card action with its record. */\n open(key: string): void {\n const found = locateKanbanCard(this.state.columns, key)\n\n if (!found || found.card.clickable === false) return\n\n if (found.card.url) {\n this.env.visit?.(found.card.url)\n return\n }\n\n if (this.board.cardAction) {\n void this.env.runner.trigger(this.board.cardAction, {\n token: this.env.token,\n component: this.board.actionComponent,\n record: key,\n })\n }\n }\n\n /** Open the quick-create modal for a column (and lane). */\n create(column: string, lane: string | null = null): void {\n if (!this.board.createAction) return\n\n void this.env.runner.trigger(this.board.createAction, {\n token: this.env.token,\n component: this.board.actionComponent,\n arguments: lane === null ? { column } : { column, lane },\n })\n }\n\n // ------------------------------------------------------------- Search, filters, polling\n\n setSearch(search: string, delay = 300): void {\n this.store.update({ search })\n this.schedule(delay)\n }\n\n setFilters(filters: Record<string, unknown> | null, delay = 300): void {\n this.store.update({ filters })\n this.schedule(delay)\n }\n\n private schedule(delay: number): void {\n if (this.searchTimer !== null) clearTimeout(this.searchTimer)\n\n this.searchTimer = setTimeout(() => {\n this.searchTimer = null\n void this.refresh()\n }, delay)\n }\n\n /** Fetch the board again with the search and filters (skipped while a card moves). */\n async refresh(): Promise<void> {\n if (this.state.picked || this.state.saving.size > 0) {\n this.refreshAfterMove = true\n return\n }\n\n const id = ++this.fetchId\n const action: ActionSchema = { type: 'Action', name: this.board.fetchAction, hasAction: true }\n const state = this.state\n\n this.store.update({ loading: true })\n\n try {\n const result = await this.env.runner.executeJson<KanbanFetchResult>(action, {\n token: this.env.token,\n component: this.board.actionComponent,\n arguments: {\n [this.board.argumentsKey]: {\n search: state.search,\n ...(state.filters ? { filters: state.filters } : {}),\n },\n },\n })\n\n // A newer request or a move started meanwhile: its answer wins.\n if (id !== this.fetchId || !result?.board) return\n\n if (this.state.picked || this.state.saving.size > 0) {\n this.pendingBoard = null\n this.refreshAfterMove = true\n return\n }\n\n this.applyBoard({ ...result.board, search: this.state.search })\n } catch {\n if (id === this.fetchId) {\n this.env.error(this.env.t('kanban.load_failed', {}, 'The board could not be loaded.'))\n }\n } finally {\n if (id === this.fetchId) this.store.update({ loading: false })\n }\n }\n\n /** Poll the board every `pollingInterval`; returns a function that stops. */\n startPolling(): () => void {\n if (!this.board.pollingInterval) return () => {}\n\n const scheduler = new PollScheduler(() => this.refresh())\n\n return scheduler.subscribe(this.board.actionComponent, this.board.pollingInterval)\n }\n\n destroy(): void {\n if (this.searchTimer !== null) clearTimeout(this.searchTimer)\n this.searchTimer = null\n }\n\n // ------------------------------------------------------------- Announcements\n\n private title(key: string): string {\n return locateKanbanCard(this.state.columns, key)?.card.title ?? ''\n }\n\n private describe(translationKey: string, key: string, fallback: string): string {\n const found = locateKanbanCard(this.state.columns, key)\n\n if (!found) return ''\n\n return this.env.t(\n translationKey,\n {\n title: found.card.title,\n column: found.column.label,\n position: found.laneIndex + 1,\n count: found.laneCount,\n },\n fallback,\n )\n }\n\n private refusedMessage(key: string, column: string): string {\n const target = this.state.columns.find((candidate) => candidate.key === column)\n\n return this.env.t(\n 'kanban.not_allowed',\n { title: this.title(key), column: target?.label ?? column },\n ':title cannot be moved to :column.',\n )\n }\n}\n\n// ----------------------------------------------------------------- Pointer dragging\n\nexport interface KanbanPointerOptions {\n /** `document.elementFromPoint` (injectable for tests). */\n elementFromPoint?: (x: number, y: number) => Element | null\n /** Milliseconds a touch must rest on a card before it drags (shorter moves scroll). */\n touchDelay?: number\n}\n\nconst CARD = '[data-slot=\"kanban-card\"]'\nconst DROPZONE = '[data-slot=\"kanban-dropzone\"]'\nconst SCROLLER = '[data-slot=\"kanban-scroller\"]'\nconst NO_DRAG =\n 'button, a, input, textarea, select, label, [contenteditable], [data-kanban-no-drag]'\n\n/**\n * Drag cards with a mouse, pen or finger. The board root delegates `pointerdown`; cards\n * are `[data-slot=\"kanban-card\"][data-key]` (not `[data-movable=\"false\"]`) inside\n * `[data-slot=\"kanban-dropzone\"][data-column][data-lane]` lists. A touch drags after a\n * short press (a quick swipe scrolls); a mouse after a few pixels. While dragging, a copy\n * of the card follows the pointer, the card itself moves to where it would land, the board\n * scrolls near its edges and Escape cancels. Returns a function that detaches everything.\n */\nexport function attachKanbanPointer(\n root: HTMLElement,\n controller: KanbanController,\n options: KanbanPointerOptions = {},\n): () => void {\n const doc = root.ownerDocument\n const view = doc.defaultView ?? window\n const fromPoint =\n options.elementFromPoint ?? ((x: number, y: number) => doc.elementFromPoint(x, y))\n const touchDelay = options.touchDelay ?? 220\n\n let pending: {\n id: number\n key: string\n element: HTMLElement\n x: number\n y: number\n touch: boolean\n timer: ReturnType<typeof setTimeout> | null\n } | null = null\n let drag: {\n id: number\n key: string\n ghost: HTMLElement\n offsetX: number\n offsetY: number\n x: number\n y: number\n frame: number | null\n } | null = null\n\n const preventTouch = (event: Event) => {\n if (drag) event.preventDefault()\n }\n\n const suppressClick = (event: Event) => {\n event.preventDefault()\n event.stopPropagation()\n }\n\n const begin = () => {\n if (!pending) return\n\n const { element, key, x, y, id, touch } = pending\n pending.timer = null\n\n if (!controller.pick(key, 'pointer')) {\n pending = null\n return\n }\n\n const box = element.getBoundingClientRect()\n const ghost = element.cloneNode(true) as HTMLElement\n\n ghost.removeAttribute('id')\n ghost.setAttribute('data-slot', 'kanban-ghost')\n ghost.removeAttribute('data-picked')\n ghost.setAttribute('aria-hidden', 'true')\n ghost.querySelectorAll('[id]').forEach((node) => node.removeAttribute('id'))\n Object.assign(ghost.style, {\n position: 'fixed',\n left: '0px',\n top: '0px',\n width: `${box.width}px`,\n margin: '0',\n pointerEvents: 'none',\n zIndex: '60',\n transform: `translate(${box.left}px, ${box.top}px)`,\n })\n doc.body.appendChild(ghost)\n doc.body.setAttribute('data-kanban-dragging', '')\n\n drag = { id, key, ghost, offsetX: x - box.left, offsetY: y - box.top, x, y, frame: null }\n pending = null\n\n if (touch) (view.navigator as Navigator | undefined)?.vibrate?.(10)\n\n autoScroll()\n }\n\n const targetAt = (x: number, y: number) => {\n const zone = fromPoint(x, y)?.closest<HTMLElement>(DROPZONE)\n\n if (!zone || !root.contains(zone)) return null\n\n const column = zone.dataset.column\n\n if (!column) return null\n\n const lane = zone.dataset.lane ?? null\n const cards = Array.from(zone.querySelectorAll<HTMLElement>(CARD)).filter(\n (card) => card.dataset.key !== drag?.key,\n )\n let index = 0\n\n for (const card of cards) {\n const box = card.getBoundingClientRect()\n\n if (y > box.top + box.height / 2) index++\n }\n\n return { column, lane: lane === '' ? null : lane, index }\n }\n\n const position = (x: number, y: number) => {\n if (!drag) return\n\n drag.x = x\n drag.y = y\n drag.ghost.style.transform = `translate(${x - drag.offsetX}px, ${y - drag.offsetY}px) rotate(2deg)`\n\n const target = targetAt(x, y)\n\n if (target) controller.hover(target)\n }\n\n // Scroll the board (and the window) while the pointer rests near an edge.\n const autoScroll = () => {\n if (!drag) return\n\n const edge = 48\n const speed = 14\n const scroller = root.querySelector<HTMLElement>(SCROLLER)\n\n if (scroller) {\n const box = scroller.getBoundingClientRect()\n\n if (drag.x < box.left + edge) scroller.scrollLeft -= speed\n else if (drag.x > box.right - edge) scroller.scrollLeft += speed\n }\n\n const zone = fromPoint(drag.x, drag.y)?.closest<HTMLElement>(DROPZONE)\n\n if (zone && zone.scrollHeight > zone.clientHeight) {\n const box = zone.getBoundingClientRect()\n\n if (drag.y < box.top + edge) zone.scrollTop -= speed\n else if (drag.y > box.bottom - edge) zone.scrollTop += speed\n }\n\n if (drag.y < edge) view.scrollBy?.(0, -speed)\n else if (drag.y > view.innerHeight - edge) view.scrollBy?.(0, speed)\n\n drag.frame = view.requestAnimationFrame?.(autoScroll) ?? null\n }\n\n const finish = (cancelled: boolean) => {\n if (pending?.timer) clearTimeout(pending.timer)\n pending = null\n\n if (drag) {\n if (drag.frame !== null) view.cancelAnimationFrame?.(drag.frame)\n drag.ghost.remove()\n doc.body.removeAttribute('data-kanban-dragging')\n drag = null\n\n if (cancelled) {\n controller.cancel()\n } else {\n void controller.drop()\n // The release would also click the card: swallow that one click.\n view.addEventListener('click', suppressClick, { capture: true, once: true })\n setTimeout(() => view.removeEventListener('click', suppressClick, { capture: true }), 0)\n }\n }\n\n doc.removeEventListener('pointermove', onMove)\n doc.removeEventListener('pointerup', onUp)\n doc.removeEventListener('pointercancel', onCancel)\n doc.removeEventListener('keydown', onKey, true)\n doc.removeEventListener('touchmove', preventTouch)\n }\n\n const onMove = (event: PointerEvent) => {\n if (drag && event.pointerId === drag.id) {\n event.preventDefault()\n position(event.clientX, event.clientY)\n return\n }\n\n if (!pending || event.pointerId !== pending.id) return\n\n const distance = Math.hypot(event.clientX - pending.x, event.clientY - pending.y)\n\n if (pending.touch) {\n // Moving before the press settles is a scroll.\n if (distance > 8) finish(true)\n return\n }\n\n if (distance > 5) {\n begin()\n position(event.clientX, event.clientY)\n }\n }\n\n const onUp = (event: PointerEvent) => {\n if ((drag && event.pointerId === drag.id) || (pending && event.pointerId === pending.id)) {\n finish(false)\n }\n }\n\n const onCancel = () => finish(true)\n\n const onKey = (event: KeyboardEvent) => {\n if (drag && event.key === 'Escape') {\n event.preventDefault()\n event.stopPropagation()\n finish(true)\n }\n }\n\n const onDown = (event: PointerEvent) => {\n if (event.button !== 0 || drag || pending) return\n\n const target = event.target as Element | null\n const card = target?.closest<HTMLElement>(CARD)\n\n if (!card || !root.contains(card) || card.dataset.movable === 'false') return\n\n const blocker = target?.closest(NO_DRAG)\n\n if (blocker && card.contains(blocker)) return\n\n const key = card.dataset.key\n\n if (!key) return\n\n const touch = event.pointerType === 'touch'\n\n pending = {\n id: event.pointerId,\n key,\n element: card,\n x: event.clientX,\n y: event.clientY,\n touch,\n timer: null,\n }\n\n if (touch) pending.timer = setTimeout(begin, touchDelay)\n\n doc.addEventListener('pointermove', onMove)\n doc.addEventListener('pointerup', onUp)\n doc.addEventListener('pointercancel', onCancel)\n doc.addEventListener('keydown', onKey, true)\n doc.addEventListener('touchmove', preventTouch, { passive: false })\n }\n\n // Native drag and drop would fight the pointer handling (images, selected text).\n const onNativeDrag = (event: Event) => {\n if ((event.target as Element | null)?.closest?.(CARD)) event.preventDefault()\n }\n\n root.addEventListener('pointerdown', onDown)\n root.addEventListener('dragstart', onNativeDrag)\n\n return () => {\n finish(true)\n root.removeEventListener('pointerdown', onDown)\n root.removeEventListener('dragstart', onNativeDrag)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"kanban.js","names":[],"sources":["../src/kanban.ts"],"sourcesContent":["import type { ActionContext, ActionRunner } from './actions'\nimport { FormStore } from './form'\nimport { HttpError } from './http'\nimport type { Translator } from './i18n'\nimport { PollScheduler } from './poll'\nimport type { ActionSchema } from './schema/actions'\nimport type {\n KanbanCardSchema,\n KanbanColumnSchema,\n KanbanFetchResult,\n KanbanLaneSchema,\n KanbanMoveResult,\n KanbanSchema,\n} from './schema/kanban'\nimport type { NotificationSchema } from './schema/notifications'\nimport { createStore, type Store } from './store'\n\n/**\n * The framework-agnostic half of the Kanban board (alfasix/alfakit-kanban): pure\n * operations on the columns, a `KanbanController` holding the working copy, the picked-up\n * card, the search and the requests, and `attachKanbanPointer()`, which drives mouse, pen\n * and touch dragging over the DOM every renderer emits. Renderers only draw the state.\n *\n * Moves are optimistic: the card moves at once and returns when the server refuses. The\n * server checks every move again (record, policy, column, transition, WIP limit, lane,\n * tenant); the checks here only keep the UI from offering moves it would refuse.\n */\n\n/** The registry key of the board component. */\nexport const KANBAN_REGISTRY_KEY = 'kanban'\n\n/** The lane key of cards without a swimlane value (`Swimlane::NONE`). */\nexport const KANBAN_NO_LANE = '__none'\n\n/** Where a card is, or should go: a column, a lane (null on boards without lanes) and a position in that lane. */\nexport interface KanbanTarget {\n column: string\n lane: string | null\n /** Position among the lane's cards in the column, from 0. */\n index: number\n}\n\n/** Why a move is not offered. */\nexport type KanbanRefusal = 'locked' | 'transition' | 'wip' | 'lane'\n\nexport interface KanbanLocation {\n column: KanbanColumnSchema\n columnIndex: number\n card: KanbanCardSchema\n /** Position in the column's card list. */\n index: number\n lane: string | null\n /** Position among the lane's cards in the column. */\n laneIndex: number\n laneCount: number\n}\n\n/** The cards of one lane of a column (every card when `lane` is null). */\nexport function kanbanCardsIn(column: KanbanColumnSchema, lane: string | null): KanbanCardSchema[] {\n return lane === null\n ? column.cards\n : column.cards.filter((card) => (card.lane ?? KANBAN_NO_LANE) === lane)\n}\n\nexport function locateKanbanCard(\n columns: KanbanColumnSchema[],\n key: string,\n): KanbanLocation | null {\n for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) {\n const column = columns[columnIndex]!\n const index = column.cards.findIndex((card) => card.key === key)\n\n if (index >= 0) {\n const card = column.cards[index]!\n const lane = card.lane ?? null\n const laneCards = kanbanCardsIn(column, lane)\n\n return {\n column,\n columnIndex,\n card,\n index,\n lane,\n laneIndex: laneCards.indexOf(card),\n laneCount: laneCards.length,\n }\n }\n }\n\n return null\n}\n\n/** Where a card is now. */\nexport function kanbanCardPosition(\n columns: KanbanColumnSchema[],\n key: string,\n): KanbanTarget | null {\n const found = locateKanbanCard(columns, key)\n\n return found ? { column: found.column.key, lane: found.lane, index: found.laneIndex } : null\n}\n\nfunction sameTarget(a: KanbanTarget, b: KanbanTarget): boolean {\n return a.column === b.column && a.lane === b.lane && a.index === b.index\n}\n\n/**\n * The columns after moving a card to `target` (counts follow the card), or null when the\n * card or column doesn't exist.\n */\nexport function moveKanbanCard(\n columns: KanbanColumnSchema[],\n key: string,\n target: KanbanTarget,\n): KanbanColumnSchema[] | null {\n const from = locateKanbanCard(columns, key)\n const toIndex = columns.findIndex((column) => column.key === target.column)\n\n if (!from || toIndex < 0) {\n return null\n }\n\n const next = columns.map((column) => ({ ...column, cards: [...column.cards] }))\n const card: KanbanCardSchema =\n target.lane === null || target.lane === from.lane\n ? from.card\n : { ...from.card, lane: target.lane }\n\n next[from.columnIndex]!.cards.splice(from.index, 1)\n\n const destination = next[toIndex]!\n const laneCards = kanbanCardsIn(destination, target.lane)\n let at: number\n\n if (laneCards.length === 0) {\n at = target.lane === null ? 0 : destination.cards.length\n } else if (target.index >= laneCards.length) {\n at = destination.cards.indexOf(laneCards[laneCards.length - 1]!) + 1\n } else {\n at = destination.cards.indexOf(laneCards[Math.max(0, target.index)]!)\n }\n\n destination.cards.splice(at, 0, card)\n\n if (from.columnIndex !== toIndex) {\n const source = next[from.columnIndex]!\n source.count = Math.max(0, source.count - 1)\n source.total = Math.max(0, source.total - 1)\n destination.count += 1\n destination.total += 1\n }\n\n return next\n}\n\n/**\n * Why the board would refuse moving a card from `origin` (where it was picked up) to\n * `target`, or null when the move is offered. `columns` is the working copy, which may\n * already show the card elsewhere.\n */\nexport function kanbanRefusal(\n board: Pick<KanbanSchema, 'movable' | 'transitions' | 'laneMovable'>,\n columns: KanbanColumnSchema[],\n key: string,\n origin: KanbanTarget,\n target: KanbanTarget,\n): KanbanRefusal | null {\n const found = locateKanbanCard(columns, key)\n\n if (!board.movable || !found || found.card.movable === false) {\n return 'locked'\n }\n\n if (target.column !== origin.column) {\n const allowed = board.transitions?.[origin.column]\n\n if (allowed && !allowed.includes(target.column)) {\n return 'transition'\n }\n\n const column = columns.find((candidate) => candidate.key === target.column)\n\n if (column?.wipStrict && column.wipLimit !== undefined) {\n const held = column.count - (found.column.key === column.key ? 1 : 0)\n\n if (held >= column.wipLimit) {\n return 'wip'\n }\n }\n }\n\n if (target.lane !== origin.lane && !board.laneMovable) {\n return 'lane'\n }\n\n return null\n}\n\n/**\n * The move as the server's move action reads it: the target column and lane, and the\n * neighbour the card follows (or precedes) in its column.\n */\nexport function kanbanMovePayload(\n board: Pick<KanbanSchema, 'orderable' | 'lanes'>,\n columns: KanbanColumnSchema[],\n key: string,\n): Record<string, unknown> {\n const found = locateKanbanCard(columns, key)\n\n if (!found) {\n return {}\n }\n\n const payload: Record<string, unknown> = { column: found.column.key }\n\n if (board.lanes && found.lane !== null) {\n payload.lane = found.lane\n }\n\n if (!board.orderable) {\n return payload\n }\n\n const after = found.column.cards[found.index - 1]\n const before = found.column.cards[found.index + 1]\n\n if (after) payload.after = after.key\n else if (before) payload.before = before.key\n else payload.position = 'end'\n\n return payload\n}\n\n/** The initials of a name, for avatars without an image. */\nexport function kanbanInitials(name: string): string {\n const parts = name.trim().split(/\\s+/).filter(Boolean)\n const letters = parts.length > 1 ? [parts[0]!, parts[parts.length - 1]!] : parts.slice(0, 1)\n\n return letters\n .map((part) => Array.from(part)[0] ?? '')\n .join('')\n .toUpperCase()\n}\n\n/** `count` against `wipLimit`: `over`, `full` or null. */\nexport function kanbanWipState(column: KanbanColumnSchema): 'over' | 'full' | null {\n if (column.wipLimit === undefined) return null\n if (column.count > column.wipLimit) return 'over'\n\n return column.count === column.wipLimit ? 'full' : null\n}\n\n/** How many filters of a filters form value hold something. */\nexport function kanbanActiveFilters(values: Record<string, unknown> | null | undefined): number {\n const filled = (value: unknown): boolean =>\n Array.isArray(value)\n ? value.length > 0\n : value !== null && value !== undefined && value !== '' && value !== false\n\n return Object.values(values ?? {}).filter(\n (state) => state !== null && typeof state === 'object' && Object.values(state).some(filled),\n ).length\n}\n\n/**\n * The form store of the board's filters (null without filters). Searchable selects load\n * their options from the schema the server names (pages only).\n */\nexport function createKanbanFiltersStore(\n board: Pick<KanbanSchema, 'filters' | 'actionComponent'>,\n token: string,\n optionsUrl?: string,\n): FormStore | null {\n if (!board.filters) return null\n\n return new FormStore(\n {\n components: board.filters.components,\n state: board.filters.state,\n columns: board.filters.columns,\n },\n {\n token,\n component: board.filters.component ?? `${board.actionComponent}.kanbanFilters`,\n refreshUrl: undefined,\n optionsUrl: board.filters.component ? optionsUrl : undefined,\n },\n )\n}\n\nexport interface KanbanPicked {\n key: string\n /** Where the card was picked up (it returns there on cancel or a refused move). */\n origin: KanbanTarget\n /** Where it is shown now. */\n target: KanbanTarget\n via: 'keyboard' | 'pointer'\n}\n\nexport interface KanbanState {\n /** The working copy of the columns. */\n columns: KanbanColumnSchema[]\n collapsed: ReadonlySet<string>\n collapsedLanes: ReadonlySet<string>\n picked: KanbanPicked | null\n /** The column the picked card may not enter, and why (for styling a refused target). */\n refused: { column: string; reason: KanbanRefusal } | null\n /** Cards whose move is being saved. */\n saving: ReadonlySet<string>\n search: string\n /** The filters form values the board was last asked for; null for the defaults. */\n filters: Record<string, unknown> | null\n loading: boolean\n /** For a polite live region. */\n announcement: string\n}\n\nexport interface KanbanEnvironment {\n runner: Pick<ActionRunner, 'executeJson' | 'trigger'>\n token: string\n t: Translator['t']\n notify(notifications: NotificationSchema[]): void\n error(message: string): void\n /** Follow a card's URL (an Inertia visit). */\n visit?(url: string): void\n}\n\nexport interface KanbanKeyResult {\n handled: boolean\n /** The card to focus next. */\n focus?: string\n /** Open the card (Enter). */\n open?: boolean\n}\n\ntype Direction = 'up' | 'down' | 'previous' | 'next'\n\n/**\n * The state of one rendered board. `setBoard()` takes a fresh schema from the server\n * (after an action ran or a poll); collapsed columns and the search are kept.\n */\nexport class KanbanController {\n public readonly store: Store<KanbanState>\n\n private searchTimer: ReturnType<typeof setTimeout> | null = null\n private pendingBoard: KanbanSchema | null = null\n private refreshAfterMove = false\n private fetchId = 0\n\n constructor(\n private board: KanbanSchema,\n private env: KanbanEnvironment,\n ) {\n this.store = createStore<KanbanState>({\n columns: board.columns,\n collapsed: new Set(board.columns.filter((column) => column.collapsed).map((c) => c.key)),\n collapsedLanes: new Set(\n (board.lanes ?? []).filter((lane) => lane.collapsed).map((l) => l.key),\n ),\n picked: null,\n refused: null,\n saving: new Set(),\n search: board.search ?? '',\n filters: null,\n loading: false,\n announcement: '',\n })\n }\n\n get state(): KanbanState {\n return this.store.get()\n }\n\n get schema(): KanbanSchema {\n return this.board\n }\n\n /** The renderer's latest runner, token and translator. */\n setEnvironment(env: KanbanEnvironment): void {\n this.env = env\n }\n\n /** The rows to draw: the board's lanes, or one row (null) without swimlanes. */\n lanes(): Array<KanbanLaneSchema | null> {\n return this.board.lanes && this.board.lanes.length > 0 ? this.board.lanes : [null]\n }\n\n /** The schema changed on the server (an action ran): show it, keeping the user's view. */\n setBoard(board: KanbanSchema): void {\n if (this.state.picked || this.state.saving.size > 0) {\n this.pendingBoard = board\n return\n }\n\n // The server draws the default view; ask again for the user's search and filters.\n if (this.isQueried()) {\n this.board = { ...board, columns: this.board.columns, lanes: this.board.lanes }\n void this.refresh()\n return\n }\n\n this.applyBoard(board)\n }\n\n private isQueried(): boolean {\n return this.state.search !== (this.board.search ?? '') || this.state.filters !== null\n }\n\n private applyBoard(board: KanbanSchema): void {\n const known = new Set(this.board.columns.map((column) => column.key))\n const collapsed = new Set(\n [...this.state.collapsed].filter((key) => board.columns.some((c) => c.key === key)),\n )\n\n for (const column of board.columns) {\n if (!known.has(column.key) && column.collapsed) collapsed.add(column.key)\n }\n\n this.board = board\n this.store.update({ columns: board.columns, collapsed })\n }\n\n // ------------------------------------------------------------- Columns and lanes\n\n isCollapsed(column: string): boolean {\n return this.state.collapsed.has(column)\n }\n\n toggleColumn(column: string, collapsed = !this.isCollapsed(column)): void {\n const next = new Set(this.state.collapsed)\n\n if (collapsed) next.add(column)\n else next.delete(column)\n\n this.store.update({ collapsed: next })\n }\n\n toggleLane(lane: string, collapsed = !this.state.collapsedLanes.has(lane)): void {\n const next = new Set(this.state.collapsedLanes)\n\n if (collapsed) next.add(lane)\n else next.delete(lane)\n\n this.store.update({ collapsedLanes: next })\n }\n\n // ------------------------------------------------------------- Moving\n\n canPick(key: string): boolean {\n const found = locateKanbanCard(this.state.columns, key)\n\n return Boolean(this.board.movable && found && found.card.movable !== false)\n }\n\n /** Pick a card up (Space, or the start of a drag). */\n pick(key: string, via: KanbanPicked['via'] = 'keyboard'): boolean {\n const origin = kanbanCardPosition(this.state.columns, key)\n\n if (!origin || !this.canPick(key) || this.state.saving.has(key)) {\n return false\n }\n\n this.store.update({\n picked: { key, origin, target: origin, via },\n refused: null,\n announcement: this.describe(\n 'kanban.picked_up',\n key,\n ':title picked up in :column, position :position of :count.',\n ),\n })\n\n return true\n }\n\n /** Why the picked card may not go to `target`, or null. */\n refusal(target: KanbanTarget): KanbanRefusal | null {\n const picked = this.state.picked\n\n if (!picked) return 'locked'\n\n return kanbanRefusal(this.board, this.state.columns, picked.key, picked.origin, target)\n }\n\n /** Show the picked card at `target` (a preview until it is dropped). */\n hover(target: KanbanTarget): boolean {\n const picked = this.state.picked\n\n if (!picked) return false\n\n if (sameTarget(picked.target, target)) {\n if (this.state.refused) this.store.update({ refused: null })\n\n return true\n }\n\n const reason = this.refusal(target)\n\n if (reason) {\n // Still over the same refused column, already shown back home: nothing changes.\n if (\n this.state.refused?.column === target.column &&\n sameTarget(picked.target, picked.origin)\n ) {\n return false\n }\n\n // A dragged card goes back to where it was picked up while it hovers a column it\n // may not enter, so what is shown is what a release would do (nothing).\n const columns =\n picked.via === 'pointer'\n ? (moveKanbanCard(this.state.columns, picked.key, picked.origin) ?? this.state.columns)\n : this.state.columns\n\n this.store.update({\n columns,\n picked: picked.via === 'pointer' ? { ...picked, target: picked.origin } : picked,\n refused: { column: target.column, reason },\n announcement:\n picked.via === 'keyboard'\n ? this.refusedMessage(picked.key, target.column)\n : this.state.announcement,\n })\n\n return false\n }\n\n const columns = moveKanbanCard(this.state.columns, picked.key, target)\n\n if (!columns) return false\n\n const position = kanbanCardPosition(columns, picked.key) ?? target\n\n this.store.update({\n columns,\n picked: { ...picked, target: position },\n refused: null,\n })\n\n if (picked.via === 'keyboard') {\n this.store.update({\n announcement: this.describe(\n 'kanban.moved',\n picked.key,\n ':title is in :column, position :position of :count.',\n ),\n })\n }\n\n return true\n }\n\n /** Put the picked card back where it was picked up. */\n cancel(): void {\n const picked = this.state.picked\n\n if (!picked) return\n\n const columns =\n moveKanbanCard(this.state.columns, picked.key, picked.origin) ?? this.state.columns\n const column = columns.find((candidate) => candidate.key === picked.origin.column)\n\n this.store.update({\n columns,\n picked: null,\n refused: null,\n announcement: this.env.t(\n 'kanban.cancelled',\n { title: this.title(picked.key), column: column?.label ?? '' },\n 'Move cancelled. :title is back in :column.',\n ),\n })\n this.flushPending()\n }\n\n /** Drop the picked card where it is shown and save the move. */\n async drop(): Promise<boolean> {\n const picked = this.state.picked\n\n if (!picked) return false\n\n // Released over a column the card may not enter: refuse it there and then (the card\n // is back where it was picked up), never save the last allowed preview instead.\n const refused = picked.via === 'pointer' ? this.state.refused : null\n\n if (refused) {\n const columns =\n moveKanbanCard(this.state.columns, picked.key, picked.origin) ?? this.state.columns\n const text = this.refusedMessage(picked.key, refused.column)\n\n this.store.update({ columns, picked: null, refused: null, announcement: text })\n this.env.error(text)\n this.flushPending()\n\n return false\n }\n\n if (sameTarget(picked.origin, picked.target)) {\n this.store.update({\n picked: null,\n refused: null,\n announcement: this.describe(\n 'kanban.dropped',\n picked.key,\n ':title dropped in :column, position :position of :count.',\n ),\n })\n this.flushPending()\n\n return false\n }\n\n const saving = new Set(this.state.saving).add(picked.key)\n\n this.store.update({\n picked: null,\n refused: null,\n saving,\n announcement: this.describe(\n 'kanban.dropped',\n picked.key,\n ':title dropped in :column, position :position of :count.',\n ),\n })\n\n return this.save(picked.key, picked.origin)\n }\n\n /** Move a card in one step (the keyboard, or a drop the renderer worked out itself). */\n async moveTo(key: string, target: KanbanTarget): Promise<boolean> {\n if (!this.pick(key, 'pointer')) return false\n if (!this.hover(target)) {\n this.cancel()\n return false\n }\n\n return this.drop()\n }\n\n private async save(key: string, origin: KanbanTarget): Promise<boolean> {\n const action: ActionSchema = { type: 'Action', name: this.board.moveAction, hasAction: true }\n const context: ActionContext = {\n token: this.env.token,\n component: this.board.actionComponent,\n record: key,\n arguments: {\n [this.board.argumentsKey]: kanbanMovePayload(this.board, this.state.columns, key),\n },\n }\n\n try {\n const result = await this.env.runner.executeJson<KanbanMoveResult>(action, context)\n\n if (result && result.ok === false) {\n throw new Error('refused')\n }\n\n this.applySaved(key, result)\n this.env.notify(result?.notifications ?? [])\n\n return true\n } catch (error) {\n const columns = moveKanbanCard(this.state.columns, key, origin) ?? this.state.columns\n const message =\n error instanceof HttpError && error.status === 422\n ? // A `ValidationException` from `beforeCardMoved()` under any key (`move`).\n (error.errors[this.board.argumentsKey]?.[0] ??\n Object.values(error.errors).find((messages) => messages?.[0])?.[0])\n : error instanceof HttpError && error.status === 403\n ? this.env.t('kanban.forbidden', {}, 'You are not allowed to move this card.')\n : undefined\n const text = message ?? this.env.t('kanban.move_failed', {}, 'The card could not be moved.')\n\n this.store.update({ columns, announcement: text })\n this.env.error(text)\n\n // The board changed under us: show the server's version.\n if (error instanceof HttpError && error.status === 422) this.refreshAfterMove = true\n\n return false\n } finally {\n const saving = new Set(this.state.saving)\n saving.delete(key)\n this.store.update({ saving })\n this.flushPending()\n }\n }\n\n private applySaved(key: string, result: KanbanMoveResult | null | undefined): void {\n const fresh = result?.card\n const counts = result?.counts ?? {}\n const columns = this.state.columns.map((column) => {\n const count = counts[column.key]\n const index = fresh ? column.cards.findIndex((card) => card.key === key) : -1\n\n if (count === undefined && index < 0) return column\n\n const cards =\n index >= 0 && fresh\n ? column.cards.map((card, at) =>\n at === index ? { ...fresh, lane: fresh.lane ?? card.lane } : card,\n )\n : column.cards\n\n return { ...column, cards, ...(count !== undefined ? { count } : {}) }\n })\n\n this.store.update({ columns })\n }\n\n private flushPending(): void {\n if (this.state.picked || this.state.saving.size > 0) return\n\n const board = this.pendingBoard\n this.pendingBoard = null\n\n if (board) {\n this.setBoard(board)\n } else if (this.refreshAfterMove) {\n this.refreshAfterMove = false\n void this.refresh()\n }\n }\n\n /** Move the picked card one step: within its lane, to the next lane or column. */\n step(direction: Direction): boolean {\n const picked = this.state.picked\n\n if (!picked) return false\n\n const { column, lane, index } = picked.target\n const columns = this.state.columns\n const current = columns.find((candidate) => candidate.key === column)\n const count = current ? kanbanCardsIn(current, lane).length : 0\n const lanes = this.lanes().map((row) => row?.key ?? null)\n const laneIndex = lanes.indexOf(lane)\n\n if (direction === 'up' || direction === 'down') {\n const within = direction === 'up' ? index - 1 : index + 1\n\n if (this.board.orderable && within >= 0 && within < count) {\n return this.hover({ column, lane, index: within })\n }\n\n const otherLane = lanes[laneIndex + (direction === 'up' ? -1 : 1)]\n\n if (otherLane === undefined || laneIndex < 0) return false\n\n const target = columns.find((candidate) => candidate.key === column)!\n const size = kanbanCardsIn(target, otherLane).length\n\n return this.hover({ column, lane: otherLane, index: direction === 'up' ? size : 0 })\n }\n\n const start = columns.findIndex((candidate) => candidate.key === column)\n const delta = direction === 'previous' ? -1 : 1\n let first: string | null = null\n\n for (let at = start + delta; at >= 0 && at < columns.length; at += delta) {\n const candidate = columns[at]!\n\n if (this.isCollapsed(candidate.key)) continue\n\n first ??= candidate.key\n\n const size = kanbanCardsIn(candidate, lane).length\n const target = { column: candidate.key, lane, index: Math.min(index, size) }\n\n if (!this.refusal(target)) {\n return this.hover(target)\n }\n }\n\n if (first) {\n this.store.update({ announcement: this.refusedMessage(picked.key, first) })\n }\n\n return false\n }\n\n /**\n * Keyboard handling for a focused card. Space picks a card up and drops it, arrow keys\n * move the focus or the picked card (mirrored right to left), Escape cancels, Enter\n * opens the card.\n */\n handleKey(\n key: string,\n event: { key: string },\n direction: 'ltr' | 'rtl' = 'ltr',\n ): KanbanKeyResult {\n const next = direction === 'rtl' ? 'ArrowLeft' : 'ArrowRight'\n const previous = direction === 'rtl' ? 'ArrowRight' : 'ArrowLeft'\n const picked = this.state.picked\n\n if (picked && picked.key === key) {\n switch (event.key) {\n case ' ':\n case 'Spacebar':\n case 'Enter':\n void this.drop()\n return { handled: true, focus: key }\n case 'Escape':\n this.cancel()\n return { handled: true, focus: key }\n case 'ArrowUp':\n this.step('up')\n return { handled: true, focus: key }\n case 'ArrowDown':\n this.step('down')\n return { handled: true, focus: key }\n case next:\n this.step('next')\n return { handled: true, focus: key }\n case previous:\n this.step('previous')\n return { handled: true, focus: key }\n case 'Tab':\n this.cancel()\n return { handled: false }\n default:\n return { handled: true, focus: key }\n }\n }\n\n switch (event.key) {\n case ' ':\n case 'Spacebar':\n if (!this.pick(key)) {\n this.store.update({\n announcement: this.env.t(\n 'kanban.not_allowed',\n {\n title: this.title(key),\n column: locateKanbanCard(this.state.columns, key)?.column.label ?? '',\n },\n ':title cannot be moved to :column.',\n ),\n })\n }\n return { handled: true, focus: key }\n case 'Enter':\n return { handled: true, open: true }\n case 'ArrowUp':\n case 'ArrowDown':\n case 'Home':\n case 'End':\n return { handled: true, focus: this.focusWithin(key, event.key) }\n case next:\n case previous:\n return { handled: true, focus: this.focusAcross(key, event.key === next ? 1 : -1) }\n default:\n return { handled: false }\n }\n }\n\n private focusWithin(key: string, keyName: string): string {\n const found = locateKanbanCard(this.state.columns, key)\n\n if (!found) return key\n\n const cards = kanbanCardsIn(found.column, found.lane)\n\n switch (keyName) {\n case 'Home':\n return cards[0]?.key ?? key\n case 'End':\n return cards[cards.length - 1]?.key ?? key\n case 'ArrowUp':\n return cards[found.laneIndex - 1]?.key ?? key\n default:\n return cards[found.laneIndex + 1]?.key ?? key\n }\n }\n\n private focusAcross(key: string, delta: number): string {\n const found = locateKanbanCard(this.state.columns, key)\n\n if (!found) return key\n\n const columns = this.state.columns\n\n for (let at = found.columnIndex + delta; at >= 0 && at < columns.length; at += delta) {\n const column = columns[at]!\n\n if (this.isCollapsed(column.key)) continue\n\n const cards = kanbanCardsIn(column, found.lane)\n\n if (cards.length > 0) {\n return cards[Math.min(found.laneIndex, cards.length - 1)]!.key\n }\n }\n\n return key\n }\n\n // ------------------------------------------------------------- Actions\n\n /** Open a card: its URL, else the card action with its record. */\n open(key: string): void {\n const found = locateKanbanCard(this.state.columns, key)\n\n if (!found || found.card.clickable === false) return\n\n if (found.card.url) {\n this.env.visit?.(found.card.url)\n return\n }\n\n if (this.board.cardAction) {\n void this.env.runner.trigger(this.board.cardAction, {\n token: this.env.token,\n component: this.board.actionComponent,\n record: key,\n })\n }\n }\n\n /** Open the quick-create modal for a column (and lane). */\n create(column: string, lane: string | null = null): void {\n if (!this.board.createAction) return\n\n void this.env.runner.trigger(this.board.createAction, {\n token: this.env.token,\n component: this.board.actionComponent,\n arguments: lane === null ? { column } : { column, lane },\n })\n }\n\n // ------------------------------------------------------------- Search, filters, polling\n\n setSearch(search: string, delay = 300): void {\n this.store.update({ search })\n this.schedule(delay)\n }\n\n setFilters(filters: Record<string, unknown> | null, delay = 300): void {\n this.store.update({ filters })\n this.schedule(delay)\n }\n\n private schedule(delay: number): void {\n if (this.searchTimer !== null) clearTimeout(this.searchTimer)\n\n this.searchTimer = setTimeout(() => {\n this.searchTimer = null\n void this.refresh()\n }, delay)\n }\n\n /** Fetch the board again with the search and filters (skipped while a card moves). */\n async refresh(): Promise<void> {\n if (this.state.picked || this.state.saving.size > 0) {\n this.refreshAfterMove = true\n return\n }\n\n const id = ++this.fetchId\n const action: ActionSchema = { type: 'Action', name: this.board.fetchAction, hasAction: true }\n const state = this.state\n\n this.store.update({ loading: true })\n\n try {\n const result = await this.env.runner.executeJson<KanbanFetchResult>(action, {\n token: this.env.token,\n component: this.board.actionComponent,\n arguments: {\n [this.board.argumentsKey]: {\n search: state.search,\n ...(state.filters ? { filters: state.filters } : {}),\n },\n },\n })\n\n // A newer request or a move started meanwhile: its answer wins.\n if (id !== this.fetchId || !result?.board) return\n\n if (this.state.picked || this.state.saving.size > 0) {\n this.pendingBoard = null\n this.refreshAfterMove = true\n return\n }\n\n this.applyBoard({ ...result.board, search: this.state.search })\n } catch {\n if (id === this.fetchId) {\n this.env.error(this.env.t('kanban.load_failed', {}, 'The board could not be loaded.'))\n }\n } finally {\n if (id === this.fetchId) this.store.update({ loading: false })\n }\n }\n\n /** Poll the board every `pollingInterval`; returns a function that stops. */\n startPolling(): () => void {\n if (!this.board.pollingInterval) return () => {}\n\n const scheduler = new PollScheduler(() => this.refresh())\n\n return scheduler.subscribe(this.board.actionComponent, this.board.pollingInterval)\n }\n\n destroy(): void {\n if (this.searchTimer !== null) clearTimeout(this.searchTimer)\n this.searchTimer = null\n }\n\n // ------------------------------------------------------------- Announcements\n\n private title(key: string): string {\n return locateKanbanCard(this.state.columns, key)?.card.title ?? ''\n }\n\n private describe(translationKey: string, key: string, fallback: string): string {\n const found = locateKanbanCard(this.state.columns, key)\n\n if (!found) return ''\n\n return this.env.t(\n translationKey,\n {\n title: found.card.title,\n column: found.column.label,\n position: found.laneIndex + 1,\n count: found.laneCount,\n },\n fallback,\n )\n }\n\n private refusedMessage(key: string, column: string): string {\n const target = this.state.columns.find((candidate) => candidate.key === column)\n\n return this.env.t(\n 'kanban.not_allowed',\n { title: this.title(key), column: target?.label ?? column },\n ':title cannot be moved to :column.',\n )\n }\n}\n\n// ----------------------------------------------------------------- Pointer dragging\n\nexport interface KanbanPointerOptions {\n /** `document.elementFromPoint` (injectable for tests). */\n elementFromPoint?: (x: number, y: number) => Element | null\n /** Milliseconds a touch must rest on a card before it drags (shorter moves scroll). */\n touchDelay?: number\n}\n\nconst CARD = '[data-slot=\"kanban-card\"]'\nconst DROPZONE = '[data-slot=\"kanban-dropzone\"]'\nconst SCROLLER = '[data-slot=\"kanban-scroller\"]'\nconst NO_DRAG =\n 'button, a, input, textarea, select, label, [contenteditable], [data-kanban-no-drag]'\n\n/**\n * Drag cards with a mouse, pen or finger. The board root delegates `pointerdown`; cards\n * are `[data-slot=\"kanban-card\"][data-key]` (not `[data-movable=\"false\"]`) inside\n * `[data-slot=\"kanban-dropzone\"][data-column][data-lane]` lists. A touch drags after a\n * short press (a quick swipe scrolls); a mouse after a few pixels. While dragging, a copy\n * of the card follows the pointer, the card itself moves to where it would land, the board\n * scrolls near its edges and Escape cancels. Returns a function that detaches everything.\n */\nexport function attachKanbanPointer(\n root: HTMLElement,\n controller: KanbanController,\n options: KanbanPointerOptions = {},\n): () => void {\n const doc = root.ownerDocument\n const view = doc.defaultView ?? window\n const fromPoint =\n options.elementFromPoint ?? ((x: number, y: number) => doc.elementFromPoint(x, y))\n const touchDelay = options.touchDelay ?? 220\n\n let pending: {\n id: number\n key: string\n element: HTMLElement\n x: number\n y: number\n touch: boolean\n timer: ReturnType<typeof setTimeout> | null\n } | null = null\n let drag: {\n id: number\n key: string\n ghost: HTMLElement\n offsetX: number\n offsetY: number\n x: number\n y: number\n frame: number | null\n } | null = null\n\n const preventTouch = (event: Event) => {\n if (drag) event.preventDefault()\n }\n\n const suppressClick = (event: Event) => {\n event.preventDefault()\n event.stopPropagation()\n }\n\n const begin = () => {\n if (!pending) return\n\n const { element, key, x, y, id, touch } = pending\n pending.timer = null\n\n if (!controller.pick(key, 'pointer')) {\n pending = null\n return\n }\n\n const box = element.getBoundingClientRect()\n const ghost = element.cloneNode(true) as HTMLElement\n\n ghost.removeAttribute('id')\n ghost.setAttribute('data-slot', 'kanban-ghost')\n ghost.removeAttribute('data-picked')\n ghost.setAttribute('aria-hidden', 'true')\n ghost.querySelectorAll('[id]').forEach((node) => node.removeAttribute('id'))\n Object.assign(ghost.style, {\n position: 'fixed',\n left: '0px',\n top: '0px',\n width: `${box.width}px`,\n margin: '0',\n pointerEvents: 'none',\n zIndex: '60',\n transform: `translate(${box.left}px, ${box.top}px)`,\n })\n doc.body.appendChild(ghost)\n doc.body.setAttribute('data-kanban-dragging', '')\n\n drag = { id, key, ghost, offsetX: x - box.left, offsetY: y - box.top, x, y, frame: null }\n pending = null\n\n if (touch) (view.navigator as Navigator | undefined)?.vibrate?.(10)\n\n autoScroll()\n }\n\n const targetAt = (x: number, y: number) => {\n const zone = fromPoint(x, y)?.closest<HTMLElement>(DROPZONE)\n\n if (!zone || !root.contains(zone)) return null\n\n const column = zone.dataset.column\n\n if (!column) return null\n\n const lane = zone.dataset.lane ?? null\n const cards = Array.from(zone.querySelectorAll<HTMLElement>(CARD)).filter(\n (card) => card.dataset.key !== drag?.key,\n )\n let index = 0\n\n for (const card of cards) {\n const box = card.getBoundingClientRect()\n\n if (y > box.top + box.height / 2) index++\n }\n\n return { column, lane: lane === '' ? null : lane, index }\n }\n\n const position = (x: number, y: number) => {\n if (!drag) return\n\n drag.x = x\n drag.y = y\n drag.ghost.style.transform = `translate(${x - drag.offsetX}px, ${y - drag.offsetY}px) rotate(2deg)`\n\n const target = targetAt(x, y)\n\n if (target) controller.hover(target)\n }\n\n // Scroll the board (and the window) while the pointer rests near an edge.\n const autoScroll = () => {\n if (!drag) return\n\n const edge = 48\n const speed = 14\n const scroller = root.querySelector<HTMLElement>(SCROLLER)\n\n if (scroller) {\n const box = scroller.getBoundingClientRect()\n\n if (drag.x < box.left + edge) scroller.scrollLeft -= speed\n else if (drag.x > box.right - edge) scroller.scrollLeft += speed\n }\n\n const zone = fromPoint(drag.x, drag.y)?.closest<HTMLElement>(DROPZONE)\n\n if (zone && zone.scrollHeight > zone.clientHeight) {\n const box = zone.getBoundingClientRect()\n\n if (drag.y < box.top + edge) zone.scrollTop -= speed\n else if (drag.y > box.bottom - edge) zone.scrollTop += speed\n }\n\n if (drag.y < edge) view.scrollBy?.(0, -speed)\n else if (drag.y > view.innerHeight - edge) view.scrollBy?.(0, speed)\n\n drag.frame = view.requestAnimationFrame?.(autoScroll) ?? null\n }\n\n const finish = (cancelled: boolean) => {\n if (pending?.timer) clearTimeout(pending.timer)\n pending = null\n\n if (drag) {\n if (drag.frame !== null) view.cancelAnimationFrame?.(drag.frame)\n drag.ghost.remove()\n doc.body.removeAttribute('data-kanban-dragging')\n drag = null\n\n if (cancelled) {\n controller.cancel()\n } else {\n void controller.drop()\n // The release would also click the card: swallow that one click.\n view.addEventListener('click', suppressClick, { capture: true, once: true })\n setTimeout(() => view.removeEventListener('click', suppressClick, { capture: true }), 0)\n }\n }\n\n doc.removeEventListener('pointermove', onMove)\n doc.removeEventListener('pointerup', onUp)\n doc.removeEventListener('pointercancel', onCancel)\n doc.removeEventListener('keydown', onKey, true)\n doc.removeEventListener('touchmove', preventTouch)\n }\n\n const onMove = (event: PointerEvent) => {\n if (drag && event.pointerId === drag.id) {\n event.preventDefault()\n position(event.clientX, event.clientY)\n return\n }\n\n if (!pending || event.pointerId !== pending.id) return\n\n const distance = Math.hypot(event.clientX - pending.x, event.clientY - pending.y)\n\n if (pending.touch) {\n // Moving before the press settles is a scroll.\n if (distance > 8) finish(true)\n return\n }\n\n if (distance > 5) {\n begin()\n position(event.clientX, event.clientY)\n }\n }\n\n const onUp = (event: PointerEvent) => {\n if ((drag && event.pointerId === drag.id) || (pending && event.pointerId === pending.id)) {\n finish(false)\n }\n }\n\n const onCancel = () => finish(true)\n\n const onKey = (event: KeyboardEvent) => {\n if (drag && event.key === 'Escape') {\n event.preventDefault()\n event.stopPropagation()\n finish(true)\n }\n }\n\n const onDown = (event: PointerEvent) => {\n if (event.button !== 0 || drag || pending) return\n\n const target = event.target as Element | null\n const card = target?.closest<HTMLElement>(CARD)\n\n if (!card || !root.contains(card) || card.dataset.movable === 'false') return\n\n const blocker = target?.closest(NO_DRAG)\n\n if (blocker && card.contains(blocker)) return\n\n const key = card.dataset.key\n\n if (!key) return\n\n const touch = event.pointerType === 'touch'\n\n pending = {\n id: event.pointerId,\n key,\n element: card,\n x: event.clientX,\n y: event.clientY,\n touch,\n timer: null,\n }\n\n if (touch) pending.timer = setTimeout(begin, touchDelay)\n\n doc.addEventListener('pointermove', onMove)\n doc.addEventListener('pointerup', onUp)\n doc.addEventListener('pointercancel', onCancel)\n doc.addEventListener('keydown', onKey, true)\n doc.addEventListener('touchmove', preventTouch, { passive: false })\n }\n\n // Native drag and drop would fight the pointer handling (images, selected text).\n const onNativeDrag = (event: Event) => {\n if ((event.target as Element | null)?.closest?.(CARD)) event.preventDefault()\n }\n\n root.addEventListener('pointerdown', onDown)\n root.addEventListener('dragstart', onNativeDrag)\n\n return () => {\n finish(true)\n root.removeEventListener('pointerdown', onDown)\n root.removeEventListener('dragstart', onNativeDrag)\n }\n}\n"],"mappings":"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"}
package/dist/prefetch.js CHANGED
@@ -60,8 +60,11 @@ function installPrefetchInvalidation() {
60
60
  if (typeof window === "undefined" || installed) return () => {};
61
61
  installed = true;
62
62
  let restoring = false;
63
+ let pendingReload = null;
63
64
  const onPopstate = () => {
64
65
  restoring = true;
66
+ pendingReload?.cancel();
67
+ pendingReload = null;
65
68
  };
66
69
  window.addEventListener("popstate", onPopstate);
67
70
  const removeStart = router.on("start", () => {
@@ -80,7 +83,13 @@ function installPrefetchInvalidation() {
80
83
  preserveState: false,
81
84
  replace: true,
82
85
  async: true,
83
- headers: { "Cache-Control": "no-cache" }
86
+ headers: { "Cache-Control": "no-cache" },
87
+ onCancelToken: (token) => {
88
+ pendingReload = token;
89
+ },
90
+ onFinish: () => {
91
+ pendingReload = null;
92
+ }
84
93
  });
85
94
  return;
86
95
  }
@@ -88,6 +97,8 @@ function installPrefetchInvalidation() {
88
97
  });
89
98
  return () => {
90
99
  installed = false;
100
+ pendingReload?.cancel();
101
+ pendingReload = null;
91
102
  window.removeEventListener("popstate", onPopstate);
92
103
  removeStart();
93
104
  removeFinish();
@@ -1 +1 @@
1
- {"version":3,"file":"prefetch.js","names":[],"sources":["../src/prefetch.ts"],"sourcesContent":["import { router } from '@inertiajs/core'\nimport { resolvePartialProps } from './partial-props'\nimport { buildTableUrl, type TableQuery } from './table'\nimport { invalidateTableTotals } from './table-totals'\n\n/**\n * How long a prefetched page is reused: navigation links (Inertia `<Link prefetch>`) and\n * a table's next page. Short, and dropped after every mutation.\n */\nexport const PREFETCH_CACHE_FOR = '10s'\n\n/**\n * Prefetch a table page (hovering or focusing \"next\") with the same visit `visitTable()`\n * makes, so the click is answered from the cache. Browser only.\n */\nexport function prefetchTablePage(\n identifier: string | undefined,\n changes: TableQuery,\n only: string[] = ['page'],\n): void {\n if (typeof window === 'undefined') return\n\n const resolved = resolvePartialProps(only)\n\n router.prefetch(\n buildTableUrl(window.location.href, identifier, changes),\n {\n preserveState: true,\n preserveScroll: true,\n replace: true,\n ...(resolved === undefined ? {} : { only: resolved }),\n },\n { cacheFor: PREFETCH_CACHE_FOR },\n )\n}\n\n/** Mutations so far; a page last shown before the latest one has stale props. */\nlet mutationCount = 0\n\n/** The mutation count when each URL was last shown. */\nconst shownAt = new Map<string, number>()\n\n/**\n * Drop prefetched pages and mark deferred table totals stale: records may have changed.\n */\nexport function invalidatePrefetchedPages(): void {\n if (typeof window === 'undefined') return\n\n mutationCount++\n router.flushAll()\n invalidateTableTotals()\n}\n\n/**\n * Internal endpoints that change records or what a page shows (actions, inline edits,\n * reordering, undo, page methods, saved views and column order: W-S2-14).\n */\nconst MUTATING_ENDPOINT =\n /\\/(?:actions(?:\\/undo)?|table\\/(?:cell|cells|reorder|views|columns)|pages\\/call)\\/?$/\n\n/** Whether a successful request to `url` with `method` may have changed records. */\nexport function isMutatingRequest(url: string, method: string): boolean {\n if (method.toUpperCase() === 'GET') return false\n\n try {\n return MUTATING_ENDPOINT.test(new URL(url, 'http://localhost').pathname)\n } catch {\n return false\n }\n}\n\nlet installed = false\n\n/**\n * Invalidate prefetched pages and table totals after every Inertia visit that is not a\n * GET (actions, form saves), and reload a page the browser's Back or Forward button\n * restores from history when records changed since it was shown: Inertia restores the\n * saved props without asking the server. Returns a function that removes the listeners.\n */\nexport function installPrefetchInvalidation(): () => void {\n // Panels and standalone components on one page share the listeners.\n if (typeof window === 'undefined' || installed) return () => {}\n\n installed = true\n let restoring = false\n const onPopstate = (): void => {\n restoring = true\n }\n window.addEventListener('popstate', onPopstate)\n\n // A visit started after the popstate (a hash-only change fires no navigate) is not a restore.\n const removeStart = router.on('start', () => {\n restoring = false\n })\n const removeFinish = router.on('finish', (event) => {\n if (event.detail.visit.method !== 'get') invalidatePrefetchedPages()\n })\n const removeNavigate = router.on('navigate', (event) => {\n const url = event.detail.page.url\n const restored = restoring\n restoring = false\n\n // A URL never shown in this page load may come from before it: reload it too.\n if (restored && (shownAt.get(url) ?? -1) < mutationCount) {\n // Without the restored state: forms fill their inputs when they mount, so a reload that\n // kept it showed the saved page's old values (a restored page has no unsaved input).\n // `router.reload()` always keeps the state, hence the visit.\n router.visit(window.location.href, {\n preserveScroll: true,\n preserveState: false,\n replace: true,\n async: true,\n headers: { 'Cache-Control': 'no-cache' },\n })\n return\n }\n\n shownAt.set(url, mutationCount)\n })\n\n return () => {\n installed = false\n window.removeEventListener('popstate', onPopstate)\n removeStart()\n removeFinish()\n removeNavigate()\n }\n}\n"],"mappings":";;;;;;;;;AASA,MAAa,qBAAqB;;;;;AAMlC,SAAgB,kBACd,YACA,SACA,OAAiB,CAAC,MAAM,GAClB;CACN,IAAI,OAAO,WAAW,aAAa;CAEnC,MAAM,WAAW,oBAAoB,IAAI;CAEzC,OAAO,SACL,cAAc,OAAO,SAAS,MAAM,YAAY,OAAO,GACvD;EACE,eAAe;EACf,gBAAgB;EAChB,SAAS;EACT,GAAI,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM,SAAS;CACrD,GACA,EAAE,UAAA,MAA6B,CACjC;AACF;;AAGA,IAAI,gBAAgB;;AAGpB,MAAM,0BAAU,IAAI,IAAoB;;;;AAKxC,SAAgB,4BAAkC;CAChD,IAAI,OAAO,WAAW,aAAa;CAEnC;CACA,OAAO,SAAS;CAChB,sBAAsB;AACxB;;;;;AAMA,MAAM,oBACJ;;AAGF,SAAgB,kBAAkB,KAAa,QAAyB;CACtE,IAAI,OAAO,YAAY,MAAM,OAAO,OAAO;CAE3C,IAAI;EACF,OAAO,kBAAkB,KAAK,IAAI,IAAI,KAAK,kBAAkB,CAAC,CAAC,QAAQ;CACzE,QAAQ;EACN,OAAO;CACT;AACF;AAEA,IAAI,YAAY;;;;;;;AAQhB,SAAgB,8BAA0C;CAExD,IAAI,OAAO,WAAW,eAAe,WAAW,aAAa,CAAC;CAE9D,YAAY;CACZ,IAAI,YAAY;CAChB,MAAM,mBAAyB;EAC7B,YAAY;CACd;CACA,OAAO,iBAAiB,YAAY,UAAU;CAG9C,MAAM,cAAc,OAAO,GAAG,eAAe;EAC3C,YAAY;CACd,CAAC;CACD,MAAM,eAAe,OAAO,GAAG,WAAW,UAAU;EAClD,IAAI,MAAM,OAAO,MAAM,WAAW,OAAO,0BAA0B;CACrE,CAAC;CACD,MAAM,iBAAiB,OAAO,GAAG,aAAa,UAAU;EACtD,MAAM,MAAM,MAAM,OAAO,KAAK;EAC9B,MAAM,WAAW;EACjB,YAAY;EAGZ,IAAI,aAAa,QAAQ,IAAI,GAAG,KAAK,MAAM,eAAe;GAIxD,OAAO,MAAM,OAAO,SAAS,MAAM;IACjC,gBAAgB;IAChB,eAAe;IACf,SAAS;IACT,OAAO;IACP,SAAS,EAAE,iBAAiB,WAAW;GACzC,CAAC;GACD;EACF;EAEA,QAAQ,IAAI,KAAK,aAAa;CAChC,CAAC;CAED,aAAa;EACX,YAAY;EACZ,OAAO,oBAAoB,YAAY,UAAU;EACjD,YAAY;EACZ,aAAa;EACb,eAAe;CACjB;AACF"}
1
+ {"version":3,"file":"prefetch.js","names":[],"sources":["../src/prefetch.ts"],"sourcesContent":["import { router } from '@inertiajs/core'\nimport { resolvePartialProps } from './partial-props'\nimport { buildTableUrl, type TableQuery } from './table'\nimport { invalidateTableTotals } from './table-totals'\n\n/**\n * How long a prefetched page is reused: navigation links (Inertia `<Link prefetch>`) and\n * a table's next page. Short, and dropped after every mutation.\n */\nexport const PREFETCH_CACHE_FOR = '10s'\n\n/**\n * Prefetch a table page (hovering or focusing \"next\") with the same visit `visitTable()`\n * makes, so the click is answered from the cache. Browser only.\n */\nexport function prefetchTablePage(\n identifier: string | undefined,\n changes: TableQuery,\n only: string[] = ['page'],\n): void {\n if (typeof window === 'undefined') return\n\n const resolved = resolvePartialProps(only)\n\n router.prefetch(\n buildTableUrl(window.location.href, identifier, changes),\n {\n preserveState: true,\n preserveScroll: true,\n replace: true,\n ...(resolved === undefined ? {} : { only: resolved }),\n },\n { cacheFor: PREFETCH_CACHE_FOR },\n )\n}\n\n/** Mutations so far; a page last shown before the latest one has stale props. */\nlet mutationCount = 0\n\n/** The mutation count when each URL was last shown. */\nconst shownAt = new Map<string, number>()\n\n/**\n * Drop prefetched pages and mark deferred table totals stale: records may have changed.\n */\nexport function invalidatePrefetchedPages(): void {\n if (typeof window === 'undefined') return\n\n mutationCount++\n router.flushAll()\n invalidateTableTotals()\n}\n\n/**\n * Internal endpoints that change records or what a page shows (actions, inline edits,\n * reordering, undo, page methods, saved views and column order: W-S2-14).\n */\nconst MUTATING_ENDPOINT =\n /\\/(?:actions(?:\\/undo)?|table\\/(?:cell|cells|reorder|views|columns)|pages\\/call)\\/?$/\n\n/** Whether a successful request to `url` with `method` may have changed records. */\nexport function isMutatingRequest(url: string, method: string): boolean {\n if (method.toUpperCase() === 'GET') return false\n\n try {\n return MUTATING_ENDPOINT.test(new URL(url, 'http://localhost').pathname)\n } catch {\n return false\n }\n}\n\nlet installed = false\n\n/**\n * Invalidate prefetched pages and table totals after every Inertia visit that is not a\n * GET (actions, form saves), and reload a page the browser's Back or Forward button\n * restores from history when records changed since it was shown: Inertia restores the\n * saved props without asking the server. Returns a function that removes the listeners.\n */\nexport function installPrefetchInvalidation(): () => void {\n // Panels and standalone components on one page share the listeners.\n if (typeof window === 'undefined' || installed) return () => {}\n\n installed = true\n let restoring = false\n // The reload of a restored page still in flight. Another Back or Forward cancels it: its\n // response would replace the history entry the browser has moved on to.\n let pendingReload: { cancel(): void } | null = null\n const onPopstate = (): void => {\n restoring = true\n pendingReload?.cancel()\n pendingReload = null\n }\n window.addEventListener('popstate', onPopstate)\n\n // A visit started after the popstate (a hash-only change fires no navigate) is not a restore.\n const removeStart = router.on('start', () => {\n restoring = false\n })\n const removeFinish = router.on('finish', (event) => {\n if (event.detail.visit.method !== 'get') invalidatePrefetchedPages()\n })\n const removeNavigate = router.on('navigate', (event) => {\n const url = event.detail.page.url\n const restored = restoring\n restoring = false\n\n // A URL never shown in this page load may come from before it: reload it too.\n if (restored && (shownAt.get(url) ?? -1) < mutationCount) {\n // Without the restored state: forms fill their inputs when they mount, so a reload that\n // kept it showed the saved page's old values (a restored page has no unsaved input).\n // `router.reload()` always keeps the state, hence the visit.\n router.visit(window.location.href, {\n preserveScroll: true,\n preserveState: false,\n replace: true,\n async: true,\n headers: { 'Cache-Control': 'no-cache' },\n onCancelToken: (token) => {\n pendingReload = token\n },\n onFinish: () => {\n pendingReload = null\n },\n })\n return\n }\n\n shownAt.set(url, mutationCount)\n })\n\n return () => {\n installed = false\n pendingReload?.cancel()\n pendingReload = null\n window.removeEventListener('popstate', onPopstate)\n removeStart()\n removeFinish()\n removeNavigate()\n }\n}\n"],"mappings":";;;;;;;;;AASA,MAAa,qBAAqB;;;;;AAMlC,SAAgB,kBACd,YACA,SACA,OAAiB,CAAC,MAAM,GAClB;CACN,IAAI,OAAO,WAAW,aAAa;CAEnC,MAAM,WAAW,oBAAoB,IAAI;CAEzC,OAAO,SACL,cAAc,OAAO,SAAS,MAAM,YAAY,OAAO,GACvD;EACE,eAAe;EACf,gBAAgB;EAChB,SAAS;EACT,GAAI,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM,SAAS;CACrD,GACA,EAAE,UAAA,MAA6B,CACjC;AACF;;AAGA,IAAI,gBAAgB;;AAGpB,MAAM,0BAAU,IAAI,IAAoB;;;;AAKxC,SAAgB,4BAAkC;CAChD,IAAI,OAAO,WAAW,aAAa;CAEnC;CACA,OAAO,SAAS;CAChB,sBAAsB;AACxB;;;;;AAMA,MAAM,oBACJ;;AAGF,SAAgB,kBAAkB,KAAa,QAAyB;CACtE,IAAI,OAAO,YAAY,MAAM,OAAO,OAAO;CAE3C,IAAI;EACF,OAAO,kBAAkB,KAAK,IAAI,IAAI,KAAK,kBAAkB,CAAC,CAAC,QAAQ;CACzE,QAAQ;EACN,OAAO;CACT;AACF;AAEA,IAAI,YAAY;;;;;;;AAQhB,SAAgB,8BAA0C;CAExD,IAAI,OAAO,WAAW,eAAe,WAAW,aAAa,CAAC;CAE9D,YAAY;CACZ,IAAI,YAAY;CAGhB,IAAI,gBAA2C;CAC/C,MAAM,mBAAyB;EAC7B,YAAY;EACZ,eAAe,OAAO;EACtB,gBAAgB;CAClB;CACA,OAAO,iBAAiB,YAAY,UAAU;CAG9C,MAAM,cAAc,OAAO,GAAG,eAAe;EAC3C,YAAY;CACd,CAAC;CACD,MAAM,eAAe,OAAO,GAAG,WAAW,UAAU;EAClD,IAAI,MAAM,OAAO,MAAM,WAAW,OAAO,0BAA0B;CACrE,CAAC;CACD,MAAM,iBAAiB,OAAO,GAAG,aAAa,UAAU;EACtD,MAAM,MAAM,MAAM,OAAO,KAAK;EAC9B,MAAM,WAAW;EACjB,YAAY;EAGZ,IAAI,aAAa,QAAQ,IAAI,GAAG,KAAK,MAAM,eAAe;GAIxD,OAAO,MAAM,OAAO,SAAS,MAAM;IACjC,gBAAgB;IAChB,eAAe;IACf,SAAS;IACT,OAAO;IACP,SAAS,EAAE,iBAAiB,WAAW;IACvC,gBAAgB,UAAU;KACxB,gBAAgB;IAClB;IACA,gBAAgB;KACd,gBAAgB;IAClB;GACF,CAAC;GACD;EACF;EAEA,QAAQ,IAAI,KAAK,aAAa;CAChC,CAAC;CAED,aAAa;EACX,YAAY;EACZ,eAAe,OAAO;EACtB,gBAAgB;EAChB,OAAO,oBAAoB,YAAY,UAAU;EACjD,YAAY;EACZ,aAAa;EACb,eAAe;CACjB;AACF"}
@@ -147,9 +147,9 @@ export type SonnerPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-l
147
147
  /**
148
148
  * The direction overlays anchored to a screen edge (the toaster, the schema inspector's
149
149
  * sheet) are placed by: the panel's reading direction, except in starter kit mode, where
150
- * the host app owns `<html dir>` and its layout (the kit's sidebar stays on the left in a
151
- * right-to-left panel language). There they take the document's side, so "top end" never
152
- * lands on the kit's sidebar, while their content keeps the panel's direction (W-D-03).
150
+ * they take the document's side (`<html dir>`, from the shared `alfakitDocument`; an app
151
+ * whose root view or sidebar was not updated may still be left to right), so "top end"
152
+ * never lands on the kit's sidebar, while their content keeps the panel's direction (W-D-03).
153
153
  */
154
154
  export declare function overlayPlacementDirection(direction: 'ltr' | 'rtl', starterKit: boolean | undefined): 'ltr' | 'rtl';
155
155
  /**
package/dist/realtime.js CHANGED
@@ -241,9 +241,9 @@ const PANEL_TOAST_OFFSET = { top: "4.5rem" };
241
241
  /**
242
242
  * The direction overlays anchored to a screen edge (the toaster, the schema inspector's
243
243
  * sheet) are placed by: the panel's reading direction, except in starter kit mode, where
244
- * the host app owns `<html dir>` and its layout (the kit's sidebar stays on the left in a
245
- * right-to-left panel language). There they take the document's side, so "top end" never
246
- * lands on the kit's sidebar, while their content keeps the panel's direction (W-D-03).
244
+ * they take the document's side (`<html dir>`, from the shared `alfakitDocument`; an app
245
+ * whose root view or sidebar was not updated may still be left to right), so "top end"
246
+ * never lands on the kit's sidebar, while their content keeps the panel's direction (W-D-03).
247
247
  */
248
248
  function overlayPlacementDirection(direction, starterKit) {
249
249
  if (starterKit !== true) return direction;
@@ -1 +1 @@
1
- {"version":3,"file":"realtime.js","names":[],"sources":["../src/realtime.ts"],"sourcesContent":["import type { NotificationSchema } from './schema/notifications'\nimport type { ToastPosition } from './schema/panel'\nimport type { RealtimeSchema } from './schema/realtime'\n\n/** Broadcast type of a toast pushed with `Notification::broadcast()`. */\nexport const BROADCAST_NOTIFICATION = 'alfakit.notification'\n\n/** Broadcast type sent by `sendToDatabase(..., isEventDispatched: true)`. */\nexport const DATABASE_NOTIFICATIONS_SENT = 'alfakit.database-notifications.sent'\n\n/** Window event the notifications menu listens to, to reload itself. */\nexport const DATABASE_NOTIFICATIONS_EVENT = 'alfakit:database-notifications-sent'\n\ntype NotificationCallback = (payload: Record<string, unknown>) => void\ntype EventCallback = (payload: Record<string, unknown>) => void\n\n/** The part of a Laravel Echo private channel Alfakit uses. */\nexport interface EchoChannelLike {\n notification(callback: NotificationCallback): unknown\n stopListeningForNotification?(callback: NotificationCallback): unknown\n listen?(event: string, callback: EventCallback): unknown\n stopListening?(event: string, callback?: EventCallback): unknown\n}\n\n/** The part of a Laravel Echo presence channel Alfakit uses (`Echo.join()`). */\nexport interface EchoPresenceChannelLike extends EchoChannelLike {\n here(callback: (members: unknown[]) => void): unknown\n joining(callback: (member: unknown) => void): unknown\n leaving(callback: (member: unknown) => void): unknown\n whisper?(event: string, data: Record<string, unknown>): unknown\n listenForWhisper?(event: string, callback: (data: Record<string, unknown>) => void): unknown\n}\n\n/** The part of Laravel Echo Alfakit uses (`window.Echo`). */\nexport interface EchoLike {\n private(channel: string): EchoChannelLike\n join?(channel: string): EchoPresenceChannelLike\n leave?(channel: string): void\n socketId?(): string | undefined\n}\n\n/**\n * The app's Laravel Echo instance, when one is set up as `window.Echo` (or was created by\n * `resolveEcho()`). Always null on the server, so SSR never touches it.\n */\nexport function getEcho(): EchoLike | null {\n if (typeof window === 'undefined') {\n return null\n }\n\n const echo = (window as unknown as { Echo?: unknown }).Echo\n\n if (echo && typeof (echo as EchoLike).private === 'function') {\n return echo as EchoLike\n }\n\n return createdEcho\n}\n\n// ---------------------------------------------------------------- Connection\n\n/** The settings `configureRealtime()` takes (`PanelSchema.realtime`). */\nexport type RealtimeConfig = RealtimeSchema\n\n/** Options for creating Echo: the panel's Echo options plus its auth endpoint and CSRF token. */\nexport type EchoConnectionOptions = Record<string, unknown> & {\n authEndpoint?: string\n csrfToken?: string\n}\n\n/**\n * Creates the Echo instance when the app has no `window.Echo` (`createAlfakit({ echo })`),\n * e.g. `(options) => new Echo({ ...options, broadcaster: 'reverb', Pusher })`.\n */\nexport type EchoFactory = (\n options: EchoConnectionOptions,\n) => EchoLike | null | undefined | Promise<EchoLike | null | undefined>\n\ntype ModuleLoader = (specifier: string) => Promise<Record<string, unknown>>\n\nlet realtimeConfig: (RealtimeConfig & { csrfToken?: string }) | null = null\nlet echoFactory: EchoFactory | null = null\nlet createdEcho: EchoLike | null = null\nlet pendingEcho: Promise<EchoLike | null> | null = null\n\n// The packages are optional peers the app installs: the specifier is a variable so bundlers\n// don't require them, and a missing package just means \"no Echo\".\nlet loadModule: ModuleLoader = (specifier) =>\n import(/* @vite-ignore */ /* webpackIgnore: true */ specifier) as Promise<Record<string, unknown>>\n\n/**\n * Set the panel's realtime settings (renderers call this with `panel.realtime` and the\n * CSRF token) and, optionally, an Echo instance or factory from `createAlfakit({ echo })`.\n */\nexport function configureRealtime(\n config: RealtimeConfig | null | undefined,\n options: { echo?: EchoLike | EchoFactory | null; csrfToken?: string } = {},\n): void {\n realtimeConfig = config ? { ...config, csrfToken: options.csrfToken } : null\n\n if (options.echo !== undefined) {\n if (typeof options.echo === 'function') {\n echoFactory = options.echo\n } else {\n createdEcho = options.echo\n echoFactory = null\n }\n }\n}\n\n/** The settings given to `configureRealtime()`. */\nexport function realtimeSettings(): RealtimeConfig | null {\n return realtimeConfig\n}\n\n/** Whether `Panel::realtime()` is on for this page. */\nexport function isRealtimeEnabled(): boolean {\n return Boolean(realtimeConfig?.live)\n}\n\n/**\n * The Echo instance to use: `window.Echo`, one made by the `createAlfakit({ echo })`\n * factory, or (with `config('alfakit.broadcasting.echo')`) one made from `laravel-echo`\n * and `pusher-js` if the app installed them. Null when none is available or on the server.\n */\nexport function resolveEcho(): Promise<EchoLike | null> {\n const existing = getEcho()\n\n if (existing || typeof window === 'undefined') {\n return Promise.resolve(existing)\n }\n\n pendingEcho ??= createEcho().then((echo) => {\n createdEcho = echo ?? null\n\n // Try again on the next call when nothing could be created.\n if (!createdEcho) pendingEcho = null\n\n return createdEcho\n })\n\n return pendingEcho\n}\n\nasync function createEcho(): Promise<EchoLike | null> {\n const config = realtimeConfig\n const options: EchoConnectionOptions = {\n ...(config?.echo ?? {}),\n ...(config?.authEndpoint ? { authEndpoint: config.authEndpoint } : {}),\n ...(config?.csrfToken ? { csrfToken: config.csrfToken } : {}),\n }\n\n try {\n if (echoFactory) {\n return (await echoFactory(options)) ?? null\n }\n\n if (!config?.live || !config.echo) {\n return null\n }\n\n const [echoModule, pusherModule] = await Promise.all([\n loadModule('laravel-echo'),\n loadModule('pusher-js'),\n ])\n const Echo = (echoModule.default ?? echoModule.Echo) as\n (new (options: Record<string, unknown>) => EchoLike) | undefined\n const Pusher = pusherModule.default ?? pusherModule.Pusher\n\n if (typeof Echo !== 'function') {\n return null\n }\n\n ;(window as unknown as { Pusher?: unknown }).Pusher ??= Pusher\n\n return new Echo({ broadcaster: 'reverb', ...options, Pusher })\n } catch {\n return null\n }\n}\n\n/** The socket id of the current Echo connection, sent as `X-Socket-ID` so Laravel's `toOthers()` skips this tab. */\nexport function currentSocketId(): string | undefined {\n try {\n return getEcho()?.socketId?.() || undefined\n } catch {\n return undefined\n }\n}\n\n/** `X-Socket-ID` when connected (merged into the requests of the renderers). */\nexport function realtimeHeaders(): Record<string, string> {\n const id = currentSocketId()\n\n return id ? { 'X-Socket-ID': id } : {}\n}\n\n/** Forget the connection and settings (tests). */\nexport function resetRealtime(options: { loader?: ModuleLoader } = {}): void {\n realtimeConfig = null\n echoFactory = null\n createdEcho = null\n pendingEcho = null\n channelRefs.clear()\n\n if (options.loader) loadModule = options.loader\n}\n\n// ---------------------------------------------------------------- Shared channels\n\nconst channelRefs = new Map<string, number>()\n\n/**\n * Use a channel of `echo` until the returned release function is called. Echo shares a\n * channel object per name, so it is left only when its last user releases it (a record's\n * presence avatars and conflict banner share its presence channel).\n */\nexport function acquireChannel<T extends 'private' | 'presence'>(\n echo: EchoLike,\n kind: T,\n name: string,\n): {\n channel: T extends 'presence' ? EchoPresenceChannelLike : EchoChannelLike\n release: () => void\n} | null {\n const channel = kind === 'presence' ? echo.join?.(name) : echo.private(name)\n\n if (!channel) return null\n\n const key = name\n channelRefs.set(key, (channelRefs.get(key) ?? 0) + 1)\n let released = false\n\n return {\n channel: channel as T extends 'presence' ? EchoPresenceChannelLike : EchoChannelLike,\n release: () => {\n if (released) return\n released = true\n\n const count = (channelRefs.get(key) ?? 1) - 1\n\n if (count <= 0) {\n channelRefs.delete(key)\n echo.leave?.(name)\n } else {\n channelRefs.set(key, count)\n }\n },\n }\n}\n\n/**\n * Run `connect` with Echo as soon as it is available: right away with `window.Echo`, else\n * once `resolveEcho()` settles. `onUnavailable` runs when there is no Echo. Returns the\n * cleanup, which also cancels a pending connection.\n */\nexport function withEcho(\n connect: (echo: EchoLike) => (() => void) | void,\n options: { echo?: EchoLike | null; onUnavailable?: () => (() => void) | void } = {},\n): () => void {\n let cleanup: (() => void) | void\n let active = true\n\n const start = (echo: EchoLike | null) => {\n if (!active) return\n\n cleanup = echo ? connect(echo) : options.onUnavailable?.()\n }\n\n if (options.echo !== undefined) {\n start(options.echo)\n } else {\n const echo = getEcho()\n\n if (echo || typeof window === 'undefined') {\n start(echo)\n } else {\n void resolveEcho().then(start)\n }\n }\n\n return () => {\n active = false\n cleanup?.()\n }\n}\n\n// ---------------------------------------------------------------- Record changes\n\n/** Echo event of `RecordChanged` (`broadcastAs()` is `alfakit.record`). */\nexport const RECORD_CHANGED_EVENT = '.alfakit.record'\n\n/** A record was created, updated, deleted or restored. Keys only: refetch to see the data. */\nexport interface RecordChange {\n event: 'created' | 'updated' | 'deleted' | 'restored'\n /** The model class with `-` for `\\\\`. */\n model: string\n key: string | number\n /** The user who changed it, when known. */\n by?: string | number | null\n /** Milliseconds since the epoch. */\n at?: number\n}\n\nfunction asRecordChange(payload: Record<string, unknown>): RecordChange | null {\n const event = payload.event\n const key = payload.key\n\n if (\n (event !== 'created' && event !== 'updated' && event !== 'deleted' && event !== 'restored') ||\n (typeof key !== 'string' && typeof key !== 'number')\n ) {\n return null\n }\n\n return {\n event,\n key,\n model: typeof payload.model === 'string' ? payload.model : '',\n by: typeof payload.by === 'string' || typeof payload.by === 'number' ? payload.by : null,\n at: typeof payload.at === 'number' ? payload.at : Date.now(),\n }\n}\n\n/**\n * Listen for record changes on a channel (`private` for models, `presence` for a record).\n * Returns the cleanup (a no-op without Echo).\n */\nexport function listenForRecordChanges(\n channelName: string,\n callback: (change: RecordChange) => void,\n options: { kind?: 'private' | 'presence'; echo?: EchoLike | null } = {},\n): () => void {\n return withEcho(\n (echo) => {\n const acquired = acquireChannel(echo, options.kind ?? 'private', channelName)\n\n if (!acquired) return\n\n const listener: EventCallback = (payload) => {\n const change = asRecordChange(payload)\n\n if (change) callback(change)\n }\n\n acquired.channel.listen?.(RECORD_CHANGED_EVENT, listener)\n\n return () => {\n acquired.channel.stopListening?.(RECORD_CHANGED_EVENT, listener)\n acquired.release()\n }\n },\n { echo: options.echo },\n )\n}\n\n// ---------------------------------------------------------------- Notifications\n\nexport interface RealtimeSubscriptionOptions {\n /** The user's private channel (`PanelSchema.realtime.channel`). */\n channel: string\n /** Defaults to `window.Echo`, or the Echo `resolveEcho()` creates. */\n echo?: EchoLike | null\n /** A broadcast toast arrived. */\n onNotification: (notification: NotificationSchema) => void\n /** A database notification was stored: reload the bell. */\n onDatabaseNotificationsSent?: () => void\n}\n\n/**\n * Listen for the user's broadcast notifications. Returns the unsubscribe function (a no-op\n * when Echo is missing). Each toast is delivered once, even if Echo reconnects or the\n * subscription is made twice (React strict mode).\n */\nexport function subscribeToNotifications(options: RealtimeSubscriptionOptions): () => void {\n if (!options.channel) {\n return () => {}\n }\n\n const seen = new Set<string>()\n\n return withEcho(\n (echo) => {\n let active = true\n\n const callback: NotificationCallback = (payload) => {\n if (!active) return\n\n if (payload.type === DATABASE_NOTIFICATIONS_SENT) {\n options.onDatabaseNotificationsSent?.()\n\n return\n }\n\n if (payload.format !== 'alfakit') return\n\n const id = typeof payload.id === 'string' ? payload.id : ''\n\n if (id !== '' && seen.has(id)) return\n if (id !== '') seen.add(id)\n\n const { type: _type, ...notification } = payload\n\n options.onNotification(notification as unknown as NotificationSchema)\n }\n\n const channel = echo.private(options.channel)\n channel.notification(callback)\n\n return () => {\n active = false\n channel.stopListeningForNotification?.(callback)\n }\n },\n { echo: options.echo },\n )\n}\n\n/** Tell mounted notification menus to reload (`DATABASE_NOTIFICATIONS_EVENT`). */\nexport function announceDatabaseNotificationsSent(): void {\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(DATABASE_NOTIFICATIONS_EVENT))\n }\n}\n\n/** Run `callback` whenever a database notification is announced. Returns the cleanup. */\nexport function onDatabaseNotificationsSent(callback: () => void): () => void {\n if (typeof window === 'undefined') {\n return () => {}\n }\n\n window.addEventListener(DATABASE_NOTIFICATIONS_EVENT, callback)\n\n return () => window.removeEventListener(DATABASE_NOTIFICATIONS_EVENT, callback)\n}\n\n/**\n * Offset of the panel's toaster: toasts at the top start below the sticky top bar (3.5rem\n * high) instead of covering the breadcrumbs, search and switchers, on phones too (where they\n * span the screen). The other sides keep sonner's defaults.\n */\nexport const PANEL_TOAST_OFFSET: { top: string } = { top: '4.5rem' }\n\nexport type SonnerPosition =\n 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'\n\n/**\n * The direction overlays anchored to a screen edge (the toaster, the schema inspector's\n * sheet) are placed by: the panel's reading direction, except in starter kit mode, where\n * the host app owns `<html dir>` and its layout (the kit's sidebar stays on the left in a\n * right-to-left panel language). There they take the document's side, so \"top end\" never\n * lands on the kit's sidebar, while their content keeps the panel's direction (W-D-03).\n */\nexport function overlayPlacementDirection(\n direction: 'ltr' | 'rtl',\n starterKit: boolean | undefined,\n): 'ltr' | 'rtl' {\n if (starterKit !== true) return direction\n if (typeof document === 'undefined') return 'ltr'\n\n return document.documentElement.dir.toLowerCase() === 'rtl' ? 'rtl' : 'ltr'\n}\n\n/**\n * The toaster position for the panel's `toastPosition()` (logical: `start`/`end`) in the\n * current reading direction. Defaults to the top end.\n */\nexport function resolveToastPosition(\n position: ToastPosition | null | undefined,\n direction: 'ltr' | 'rtl',\n): SonnerPosition {\n const [vertical, horizontal] = (position ?? 'top-end').split('-') as [\n 'top' | 'bottom',\n 'start' | 'center' | 'end',\n ]\n\n if (horizontal === 'center') {\n return `${vertical}-center`\n }\n\n const isLeft = (horizontal === 'start') === (direction === 'ltr')\n\n return `${vertical}-${isLeft ? 'left' : 'right'}`\n}\n"],"mappings":";;AAKA,MAAa,yBAAyB;;AAGtC,MAAa,8BAA8B;;AAG3C,MAAa,+BAA+B;;;;;AAkC5C,SAAgB,UAA2B;CACzC,IAAI,OAAO,WAAW,aACpB,OAAO;CAGT,MAAM,OAAQ,OAAyC;CAEvD,IAAI,QAAQ,OAAQ,KAAkB,YAAY,YAChD,OAAO;CAGT,OAAO;AACT;AAuBA,IAAI,iBAAmE;AACvE,IAAI,cAAkC;AACtC,IAAI,cAA+B;AACnC,IAAI,cAA+C;AAInD,IAAI,cAA4B,cAC9B;;;CAAoD;;;;;;AAMtD,SAAgB,kBACd,QACA,UAAwE,CAAC,GACnE;CACN,iBAAiB,SAAS;EAAE,GAAG;EAAQ,WAAW,QAAQ;CAAU,IAAI;CAExE,IAAI,QAAQ,SAAS,KAAA,GAAW;EAC9B,IAAI,OAAO,QAAQ,SAAS,YAC1B,cAAc,QAAQ;OACjB;GACL,cAAc,QAAQ;GACtB,cAAc;EAChB;CACF;AACF;;AAGA,SAAgB,mBAA0C;CACxD,OAAO;AACT;;AAGA,SAAgB,oBAA6B;CAC3C,OAAO,QAAQ,gBAAgB,IAAI;AACrC;;;;;;AAOA,SAAgB,cAAwC;CACtD,MAAM,WAAW,QAAQ;CAEzB,IAAI,YAAY,OAAO,WAAW,aAChC,OAAO,QAAQ,QAAQ,QAAQ;CAGjC,gBAAgB,WAAW,CAAC,CAAC,MAAM,SAAS;EAC1C,cAAc,QAAQ;EAGtB,IAAI,CAAC,aAAa,cAAc;EAEhC,OAAO;CACT,CAAC;CAED,OAAO;AACT;AAEA,eAAe,aAAuC;CACpD,MAAM,SAAS;CACf,MAAM,UAAiC;EACrC,GAAI,QAAQ,QAAQ,CAAC;EACrB,GAAI,QAAQ,eAAe,EAAE,cAAc,OAAO,aAAa,IAAI,CAAC;EACpE,GAAI,QAAQ,YAAY,EAAE,WAAW,OAAO,UAAU,IAAI,CAAC;CAC7D;CAEA,IAAI;EACF,IAAI,aACF,OAAQ,MAAM,YAAY,OAAO,KAAM;EAGzC,IAAI,CAAC,QAAQ,QAAQ,CAAC,OAAO,MAC3B,OAAO;EAGT,MAAM,CAAC,YAAY,gBAAgB,MAAM,QAAQ,IAAI,CACnD,WAAW,cAAc,GACzB,WAAW,WAAW,CACxB,CAAC;EACD,MAAM,OAAQ,WAAW,WAAW,WAAW;EAE/C,MAAM,SAAS,aAAa,WAAW,aAAa;EAEpD,IAAI,OAAO,SAAS,YAClB,OAAO;EAGR,OAA4C,WAAW;EAExD,OAAO,IAAI,KAAK;GAAE,aAAa;GAAU,GAAG;GAAS;EAAO,CAAC;CAC/D,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,SAAgB,kBAAsC;CACpD,IAAI;EACF,OAAO,QAAQ,CAAC,EAAE,WAAW,KAAK,KAAA;CACpC,QAAQ;EACN;CACF;AACF;;AAGA,SAAgB,kBAA0C;CACxD,MAAM,KAAK,gBAAgB;CAE3B,OAAO,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;AACvC;;AAGA,SAAgB,cAAc,UAAqC,CAAC,GAAS;CAC3E,iBAAiB;CACjB,cAAc;CACd,cAAc;CACd,cAAc;CACd,YAAY,MAAM;CAElB,IAAI,QAAQ,QAAQ,aAAa,QAAQ;AAC3C;AAIA,MAAM,8BAAc,IAAI,IAAoB;;;;;;AAO5C,SAAgB,eACd,MACA,MACA,MAIO;CACP,MAAM,UAAU,SAAS,aAAa,KAAK,OAAO,IAAI,IAAI,KAAK,QAAQ,IAAI;CAE3E,IAAI,CAAC,SAAS,OAAO;CAErB,MAAM,MAAM;CACZ,YAAY,IAAI,MAAM,YAAY,IAAI,GAAG,KAAK,KAAK,CAAC;CACpD,IAAI,WAAW;CAEf,OAAO;EACI;EACT,eAAe;GACb,IAAI,UAAU;GACd,WAAW;GAEX,MAAM,SAAS,YAAY,IAAI,GAAG,KAAK,KAAK;GAE5C,IAAI,SAAS,GAAG;IACd,YAAY,OAAO,GAAG;IACtB,KAAK,QAAQ,IAAI;GACnB,OACE,YAAY,IAAI,KAAK,KAAK;EAE9B;CACF;AACF;;;;;;AAOA,SAAgB,SACd,SACA,UAAiF,CAAC,GACtE;CACZ,IAAI;CACJ,IAAI,SAAS;CAEb,MAAM,SAAS,SAA0B;EACvC,IAAI,CAAC,QAAQ;EAEb,UAAU,OAAO,QAAQ,IAAI,IAAI,QAAQ,gBAAgB;CAC3D;CAEA,IAAI,QAAQ,SAAS,KAAA,GACnB,MAAM,QAAQ,IAAI;MACb;EACL,MAAM,OAAO,QAAQ;EAErB,IAAI,QAAQ,OAAO,WAAW,aAC5B,MAAM,IAAI;OAEV,YAAiB,CAAC,CAAC,KAAK,KAAK;CAEjC;CAEA,aAAa;EACX,SAAS;EACT,UAAU;CACZ;AACF;;AAKA,MAAa,uBAAuB;AAcpC,SAAS,eAAe,SAAuD;CAC7E,MAAM,QAAQ,QAAQ;CACtB,MAAM,MAAM,QAAQ;CAEpB,IACG,UAAU,aAAa,UAAU,aAAa,UAAU,aAAa,UAAU,cAC/E,OAAO,QAAQ,YAAY,OAAO,QAAQ,UAE3C,OAAO;CAGT,OAAO;EACL;EACA;EACA,OAAO,OAAO,QAAQ,UAAU,WAAW,QAAQ,QAAQ;EAC3D,IAAI,OAAO,QAAQ,OAAO,YAAY,OAAO,QAAQ,OAAO,WAAW,QAAQ,KAAK;EACpF,IAAI,OAAO,QAAQ,OAAO,WAAW,QAAQ,KAAK,KAAK,IAAI;CAC7D;AACF;;;;;AAMA,SAAgB,uBACd,aACA,UACA,UAAqE,CAAC,GAC1D;CACZ,OAAO,UACJ,SAAS;EACR,MAAM,WAAW,eAAe,MAAM,QAAQ,QAAQ,WAAW,WAAW;EAE5E,IAAI,CAAC,UAAU;EAEf,MAAM,YAA2B,YAAY;GAC3C,MAAM,SAAS,eAAe,OAAO;GAErC,IAAI,QAAQ,SAAS,MAAM;EAC7B;EAEA,SAAS,QAAQ,SAAS,sBAAsB,QAAQ;EAExD,aAAa;GACX,SAAS,QAAQ,gBAAgB,sBAAsB,QAAQ;GAC/D,SAAS,QAAQ;EACnB;CACF,GACA,EAAE,MAAM,QAAQ,KAAK,CACvB;AACF;;;;;;AAoBA,SAAgB,yBAAyB,SAAkD;CACzF,IAAI,CAAC,QAAQ,SACX,aAAa,CAAC;CAGhB,MAAM,uBAAO,IAAI,IAAY;CAE7B,OAAO,UACJ,SAAS;EACR,IAAI,SAAS;EAEb,MAAM,YAAkC,YAAY;GAClD,IAAI,CAAC,QAAQ;GAEb,IAAI,QAAQ,SAAA,uCAAsC;IAChD,QAAQ,8BAA8B;IAEtC;GACF;GAEA,IAAI,QAAQ,WAAW,WAAW;GAElC,MAAM,KAAK,OAAO,QAAQ,OAAO,WAAW,QAAQ,KAAK;GAEzD,IAAI,OAAO,MAAM,KAAK,IAAI,EAAE,GAAG;GAC/B,IAAI,OAAO,IAAI,KAAK,IAAI,EAAE;GAE1B,MAAM,EAAE,MAAM,OAAO,GAAG,iBAAiB;GAEzC,QAAQ,eAAe,YAA6C;EACtE;EAEA,MAAM,UAAU,KAAK,QAAQ,QAAQ,OAAO;EAC5C,QAAQ,aAAa,QAAQ;EAE7B,aAAa;GACX,SAAS;GACT,QAAQ,+BAA+B,QAAQ;EACjD;CACF,GACA,EAAE,MAAM,QAAQ,KAAK,CACvB;AACF;;AAGA,SAAgB,oCAA0C;CACxD,IAAI,OAAO,WAAW,aACpB,OAAO,cAAc,IAAI,YAAY,4BAA4B,CAAC;AAEtE;;AAGA,SAAgB,4BAA4B,UAAkC;CAC5E,IAAI,OAAO,WAAW,aACpB,aAAa,CAAC;CAGhB,OAAO,iBAAiB,8BAA8B,QAAQ;CAE9D,aAAa,OAAO,oBAAoB,8BAA8B,QAAQ;AAChF;;;;;;AAOA,MAAa,qBAAsC,EAAE,KAAK,SAAS;;;;;;;;AAYnE,SAAgB,0BACd,WACA,YACe;CACf,IAAI,eAAe,MAAM,OAAO;CAChC,IAAI,OAAO,aAAa,aAAa,OAAO;CAE5C,OAAO,SAAS,gBAAgB,IAAI,YAAY,MAAM,QAAQ,QAAQ;AACxE;;;;;AAMA,SAAgB,qBACd,UACA,WACgB;CAChB,MAAM,CAAC,UAAU,eAAe,YAAY,UAAA,CAAW,MAAM,GAAG;CAKhE,IAAI,eAAe,UACjB,OAAO,GAAG,SAAS;CAKrB,OAAO,GAAG,SAAS,GAFH,eAAe,aAAc,cAAc,SAE5B,SAAS;AAC1C"}
1
+ {"version":3,"file":"realtime.js","names":[],"sources":["../src/realtime.ts"],"sourcesContent":["import type { NotificationSchema } from './schema/notifications'\nimport type { ToastPosition } from './schema/panel'\nimport type { RealtimeSchema } from './schema/realtime'\n\n/** Broadcast type of a toast pushed with `Notification::broadcast()`. */\nexport const BROADCAST_NOTIFICATION = 'alfakit.notification'\n\n/** Broadcast type sent by `sendToDatabase(..., isEventDispatched: true)`. */\nexport const DATABASE_NOTIFICATIONS_SENT = 'alfakit.database-notifications.sent'\n\n/** Window event the notifications menu listens to, to reload itself. */\nexport const DATABASE_NOTIFICATIONS_EVENT = 'alfakit:database-notifications-sent'\n\ntype NotificationCallback = (payload: Record<string, unknown>) => void\ntype EventCallback = (payload: Record<string, unknown>) => void\n\n/** The part of a Laravel Echo private channel Alfakit uses. */\nexport interface EchoChannelLike {\n notification(callback: NotificationCallback): unknown\n stopListeningForNotification?(callback: NotificationCallback): unknown\n listen?(event: string, callback: EventCallback): unknown\n stopListening?(event: string, callback?: EventCallback): unknown\n}\n\n/** The part of a Laravel Echo presence channel Alfakit uses (`Echo.join()`). */\nexport interface EchoPresenceChannelLike extends EchoChannelLike {\n here(callback: (members: unknown[]) => void): unknown\n joining(callback: (member: unknown) => void): unknown\n leaving(callback: (member: unknown) => void): unknown\n whisper?(event: string, data: Record<string, unknown>): unknown\n listenForWhisper?(event: string, callback: (data: Record<string, unknown>) => void): unknown\n}\n\n/** The part of Laravel Echo Alfakit uses (`window.Echo`). */\nexport interface EchoLike {\n private(channel: string): EchoChannelLike\n join?(channel: string): EchoPresenceChannelLike\n leave?(channel: string): void\n socketId?(): string | undefined\n}\n\n/**\n * The app's Laravel Echo instance, when one is set up as `window.Echo` (or was created by\n * `resolveEcho()`). Always null on the server, so SSR never touches it.\n */\nexport function getEcho(): EchoLike | null {\n if (typeof window === 'undefined') {\n return null\n }\n\n const echo = (window as unknown as { Echo?: unknown }).Echo\n\n if (echo && typeof (echo as EchoLike).private === 'function') {\n return echo as EchoLike\n }\n\n return createdEcho\n}\n\n// ---------------------------------------------------------------- Connection\n\n/** The settings `configureRealtime()` takes (`PanelSchema.realtime`). */\nexport type RealtimeConfig = RealtimeSchema\n\n/** Options for creating Echo: the panel's Echo options plus its auth endpoint and CSRF token. */\nexport type EchoConnectionOptions = Record<string, unknown> & {\n authEndpoint?: string\n csrfToken?: string\n}\n\n/**\n * Creates the Echo instance when the app has no `window.Echo` (`createAlfakit({ echo })`),\n * e.g. `(options) => new Echo({ ...options, broadcaster: 'reverb', Pusher })`.\n */\nexport type EchoFactory = (\n options: EchoConnectionOptions,\n) => EchoLike | null | undefined | Promise<EchoLike | null | undefined>\n\ntype ModuleLoader = (specifier: string) => Promise<Record<string, unknown>>\n\nlet realtimeConfig: (RealtimeConfig & { csrfToken?: string }) | null = null\nlet echoFactory: EchoFactory | null = null\nlet createdEcho: EchoLike | null = null\nlet pendingEcho: Promise<EchoLike | null> | null = null\n\n// The packages are optional peers the app installs: the specifier is a variable so bundlers\n// don't require them, and a missing package just means \"no Echo\".\nlet loadModule: ModuleLoader = (specifier) =>\n import(/* @vite-ignore */ /* webpackIgnore: true */ specifier) as Promise<Record<string, unknown>>\n\n/**\n * Set the panel's realtime settings (renderers call this with `panel.realtime` and the\n * CSRF token) and, optionally, an Echo instance or factory from `createAlfakit({ echo })`.\n */\nexport function configureRealtime(\n config: RealtimeConfig | null | undefined,\n options: { echo?: EchoLike | EchoFactory | null; csrfToken?: string } = {},\n): void {\n realtimeConfig = config ? { ...config, csrfToken: options.csrfToken } : null\n\n if (options.echo !== undefined) {\n if (typeof options.echo === 'function') {\n echoFactory = options.echo\n } else {\n createdEcho = options.echo\n echoFactory = null\n }\n }\n}\n\n/** The settings given to `configureRealtime()`. */\nexport function realtimeSettings(): RealtimeConfig | null {\n return realtimeConfig\n}\n\n/** Whether `Panel::realtime()` is on for this page. */\nexport function isRealtimeEnabled(): boolean {\n return Boolean(realtimeConfig?.live)\n}\n\n/**\n * The Echo instance to use: `window.Echo`, one made by the `createAlfakit({ echo })`\n * factory, or (with `config('alfakit.broadcasting.echo')`) one made from `laravel-echo`\n * and `pusher-js` if the app installed them. Null when none is available or on the server.\n */\nexport function resolveEcho(): Promise<EchoLike | null> {\n const existing = getEcho()\n\n if (existing || typeof window === 'undefined') {\n return Promise.resolve(existing)\n }\n\n pendingEcho ??= createEcho().then((echo) => {\n createdEcho = echo ?? null\n\n // Try again on the next call when nothing could be created.\n if (!createdEcho) pendingEcho = null\n\n return createdEcho\n })\n\n return pendingEcho\n}\n\nasync function createEcho(): Promise<EchoLike | null> {\n const config = realtimeConfig\n const options: EchoConnectionOptions = {\n ...(config?.echo ?? {}),\n ...(config?.authEndpoint ? { authEndpoint: config.authEndpoint } : {}),\n ...(config?.csrfToken ? { csrfToken: config.csrfToken } : {}),\n }\n\n try {\n if (echoFactory) {\n return (await echoFactory(options)) ?? null\n }\n\n if (!config?.live || !config.echo) {\n return null\n }\n\n const [echoModule, pusherModule] = await Promise.all([\n loadModule('laravel-echo'),\n loadModule('pusher-js'),\n ])\n const Echo = (echoModule.default ?? echoModule.Echo) as\n (new (options: Record<string, unknown>) => EchoLike) | undefined\n const Pusher = pusherModule.default ?? pusherModule.Pusher\n\n if (typeof Echo !== 'function') {\n return null\n }\n\n ;(window as unknown as { Pusher?: unknown }).Pusher ??= Pusher\n\n return new Echo({ broadcaster: 'reverb', ...options, Pusher })\n } catch {\n return null\n }\n}\n\n/** The socket id of the current Echo connection, sent as `X-Socket-ID` so Laravel's `toOthers()` skips this tab. */\nexport function currentSocketId(): string | undefined {\n try {\n return getEcho()?.socketId?.() || undefined\n } catch {\n return undefined\n }\n}\n\n/** `X-Socket-ID` when connected (merged into the requests of the renderers). */\nexport function realtimeHeaders(): Record<string, string> {\n const id = currentSocketId()\n\n return id ? { 'X-Socket-ID': id } : {}\n}\n\n/** Forget the connection and settings (tests). */\nexport function resetRealtime(options: { loader?: ModuleLoader } = {}): void {\n realtimeConfig = null\n echoFactory = null\n createdEcho = null\n pendingEcho = null\n channelRefs.clear()\n\n if (options.loader) loadModule = options.loader\n}\n\n// ---------------------------------------------------------------- Shared channels\n\nconst channelRefs = new Map<string, number>()\n\n/**\n * Use a channel of `echo` until the returned release function is called. Echo shares a\n * channel object per name, so it is left only when its last user releases it (a record's\n * presence avatars and conflict banner share its presence channel).\n */\nexport function acquireChannel<T extends 'private' | 'presence'>(\n echo: EchoLike,\n kind: T,\n name: string,\n): {\n channel: T extends 'presence' ? EchoPresenceChannelLike : EchoChannelLike\n release: () => void\n} | null {\n const channel = kind === 'presence' ? echo.join?.(name) : echo.private(name)\n\n if (!channel) return null\n\n const key = name\n channelRefs.set(key, (channelRefs.get(key) ?? 0) + 1)\n let released = false\n\n return {\n channel: channel as T extends 'presence' ? EchoPresenceChannelLike : EchoChannelLike,\n release: () => {\n if (released) return\n released = true\n\n const count = (channelRefs.get(key) ?? 1) - 1\n\n if (count <= 0) {\n channelRefs.delete(key)\n echo.leave?.(name)\n } else {\n channelRefs.set(key, count)\n }\n },\n }\n}\n\n/**\n * Run `connect` with Echo as soon as it is available: right away with `window.Echo`, else\n * once `resolveEcho()` settles. `onUnavailable` runs when there is no Echo. Returns the\n * cleanup, which also cancels a pending connection.\n */\nexport function withEcho(\n connect: (echo: EchoLike) => (() => void) | void,\n options: { echo?: EchoLike | null; onUnavailable?: () => (() => void) | void } = {},\n): () => void {\n let cleanup: (() => void) | void\n let active = true\n\n const start = (echo: EchoLike | null) => {\n if (!active) return\n\n cleanup = echo ? connect(echo) : options.onUnavailable?.()\n }\n\n if (options.echo !== undefined) {\n start(options.echo)\n } else {\n const echo = getEcho()\n\n if (echo || typeof window === 'undefined') {\n start(echo)\n } else {\n void resolveEcho().then(start)\n }\n }\n\n return () => {\n active = false\n cleanup?.()\n }\n}\n\n// ---------------------------------------------------------------- Record changes\n\n/** Echo event of `RecordChanged` (`broadcastAs()` is `alfakit.record`). */\nexport const RECORD_CHANGED_EVENT = '.alfakit.record'\n\n/** A record was created, updated, deleted or restored. Keys only: refetch to see the data. */\nexport interface RecordChange {\n event: 'created' | 'updated' | 'deleted' | 'restored'\n /** The model class with `-` for `\\\\`. */\n model: string\n key: string | number\n /** The user who changed it, when known. */\n by?: string | number | null\n /** Milliseconds since the epoch. */\n at?: number\n}\n\nfunction asRecordChange(payload: Record<string, unknown>): RecordChange | null {\n const event = payload.event\n const key = payload.key\n\n if (\n (event !== 'created' && event !== 'updated' && event !== 'deleted' && event !== 'restored') ||\n (typeof key !== 'string' && typeof key !== 'number')\n ) {\n return null\n }\n\n return {\n event,\n key,\n model: typeof payload.model === 'string' ? payload.model : '',\n by: typeof payload.by === 'string' || typeof payload.by === 'number' ? payload.by : null,\n at: typeof payload.at === 'number' ? payload.at : Date.now(),\n }\n}\n\n/**\n * Listen for record changes on a channel (`private` for models, `presence` for a record).\n * Returns the cleanup (a no-op without Echo).\n */\nexport function listenForRecordChanges(\n channelName: string,\n callback: (change: RecordChange) => void,\n options: { kind?: 'private' | 'presence'; echo?: EchoLike | null } = {},\n): () => void {\n return withEcho(\n (echo) => {\n const acquired = acquireChannel(echo, options.kind ?? 'private', channelName)\n\n if (!acquired) return\n\n const listener: EventCallback = (payload) => {\n const change = asRecordChange(payload)\n\n if (change) callback(change)\n }\n\n acquired.channel.listen?.(RECORD_CHANGED_EVENT, listener)\n\n return () => {\n acquired.channel.stopListening?.(RECORD_CHANGED_EVENT, listener)\n acquired.release()\n }\n },\n { echo: options.echo },\n )\n}\n\n// ---------------------------------------------------------------- Notifications\n\nexport interface RealtimeSubscriptionOptions {\n /** The user's private channel (`PanelSchema.realtime.channel`). */\n channel: string\n /** Defaults to `window.Echo`, or the Echo `resolveEcho()` creates. */\n echo?: EchoLike | null\n /** A broadcast toast arrived. */\n onNotification: (notification: NotificationSchema) => void\n /** A database notification was stored: reload the bell. */\n onDatabaseNotificationsSent?: () => void\n}\n\n/**\n * Listen for the user's broadcast notifications. Returns the unsubscribe function (a no-op\n * when Echo is missing). Each toast is delivered once, even if Echo reconnects or the\n * subscription is made twice (React strict mode).\n */\nexport function subscribeToNotifications(options: RealtimeSubscriptionOptions): () => void {\n if (!options.channel) {\n return () => {}\n }\n\n const seen = new Set<string>()\n\n return withEcho(\n (echo) => {\n let active = true\n\n const callback: NotificationCallback = (payload) => {\n if (!active) return\n\n if (payload.type === DATABASE_NOTIFICATIONS_SENT) {\n options.onDatabaseNotificationsSent?.()\n\n return\n }\n\n if (payload.format !== 'alfakit') return\n\n const id = typeof payload.id === 'string' ? payload.id : ''\n\n if (id !== '' && seen.has(id)) return\n if (id !== '') seen.add(id)\n\n const { type: _type, ...notification } = payload\n\n options.onNotification(notification as unknown as NotificationSchema)\n }\n\n const channel = echo.private(options.channel)\n channel.notification(callback)\n\n return () => {\n active = false\n channel.stopListeningForNotification?.(callback)\n }\n },\n { echo: options.echo },\n )\n}\n\n/** Tell mounted notification menus to reload (`DATABASE_NOTIFICATIONS_EVENT`). */\nexport function announceDatabaseNotificationsSent(): void {\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(DATABASE_NOTIFICATIONS_EVENT))\n }\n}\n\n/** Run `callback` whenever a database notification is announced. Returns the cleanup. */\nexport function onDatabaseNotificationsSent(callback: () => void): () => void {\n if (typeof window === 'undefined') {\n return () => {}\n }\n\n window.addEventListener(DATABASE_NOTIFICATIONS_EVENT, callback)\n\n return () => window.removeEventListener(DATABASE_NOTIFICATIONS_EVENT, callback)\n}\n\n/**\n * Offset of the panel's toaster: toasts at the top start below the sticky top bar (3.5rem\n * high) instead of covering the breadcrumbs, search and switchers, on phones too (where they\n * span the screen). The other sides keep sonner's defaults.\n */\nexport const PANEL_TOAST_OFFSET: { top: string } = { top: '4.5rem' }\n\nexport type SonnerPosition =\n 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'\n\n/**\n * The direction overlays anchored to a screen edge (the toaster, the schema inspector's\n * sheet) are placed by: the panel's reading direction, except in starter kit mode, where\n * they take the document's side (`<html dir>`, from the shared `alfakitDocument`; an app\n * whose root view or sidebar was not updated may still be left to right), so \"top end\"\n * never lands on the kit's sidebar, while their content keeps the panel's direction (W-D-03).\n */\nexport function overlayPlacementDirection(\n direction: 'ltr' | 'rtl',\n starterKit: boolean | undefined,\n): 'ltr' | 'rtl' {\n if (starterKit !== true) return direction\n if (typeof document === 'undefined') return 'ltr'\n\n return document.documentElement.dir.toLowerCase() === 'rtl' ? 'rtl' : 'ltr'\n}\n\n/**\n * The toaster position for the panel's `toastPosition()` (logical: `start`/`end`) in the\n * current reading direction. Defaults to the top end.\n */\nexport function resolveToastPosition(\n position: ToastPosition | null | undefined,\n direction: 'ltr' | 'rtl',\n): SonnerPosition {\n const [vertical, horizontal] = (position ?? 'top-end').split('-') as [\n 'top' | 'bottom',\n 'start' | 'center' | 'end',\n ]\n\n if (horizontal === 'center') {\n return `${vertical}-center`\n }\n\n const isLeft = (horizontal === 'start') === (direction === 'ltr')\n\n return `${vertical}-${isLeft ? 'left' : 'right'}`\n}\n"],"mappings":"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"}
@@ -18,10 +18,19 @@ export interface CmsLinkSchema {
18
18
  newTab: boolean;
19
19
  }
20
20
  /** One rendered block: its type, a stable id (an anchor and a key) and its data. */
21
+ /** The **Style** section of a block; only options that differ from the default are sent. */
22
+ export interface CmsBlockStyle {
23
+ background?: 'muted' | 'primary' | 'secondary' | 'dark';
24
+ spacing?: 'none' | 'small' | 'large';
25
+ visibility?: 'desktop' | 'mobile';
26
+ /** The block's `id`, for in-page links (`#pricing`). */
27
+ anchor?: string;
28
+ }
21
29
  export interface CmsBlockSchema<TData = Record<string, unknown>> {
22
30
  type: string;
23
31
  id: string;
24
32
  data: TData;
33
+ style?: CmsBlockStyle;
25
34
  }
26
35
  export interface CmsHeroBlockData {
27
36
  eyebrow: string | null;
@@ -197,6 +206,20 @@ export interface CmsSiteSchema {
197
206
  url: string;
198
207
  current: boolean;
199
208
  }[];
209
+ /** **Site design** (`CmsPlugin::siteDesign()`); null when nothing is set. */
210
+ design?: CmsSiteDesign | null;
211
+ }
212
+ /** The site's colors, fonts, logo and favicon, built and checked on the server. */
213
+ export interface CmsSiteDesign {
214
+ /** Theme variables and custom CSS, for a `<style>` element (never contains `<`). */
215
+ css: string | null;
216
+ /** The fonts' stylesheet URL. */
217
+ fonts: string | null;
218
+ logo: {
219
+ url: string;
220
+ alt: string;
221
+ } | null;
222
+ favicon: string | null;
200
223
  }
201
224
  /** The `cms` prop of the `Alfakit/CmsPage` Inertia page. */
202
225
  export interface CmsPayload {
@@ -1 +1 @@
1
- {"version":3,"file":"cms.d.ts","names":[],"sources":["../../src/schema/cms.ts"],"mappings":";;;;;;;;iBASiB;EACf;EACA;EACA;;;iBAIe;EACf;EACA;EACA;;;iBAIe,eAAe,QAAQ;EACtC;EACA;EACA,MAAM;;iBAGS;EACf;EACA;EACA;EACA,eAAe;EACf,iBAAiB;EACjB,OAAO;EACP;;iBAGe;;EAEf;EACA;;iBAGe;EACf,OAAO;EACP;EACA;;iBAGe;EACf;EACA;EACA,QAAQ;;iBAGO;EACf;EACA;EACA,eAAe;EACf,iBAAiB;EACjB;;iBAGe;;EAEf;EACA;EACA;EACA;;iBAGe;EACf;EACA;EACA;EACA,OAAO;;iBAGQ;EACf;EACA;IAAS;IAAkB;;;iBAGZ;EACf;EACA;EACA;EACA,QAAQ;;iBAGO;EACf;EACA,OAAO;;YAGG;iBAEK;EACf;IACE;;IAEA;IACA;;EAEF;EACA,aAAa;;iBAGE;EACf;EACA;IAAW,QAAQ;;;iBAGJ;EACf;EACA;;EAEA;;EAEA,SAAS;;iBAGM;;EAEf;;EAEA,QAAQ;;;iBAIO;EACf,MAAM;EACN,aAAa;EACb,OAAO;EACP,SAAS;EACT,kBAAkB;EAClB,UAAU;EACV,KAAK;EACL,cAAc;EACd,OAAO;EACP,SAAS;EACT,MAAM;EACN,SAAS;;iBAGM;EACf;EACA;EACA;EACA;EACA,WAAW;EACX,SAAS;EACT;IAAc;IAAkB;;;EAEhC;;iBAGe;EACf;EACA;;iBAGe;EACf;EACA;EACA;;iBAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAQ;EACR,aAAa;EACb,YAAY;EACZ,KAAK;;iBAGU;EACf;EACA;;EAEA;EACA;;EAEA;;EAEA;EACA,UAAU;;iBAGK;EACf;EACA;EACA,OAAO;;iBAGQ;EACf;EACA;EACA;EACA;EACA;EACA;IAAW;IAAc;IAAe;IAAa;;;;iBAItC;EACf,MAAM;EACN,OAAO,eAAe;EACtB,MAAM;;EAEN;;EAEA,QAAQ;;EAER;;EAEA,cAAc;;iBAGC;EACf;;EAEA;EACA;EACA;;;;;;iBAOe;EACf;EACA;EACA;EACA;;EAEA;EACA;EACA;;EAEA,SAAS;;EAET,gBAAgB;;EAEhB,UAAU;;EAEV,iBAAiB;;;iBAIF;EACf;;EAEA;EACA;;;iBAIe;EACf;EACA;EACA;;EAEA;;iBAGe;EACf,KAAK"}
1
+ {"version":3,"file":"cms.d.ts","names":[],"sources":["../../src/schema/cms.ts"],"mappings":";;;;;;;;iBASiB;EACf;EACA;EACA;;;iBAIe;EACf;EACA;EACA;;;;iBAKe;EACf;EACA;EACA;;EAEA;;iBAGe,eAAe,QAAQ;EACtC;EACA;EACA,MAAM;EACN,QAAQ;;iBAGO;EACf;EACA;EACA;EACA,eAAe;EACf,iBAAiB;EACjB,OAAO;EACP;;iBAGe;;EAEf;EACA;;iBAGe;EACf,OAAO;EACP;EACA;;iBAGe;EACf;EACA;EACA,QAAQ;;iBAGO;EACf;EACA;EACA,eAAe;EACf,iBAAiB;EACjB;;iBAGe;;EAEf;EACA;EACA;EACA;;iBAGe;EACf;EACA;EACA;EACA,OAAO;;iBAGQ;EACf;EACA;IAAS;IAAkB;;;iBAGZ;EACf;EACA;EACA;EACA,QAAQ;;iBAGO;EACf;EACA,OAAO;;YAGG;iBAEK;EACf;IACE;;IAEA;IACA;;EAEF;EACA,aAAa;;iBAGE;EACf;EACA;IAAW,QAAQ;;;iBAGJ;EACf;EACA;;EAEA;;EAEA,SAAS;;iBAGM;;EAEf;;EAEA,QAAQ;;;iBAIO;EACf,MAAM;EACN,aAAa;EACb,OAAO;EACP,SAAS;EACT,kBAAkB;EAClB,UAAU;EACV,KAAK;EACL,cAAc;EACd,OAAO;EACP,SAAS;EACT,MAAM;EACN,SAAS;;iBAGM;EACf;EACA;EACA;EACA;EACA,WAAW;EACX,SAAS;EACT;IAAc;IAAkB;;;EAEhC;;iBAGe;EACf;EACA;;iBAGe;EACf;EACA;EACA;;iBAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAQ;EACR,aAAa;EACb,YAAY;EACZ,KAAK;;iBAGU;EACf;EACA;;EAEA;EACA;;EAEA;;EAEA;EACA,UAAU;;iBAGK;EACf;EACA;EACA,OAAO;;iBAGQ;EACf;EACA;EACA;EACA;EACA;EACA;IAAW;IAAc;IAAe;IAAa;;;EAErD,SAAS;;;iBAIM;;EAEf;;EAEA;EACA;IAAQ;IAAa;;EACrB;;;iBAIe;EACf,MAAM;EACN,OAAO,eAAe;EACtB,MAAM;;EAEN;;EAEA,QAAQ;;EAER;;EAEA,cAAc;;iBAGC;EACf;;EAEA;EACA;EACA;;;;;;iBAOe;EACf;EACA;EACA;EACA;;EAEA;EACA;EACA;;EAEA,SAAS;;EAET,gBAAgB;;EAEhB,UAAU;;EAEV,iBAAiB;;;iBAIF;EACf;;EAEA;EACA;;;iBAIe;EACf;EACA;EACA;;EAEA;;iBAGe;EACf,KAAK"}
@@ -334,6 +334,8 @@ export interface FileUploadSchema extends BaseField {
334
334
  image?: boolean;
335
335
  avatar?: boolean;
336
336
  acceptedFileTypes?: string[];
337
+ /** Short names of the accepted types for the drop zone hint (`PNG, JPG, ICO`, `Images`). */
338
+ acceptedFileTypesLabel?: string;
337
339
  maxSize?: number;
338
340
  minSize?: number;
339
341
  /** Encrypted limits sent with each upload so the endpoint applies the field's `maxSize()`. */
@@ -1 +1 @@
1
- {"version":3,"file":"components.d.ts","names":[],"sources":["../../src/schema/components.ts"],"mappings":";;;;;;;iBAyBiB;EACf;EACA;EACA;EACA;EACA;EACA,UAAU;EACV,aAAa,YAAY;EACzB,cAAc;;EAEd,cAAc;EACd,kBAAkB;EAClB,OAAO;;EAEP;;EAEA,SAAS;;EAET;;EAEA;;EAEA,UAAU;;;;;EAKV;;EAEA;;;iBAIe;EACf,YAAY;EACZ;;EAEA,SAAS;;EAET,OAAO;;EAEP;;iBAGe;EACf;EACA;;;;;;;iBAQe;EACf,gBAAgB;EAChB,gBAAgB;EAChB,cAAc;;;;;;YAOJ;;;;;YAgBA;EACR,WAAW;EAAiB;;EAAqB,QAAQ;EAAiB;;iBAE7D;EACf,OAAO;;EAEP;;iBAGe;EACf,QAAQ,QAAQ,OAAO,iBAAiB;;;iBAIzB,kBAAkB,eAAe,cAAc;EAC9D;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA,WAAW;EACX,YAAY;;EAEZ;EACA,OAAO;EACP,uBAAuB;;EAEvB,8BAA8B;;EAE9B;;;;;EAKA,eAAe;;;iBAIA;EACf;;EAEA;;EAEA;EACA;;EAEA;;iBAGe;EACf,SAAS;EACT;;iBAGe;EACf;EACA;EACA,aAAa;EACb,aAAa;EACb,kBAAkB;EAClB,kBAAkB;;iBAKH,wBAAwB,WAAW;EAClD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA;EACA;;EAEA;;EAEA;EACA;;EAEA;;iBAGe,uBAAuB;EACtC;EACA;EACA;EACA;EACA;EACA;;iBAGe,qBAAqB;EACpC;;;iBAIe,+BAA+B;EAC9C;;EAEA;;EAEA;;iBAGe,uBAAuB;EACtC;EACA;;iBAGe,qBAAqB;EACpC;EACA;EACA,SAAS;EACT,UAAU;EACV,UAAU;EACV,WAAW;;iBAGI,qBAAqB,WAAW;EAC/C;EACA,SAAS;;EAET,iBAAiB;EACjB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;;EAEA;;EAEA;;EAEA;EACA;EACA;;EAEA;;iBAGe,2BAA2B;EAC1C;EACA,SAAS;EACT;;EAEA;;EAEA;;EAEA;EACA;EACA;EACA;;EAEA,gBAAgB;;EAEhB;;EAEA,kBAAkB;EAClB,oBAAoB;;YAGV;iBAEK;EACf;EACA,OAAO;EACP;;iBAGe,oBAAoB;EACnC;EACA,SAAS;EACT;;iBAGe,yBAAyB,WAAW;EACnD;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA;EACA;;EAEA;;EAEA;;EAEA;;iBAGe,yBAAyB,WAAW;EACnD;;EAEA;;EAEA;EACA;EACA;;iBAGe,0BAA0B;EACzC;EACA;;iBAGe,wBAAwB;EACvC;EACA;EACA;EACA,QAAQ;EACR;EACA;EACA;;iBAGe,uBAAuB;EACtC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA,cAAc,QAAQ,qCAAqC;;iBAG5C;EACf;EACA;EACA;;EAEA;;EAEA;;EAEA;EACA;EACA;;;iBAIe;EACf;EACA;EACA;;iBAGe,yBAAyB;EACxC;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA;;EAEA;;EAEA;;EAEA,cAAc;EACd,YAAY;;EAEZ;EACA;EACA;EACA;EACA;;EAEA,cAAc;;EAEd;;EAEA;IACE;IACA;IACA;IACA;;;EAGF;;EAEA;EACA,OAAO;;iBAGQ;;EAEf,cAAc;;EAEd;;EAEA;;EAEA;;EAEA;EACA;;;iBAIe;EACf;EACA;EACA,OAAO;;EAEP;;;;;EAKA,QAAQ;;;iBAIO;;EAEf;;EAEA,QAAQ;IAAQ;IAAY;;;EAE5B;EACA;;;iBAIe;EACf;EACA;;;iBAIe;EACf;;EAEA,OAAO;EACP;;EAEA;;;iBAIe;EACf;EACA;EACA,OAAO;;EAEP,WAAW;;;;;EAKX,SAAS;;EAET;IAAW;IAAc,aAAa;;;iBAGvB,yBAAyB;EACxC;;EAEA;EACA,sBAAsB,eAAe;;;;;EAKrC,mBAAmB;EACnB;EACA;;EAEA;;EAEA;EACA;EACA;EACA;;EAEA;;EAEA,cAAc;;EAEd;EACA;IACE;IACA;;IAEA;;IAEA;;IAEA;;;EAGF,aAAa;IAAQ;IAAe;;;EAEpC;EACA;EACA;;;;;EAKA;;;;;;EAMA,gBAAgB;;EAEhB;EACA,eAAe;EACf,WAAW;EACX,YAAY;;;iBAIG;EACf;;EAEA;EACA;EACA;;EAEA;EACA;EACA;;;YAIU;;;;;;iBAiBK,yBAAyB;EACxC;;;YAIU,oBAAoB,QAAQ,OAAO,gBAAgB;;iBAG9C;;EAEf,cAAc;;;;;EAKd,mBAAmB;;EAEnB;;EAEA;EACA,qBAAqB;EACrB;;;iBAIe;EACf,cAAc;EACd,UAAU;;iBAGK,2BAA2B;EAC1C;EACA;EACA,QAAQ;;iBAGO,uBAAuB,WAAW;EACjD;EACA,OAAO;;EAEP,UAAU;EACV,eAAe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OAAO;;EAEP,QAAQ;;EAER;;EAEA,aAAa;;YAGH,cACR,kBACA,iBACA,eACA,yBACA,iBACA,eACA,eACA,qBACA,cACA,mBACA,mBACA,oBACA,kBACA,iBACA,mBACA,mBACA,iBACA,gBACA,sBACA,eACA,mBACA,yBACA,mBACA;qBAES;wBA+BG,QAAQ,WAAW,kBAAkB,aAAa;iBAUjD,mBAAmB;EAClC;;iBAGe,oBAAoB;EACnC;;;;;;iBAOe,yBAAyB;EACxC;;iBAGe,sBAAsB;EACrC;EACA;;EAEA;EACA;EACA,OAAO;EACP;EACA;;EAEA;EACA;EACA;EACA;EACA,gBAAgB;;EAEhB,cAAc;EACd,gBAAgB;;EAEhB,yBAAyB;;EAEzB,SAAS;;iBAGM,uBAAuB;EACtC;;EAEA;;iBAGe,kBAAkB;EACjC;;;;;EAKA;EACA,OAAO;EACP,eAAe;EACf;EACA,aAAa;;EAEb;;EAEA;;;iBAIe;EACf;EACA,aAAa;EACb;;iBAGe,mBAAmB;EAClC;EACA,QAAQ;EACR;EACA;EACA;EACA;;EAEA;;EAEA;IAAe;IAAa;;;iBAGb,mBAAmB;EAClC;EACA;EACA,OAAO;;EAEP,gBAAgB;;iBAGD,qBAAqB;EACpC;EACA,QAAQ;EACR;EACA;EACA;;EAEA,eAAe;;EAEf;EACA;EACA;;;;;EAKA,aAAa;EACb,iBAAiB;EACjB,gBAAgB;;EAEhB;;;;;EAKA;IAAa;IAAc;;;iBAGZ,0BAA0B;EACzC;EACA;EACA;;iBAGe,mBAAmB;EAClC;EACA;;iBAGe,mBAAmB;EAClC;EACA;EACA,QAAQ;EACR,OAAO;EACP;EACA;EACA;;EAEA;EACA;;EAEA;EACA;;EAEA;;;iBAIe,mBAAmB;EAClC;EACA;EACA,QAAQ;;;iBAIO,mBAAmB;EAClC;EACA;EACA;;EAEA,YAAY;;;iBAIG,sBAAsB;EACrC;EACA;EACA;EACA,OAAO;;EAEP,WAAW;EACX,QAAQ;;;;;EAKR,SAAS;;EAET,YAAY;EACZ,UAAU;;EAEV,yBAAyB;;EAEzB,WAAW;;EAEX,SAAS;;;;;;iBAOM,+BAA+B;EAC9C;EACA,SAAS;EACT,YAAY;EACZ;EACA;;;iBAIe,kCAAkC;EACjD;EACA;EACA;EACA,OAAO;EACP,QAAQ;EACR;EACA,UAAU;;EAEV,SAAS;;iBAGM,mBAAmB;EAClC;EACA,MAAM;EACN,QAAQ;EACR,OAAO;EACP;;iBAGe,oBAAoB;EACnC;EACA;EACA;;EAEA;EACA;EACA,YAAY;EACZ;EACA;;;iBAIe,4BAA4B;EAC3C;EACA,OAAO;;YAGG,eACR,aACA,gBACA,yBACA,4BACA,aACA,cACA,sBACA,aACA,cACA,mBACA,gBACA,iBACA,aACA,YACA,eACA,aACA,oBACA,aACA,aACA;iBAIa,kBAAkB,eAAe,cAAc;EAC9D;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA,WAAW;EACX,YAAY;EACZ;;EAEA;;;;;EAKA,8BAA8B;;EAE9B,YAAY;;iBAGG;EACf;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA;;iBAGe;EACf;EACA;EACA;EACA;EACA;;;;;EAKA;EACA;;;iBAIe;;EAEf;;EAEA;;iBAGe,wBAAwB,WAAW,gBAAgB;EAClE;EACA,oCAAoC;EACpC,QAAQ;EACR,OAAO;EACP,eAAe;EACf,YAAY;;EAEZ,aAAa,MAAM;;EAEnB,YAAY,MAAM;;EAElB,WAAW;EACX;;iBAGe,wBAAwB;EACvC;EACA,OAAO;EACP,QAAQ;EACR,OAAO;;iBAGQ,yBAAyB,WAAW;EACnD;;EAEA,qBAAqB;EACrB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;;EAEA;;EAEA;;EAEA,OAAO;;;iBAIQ,wBAAwB;EACvC;EACA;;EAEA;;EAEA;EACA;EACA;EACA;;EAEA;;iBAGe,yBAAyB,WAAW;EACnD;EACA;;iBAGe,4BAA4B;EAC3C;EACA,QAAQ;EACR;EACA;;iBAGe,8BAA8B;EAC7C;EACA,OAAO;EACP;EACA,OAAO;;EAEP,QAAQ;;;iBAIO,wBAAwB;EACvC;EACA;EACA,QAAQ;;YAGE,cACR,kBACA,kBACA,kBACA,mBACA,kBACA,mBACA,sBACA;YAEQ,kBACV,cAAc,eAAe,eAAe;EAAkB"}
1
+ {"version":3,"file":"components.d.ts","names":[],"sources":["../../src/schema/components.ts"],"mappings":"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"}