@alfakit/core 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/components.css +19 -0
- package/dist/advanced-fields.d.ts +6 -0
- package/dist/advanced-fields.d.ts.map +1 -1
- package/dist/advanced-fields.js +20 -1
- package/dist/advanced-fields.js.map +1 -1
- package/dist/chart.d.ts.map +1 -1
- package/dist/chart.js +23 -15
- package/dist/chart.js.map +1 -1
- package/dist/conflicts.d.ts.map +1 -1
- package/dist/conflicts.js +3 -5
- package/dist/conflicts.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/item-actions.d.ts +10 -0
- package/dist/item-actions.d.ts.map +1 -1
- package/dist/item-actions.js +8 -1
- package/dist/item-actions.js.map +1 -1
- package/dist/prefetch.d.ts.map +1 -1
- package/dist/prefetch.js +5 -2
- package/dist/prefetch.js.map +1 -1
- package/dist/presence.d.ts +0 -4
- package/dist/presence.d.ts.map +1 -1
- package/dist/presence.js +15 -2
- package/dist/presence.js.map +1 -1
- package/dist/schema/components.d.ts +5 -0
- package/dist/schema/components.d.ts.map +1 -1
- package/dist/schema/components.js.map +1 -1
- package/dist/schema/form.d.ts +2 -0
- package/dist/schema/form.d.ts.map +1 -1
- package/dist/schema/form.js.map +1 -1
- package/dist/starter-kit.d.ts.map +1 -1
- package/dist/starter-kit.js +17 -2
- package/dist/starter-kit.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @alfakit/core
|
|
2
2
|
|
|
3
|
+
## 0.1.13
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Released with AlfaKit 0.1.13 to keep the npm and PHP versions the same (no core changes).
|
|
8
|
+
|
|
9
|
+
## 0.1.12
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Starter kit sidebar items keyed by URL (language and tenant switches no longer leave links pointing at the old page or tenant); upload previews kept after a round trip; chart registries shared by a second copy of core under `npm run dev`; conflict banner finds forms inside relation-manager tabs; presence joins an already-subscribed channel; saved views and column changes count as mutations for Back; starter kit toasts reach the kit's toaster.
|
|
14
|
+
|
|
3
15
|
## 0.1.11
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/components.css
CHANGED
|
@@ -182,6 +182,13 @@
|
|
|
182
182
|
.fc[data-slot='calendar'] .fc-day-other .fc-daygrid-day-number {
|
|
183
183
|
color: var(--muted-foreground);
|
|
184
184
|
}
|
|
185
|
+
/*
|
|
186
|
+
* FullCalendar fades other months' dates to 30% opacity, which fails WCAG AA contrast
|
|
187
|
+
* (W-V4-20); the muted colour alone sets them apart.
|
|
188
|
+
*/
|
|
189
|
+
.fc[data-slot='calendar'] .fc-day-other .fc-daygrid-day-top {
|
|
190
|
+
opacity: 1;
|
|
191
|
+
}
|
|
185
192
|
/* Today: a tinted cell (`--fc-today-bg-color`) and the date in a primary pill. */
|
|
186
193
|
.fc[data-slot='calendar'] .fc-daygrid-day.fc-day-today .fc-daygrid-day-number {
|
|
187
194
|
display: inline-flex;
|
|
@@ -345,3 +352,15 @@
|
|
|
345
352
|
transition: none;
|
|
346
353
|
}
|
|
347
354
|
}
|
|
355
|
+
|
|
356
|
+
/* ------------------------------------------------------------------ Dev tools */
|
|
357
|
+
|
|
358
|
+
/*
|
|
359
|
+
* The floating debug-panel and schema-inspector buttons (local development) sit at the
|
|
360
|
+
* bottom end, over the helpdesk inbox's reply composer and its Send button: lift them
|
|
361
|
+
* above the composer while the inbox is on the page (W-S4-09).
|
|
362
|
+
*/
|
|
363
|
+
body:has([data-slot='helpdesk-inbox']) [data-slot='dev-tools-toggle'],
|
|
364
|
+
body:has([data-slot='helpdesk-inbox']) [data-slot='starter-kit-debug-panel'] {
|
|
365
|
+
bottom: 14rem;
|
|
366
|
+
}
|
|
@@ -183,6 +183,12 @@ export declare function pickerFilterFormOptions(owner: FormStore, path: string):
|
|
|
183
183
|
* either side normalises the same way.
|
|
184
184
|
*/
|
|
185
185
|
export declare function fileUploadPaths(value: unknown): string[];
|
|
186
|
+
/**
|
|
187
|
+
* The file entry of each path: the server's (`component.files`), or the one this session
|
|
188
|
+
* uploaded. A temporary upload comes back from the server without a `url` after a round trip,
|
|
189
|
+
* so the session's entry (with its local image preview) wins over it then (W-S2-02).
|
|
190
|
+
*/
|
|
191
|
+
export declare function fileUploadEntries<T extends UploadedFile>(paths: readonly string[], serverFiles: readonly UploadedFile[], uploaded: readonly T[]): Array<T | UploadedFile>;
|
|
186
192
|
/** The picker's `accept`: the accepted types plus the extensions of `mimeTypeMap()`. */
|
|
187
193
|
export declare function fileUploadAccept(schema: Pick<FileUploadSchema, 'acceptedFileTypes' | 'mimeTypeMap'>): string | undefined;
|
|
188
194
|
/** A `'2:1'` / `'16/9'` ratio as a CSS `aspect-ratio` value. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"advanced-fields.d.ts","names":[],"sources":["../src/advanced-fields.ts"],"mappings":";;;;;;;wBAkBgB,aAAa,iBAAiB;;wBAY9B,kBACd,OAAO,oBACP,OAAO,oBACP,iBACC;;wBAQa,mBAAmB,OAAO,qBAAqB;;;;;wBAY/C,oBAAoB,UAAU,KAAK,wCACjD,QAAQ,KACR,gBACC;iBAYc,kBAAkB;;EAEjC;EACA,QAAQ;;;;;;wBAOM,mBAAmB,UAAU,KAAK,8BAChD,QAAQ,MACP,kBAAkB;;wBAgBL,mBACd,WAAW,KAAK,4BAChB,OAAO,KAAK,0CACZ,OAAO;;;;;;wBAaO,kBACd,WAAW,KAAK,oCAChB,OAAO,oBACP,gBACC;;wBAca,iBACd,WAAW,KAAK,oCAChB,OAAO,oBACP;;iBAWe;EACf;EACA;;;;;;;;wBASc,kBACd,aACA,iBACA,OAAO,qBACP;;wBAuDc,kBACd,kBACA,gBACA;;wBAcc,sBAAsB,kBAAkB;;wBAiBxC,aACd,gBACA,QAAQ,KAAK;;wBAoBC,YACd,kBACA,QAAQ,KAAK;iBAKE;EACf;;EAEA;;EAEA;;EAEA;;;;;;wBAOc,WACd,QAAQ,KAAK,oEACZ;KAoCE,cAAc,KAAK;;wBAqCR,cAAc,eAAe,QAAQ;;wBAkBrC,cAAc,iBAAiB,QAAQ,aAAa;;wBAkBpD,WAAW,eAAe,QAAQ;;wBAKlC,aACd,QAAQ,KAAK;;wBAQC,mBAAmB,QAAQ,KAAK,2BAA2B;;wBAK3D,kBAAkB,QAAQ,KAAK;;;;;wBAQ/B,mBACd,kBACA,QAAQ,KAAK,6BAA6B,cACzC;EAAQ;EAAe;;;wBAcV,gBACd;EAAW;EAAe;GAC1B;EAAW;EAAmB;EAAuB;IACpD;;wBAca,eAAe,QAAQ,KAAK,4BAA4B,IAAI;;wBAK5D,gBACd,QAAQ,KAAK,sBACb;;;;;wBASc,gBACd,QAAQ,aACR;EACG;EAAa;EAAa;EAAc;;;wBAc7B,kBAAkB,kBAAkB,QAAQ;;wBAO5C,oBAAoB,kBAAkB,QAAQ,aAAa;KAMtE,cAAc,cACjB,KAAK;;;;;;;wBAQS,iBACd,oBACA,gBACA,QAAQ;;;;;wBAyDM,YAAY,kBAAkB,eAAe,QAAQ;;;;;;wBAgBrD,qBACd,OAAO,cACP;EACE,OAAO;EACP;EACA,QAAQ;EACR;EACA;;EAEA;EACA,WAAW;EACX;;;;;;wBAsDY,gBACd,QAAQ,oBACR,QAAQ,KAAK;iBAWE;EACf;EACA;EACA;;;;;;wBAOc,WACd,eACA,wBACA,sBACA,iBACA,oBACC;;wBAyCa,kBACd,eACA,wBACA,uBACC;;wBAea,gBAAgB,oBAAoB,OAAO,aAAa;;;;;wBAgBxD,oBACd,oBACA,aACA,mBACA;;;;;;;wBAmBc,wBAAwB,OAAO,WAAW,eAAe;;;;;;wBAqBzD,gBAAgB;;
|
|
1
|
+
{"version":3,"file":"advanced-fields.d.ts","names":[],"sources":["../src/advanced-fields.ts"],"mappings":";;;;;;;wBAkBgB,aAAa,iBAAiB;;wBAY9B,kBACd,OAAO,oBACP,OAAO,oBACP,iBACC;;wBAQa,mBAAmB,OAAO,qBAAqB;;;;;wBAY/C,oBAAoB,UAAU,KAAK,wCACjD,QAAQ,KACR,gBACC;iBAYc,kBAAkB;;EAEjC;EACA,QAAQ;;;;;;wBAOM,mBAAmB,UAAU,KAAK,8BAChD,QAAQ,MACP,kBAAkB;;wBAgBL,mBACd,WAAW,KAAK,4BAChB,OAAO,KAAK,0CACZ,OAAO;;;;;;wBAaO,kBACd,WAAW,KAAK,oCAChB,OAAO,oBACP,gBACC;;wBAca,iBACd,WAAW,KAAK,oCAChB,OAAO,oBACP;;iBAWe;EACf;EACA;;;;;;;;wBASc,kBACd,aACA,iBACA,OAAO,qBACP;;wBAuDc,kBACd,kBACA,gBACA;;wBAcc,sBAAsB,kBAAkB;;wBAiBxC,aACd,gBACA,QAAQ,KAAK;;wBAoBC,YACd,kBACA,QAAQ,KAAK;iBAKE;EACf;;EAEA;;EAEA;;EAEA;;;;;;wBAOc,WACd,QAAQ,KAAK,oEACZ;KAoCE,cAAc,KAAK;;wBAqCR,cAAc,eAAe,QAAQ;;wBAkBrC,cAAc,iBAAiB,QAAQ,aAAa;;wBAkBpD,WAAW,eAAe,QAAQ;;wBAKlC,aACd,QAAQ,KAAK;;wBAQC,mBAAmB,QAAQ,KAAK,2BAA2B;;wBAK3D,kBAAkB,QAAQ,KAAK;;;;;wBAQ/B,mBACd,kBACA,QAAQ,KAAK,6BAA6B,cACzC;EAAQ;EAAe;;;wBAcV,gBACd;EAAW;EAAe;GAC1B;EAAW;EAAmB;EAAuB;IACpD;;wBAca,eAAe,QAAQ,KAAK,4BAA4B,IAAI;;wBAK5D,gBACd,QAAQ,KAAK,sBACb;;;;;wBASc,gBACd,QAAQ,aACR;EACG;EAAa;EAAa;EAAc;;;wBAc7B,kBAAkB,kBAAkB,QAAQ;;wBAO5C,oBAAoB,kBAAkB,QAAQ,aAAa;KAMtE,cAAc,cACjB,KAAK;;;;;;;wBAQS,iBACd,oBACA,gBACA,QAAQ;;;;;wBAyDM,YAAY,kBAAkB,eAAe,QAAQ;;;;;;wBAgBrD,qBACd,OAAO,cACP;EACE,OAAO;EACP;EACA,QAAQ;EACR;EACA;;EAEA;EACA,WAAW;EACX;;;;;;wBAsDY,gBACd,QAAQ,oBACR,QAAQ,KAAK;iBAWE;EACf;EACA;EACA;;;;;;wBAOc,WACd,eACA,wBACA,sBACA,iBACA,oBACC;;wBAyCa,kBACd,eACA,wBACA,uBACC;;wBAea,gBAAgB,oBAAoB,OAAO,aAAa;;;;;wBAgBxD,oBACd,oBACA,aACA,mBACA;;;;;;;wBAmBc,wBAAwB,OAAO,WAAW,eAAe;;;;;;wBAqBzD,gBAAgB;;;;;;wBAahB,kBAAkB,UAAU,cAC1C,0BACA,sBAAsB,gBACtB,mBAAmB,MAClB,MAAM,IAAI;;wBAYG,iBACd,QAAQ,KAAK;;wBAYC,eAAe;;;;;wBAUf,mBACd,iBACA,aACA,QAAQ,KAAK,kCACb;;wBAQc,kBAAkB,MAAM,KAAK;EAC3C;EACA;;;;;;wBASc,mBAAmB,iBAAiB,GAAG,YAAY,QAAQ,OAAO,QAAQ;;wBAuB1E,mBAAmB,gBAAgB;iBAMlC;EACf;EACA;EACA;EACA;EACA;EACA;;;wBAIc,iBACd,YAAY,yCACX"}
|
package/dist/advanced-fields.js
CHANGED
|
@@ -475,6 +475,25 @@ function fileUploadPaths(value) {
|
|
|
475
475
|
if (Array.isArray(value)) return value.filter((path) => typeof path === "string" && path !== "");
|
|
476
476
|
return typeof value === "string" && value !== "" ? [value] : [];
|
|
477
477
|
}
|
|
478
|
+
/**
|
|
479
|
+
* The file entry of each path: the server's (`component.files`), or the one this session
|
|
480
|
+
* uploaded. A temporary upload comes back from the server without a `url` after a round trip,
|
|
481
|
+
* so the session's entry (with its local image preview) wins over it then (W-S2-02).
|
|
482
|
+
*/
|
|
483
|
+
function fileUploadEntries(paths, serverFiles, uploaded) {
|
|
484
|
+
return paths.map((path) => {
|
|
485
|
+
const server = serverFiles.find((file) => file.path === path);
|
|
486
|
+
const local = uploaded.find((file) => file.path === path);
|
|
487
|
+
if (local && !server?.url) return {
|
|
488
|
+
...server,
|
|
489
|
+
...local
|
|
490
|
+
};
|
|
491
|
+
return server ?? local ?? {
|
|
492
|
+
path,
|
|
493
|
+
name: path.replace(/^tmp:/, "")
|
|
494
|
+
};
|
|
495
|
+
});
|
|
496
|
+
}
|
|
478
497
|
/** The picker's `accept`: the accepted types plus the extensions of `mimeTypeMap()`. */
|
|
479
498
|
function fileUploadAccept(schema) {
|
|
480
499
|
if (!schema.acceptedFileTypes || schema.acceptedFileTypes.length === 0) return void 0;
|
|
@@ -541,6 +560,6 @@ function modalTablePaging(pagination) {
|
|
|
541
560
|
};
|
|
542
561
|
}
|
|
543
562
|
//#endregion
|
|
544
|
-
export { builderBlockCounts, builderItemLabel, builderItemSchema, builderItems, builderPickerMove, canAddBuilderBlock, concurrencyLimiter, cssAspectRatio, fileUploadAccept, fileUploadPaths, filterBuilderBlocks, groupBuilderBlocks, indentCode, insertBuilderItem, insertUploadedFile, modalTablePaging, modalTableQuery, modalTableSelected, newlineWithIndent, pickerFilterFormOptions, sliderBehavior, sliderBlocksTap, sliderBounds, sliderDirection, sliderFillSections, sliderFillStyle, sliderFromPrimitive, sliderHasTooltips, sliderNextValues, sliderPercent, sliderPips, sliderPrimitive, sliderShift, sliderShowsTooltip, sliderSnap, sliderState, sliderToPrimitive, sliderValueAt, sliderValues, startSliderRangeDrag, toggleButtonsNext, toggleButtonsSelected, toggleModalTableKey, uploadedFileLinks };
|
|
563
|
+
export { builderBlockCounts, builderItemLabel, builderItemSchema, builderItems, builderPickerMove, canAddBuilderBlock, concurrencyLimiter, cssAspectRatio, fileUploadAccept, fileUploadEntries, fileUploadPaths, filterBuilderBlocks, groupBuilderBlocks, indentCode, insertBuilderItem, insertUploadedFile, modalTablePaging, modalTableQuery, modalTableSelected, newlineWithIndent, pickerFilterFormOptions, sliderBehavior, sliderBlocksTap, sliderBounds, sliderDirection, sliderFillSections, sliderFillStyle, sliderFromPrimitive, sliderHasTooltips, sliderNextValues, sliderPercent, sliderPips, sliderPrimitive, sliderShift, sliderShowsTooltip, sliderSnap, sliderState, sliderToPrimitive, sliderValueAt, sliderValues, startSliderRangeDrag, toggleButtonsNext, toggleButtonsSelected, toggleModalTableKey, uploadedFileLinks };
|
|
545
564
|
|
|
546
565
|
//# sourceMappingURL=advanced-fields.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"advanced-fields.js","names":[],"sources":["../src/advanced-fields.ts"],"sourcesContent":["import type { FormStore, FormStoreOptions } from './form'\nimport type {\n BuilderBlockSchema,\n BuilderItemState,\n BuilderSchema,\n SliderBehavior,\n SliderSchema,\n} from './schema/advanced-fields'\nimport type { FileUploadSchema, SchemaComponent, UploadedFile } from './schema/components'\nimport type { AnyPaginationSchema } from './schema/table'\nimport type { TableQuery } from './table'\nimport { tableQueryKey } from './table'\nimport { cloneState } from './utils/clone'\nimport { templateForIndex } from './utils/path'\n\n// ---------------------------------------------------------------- Builder\n\n/** The Builder items in a state value, keeping only well-formed `{type, data}` items. */\nexport function builderItems(value: unknown): BuilderItemState[] {\n if (!Array.isArray(value)) return []\n\n return value.filter(\n (item): item is BuilderItemState =>\n typeof item === 'object' &&\n item !== null &&\n typeof (item as BuilderItemState).type === 'string',\n )\n}\n\n/** Insert a fresh item of `block` at `index` (the end by default). */\nexport function insertBuilderItem(\n items: BuilderItemState[],\n block: BuilderBlockSchema,\n index: number = items.length,\n): BuilderItemState[] {\n const copy = [...items]\n copy.splice(Math.max(0, Math.min(index, items.length)), 0, cloneState(block.state))\n\n return copy\n}\n\n/** How many items of each block type the builder holds. */\nexport function builderBlockCounts(items: BuilderItemState[]): Record<string, number> {\n const counts: Record<string, number> = {}\n\n for (const item of items) counts[item.type] = (counts[item.type] ?? 0) + 1\n\n return counts\n}\n\n/**\n * Blocks whose label (or group heading) contains `query` (case-insensitive), for\n * `Builder::searchable()`.\n */\nexport function filterBuilderBlocks<T extends Pick<BuilderBlockSchema, 'label' | 'group'>>(\n blocks: T[],\n query: string,\n): T[] {\n const needle = query.trim().toLocaleLowerCase()\n\n return needle === ''\n ? blocks\n : blocks.filter(\n (block) =>\n block.label.toLocaleLowerCase().includes(needle) ||\n (block.group ?? '').toLocaleLowerCase().includes(needle),\n )\n}\n\nexport interface BuilderBlockGroup<T> {\n /** The heading; null for ungrouped blocks (listed first, without a heading). */\n group: string | null\n blocks: T[]\n}\n\n/**\n * Picker blocks under their headings (`Builder::blocks(['Group' => [...]])`): ungrouped\n * blocks first, then each group in the order it first appears. Empty groups are dropped.\n */\nexport function groupBuilderBlocks<T extends Pick<BuilderBlockSchema, 'group'>>(\n blocks: T[],\n): BuilderBlockGroup<T>[] {\n const groups = new Map<string | null, T[]>([[null, []]])\n\n for (const block of blocks) {\n const group = block.group ?? null\n\n if (!groups.has(group)) groups.set(group, [])\n groups.get(group)!.push(block)\n }\n\n return [...groups.entries()]\n .filter(([, items]) => items.length > 0)\n .map(([group, items]) => ({ group, blocks: items }))\n}\n\n/** Whether another item of `block` may be added (its own `maxItems` and the builder's). */\nexport function canAddBuilderBlock(\n component: Pick<BuilderSchema, 'maxItems'>,\n block: Pick<BuilderBlockSchema, 'name' | 'maxItems'>,\n items: BuilderItemState[],\n): boolean {\n // `null` (sent by PHP for \"no limit\") and `undefined` both mean unlimited.\n if (component.maxItems != null && items.length >= component.maxItems) return false\n\n return block.maxItems == null || (builderBlockCounts(items)[block.name] ?? 0) < block.maxItems\n}\n\n/**\n * The schema to render for the item at `index`: the server's per-item schema when it\n * describes the same block, else the block template with the index filled in (items the\n * browser just added, cloned or moved before the server has seen them).\n */\nexport function builderItemSchema(\n component: Pick<BuilderSchema, 'items' | 'blocks'>,\n items: BuilderItemState[],\n index: number,\n): SchemaComponent[] {\n const type = items[index]?.type\n const server = component.items[index]\n\n if (server && server.type === type) {\n return server.schema\n }\n\n const block = component.blocks.find((candidate) => candidate.name === type)\n\n return block ? templateForIndex(block.schema, index) : []\n}\n\n/** Label of the item at `index`: the server's when it describes the same block. */\nexport function builderItemLabel(\n component: Pick<BuilderSchema, 'items' | 'blocks'>,\n items: BuilderItemState[],\n index: number,\n): string | null {\n const type = items[index]?.type\n const server = component.items[index]\n\n if (server && server.type === type && server.label) return server.label\n\n return component.blocks.find((block) => block.name === type)?.label ?? type ?? null\n}\n\n/** Where an option of the block picker sits (`getBoundingClientRect()` is enough). */\nexport interface BuilderPickerRect {\n top: number\n left: number\n}\n\n/**\n * Keyboard movement in the block picker grid: the index of the option to focus after\n * `key` on option `current`, or null when the key moves nowhere. Left/right follow the\n * reading direction (`rtl`), up/down go to the closest option of the row above/below\n * (rows may be ragged: groups start new rows), Home/End jump to the first/last option.\n */\nexport function builderPickerMove(\n key: string,\n current: number,\n rects: BuilderPickerRect[],\n rtl = false,\n): number | null {\n const count = rects.length\n\n if (count === 0) return null\n if (key === 'Home') return 0\n if (key === 'End') return count - 1\n\n const forward = rtl ? 'ArrowLeft' : 'ArrowRight'\n const backward = rtl ? 'ArrowRight' : 'ArrowLeft'\n\n if (key === forward) return current < count - 1 ? current + 1 : null\n if (key === backward) return current > 0 ? current - 1 : null\n if (key !== 'ArrowDown' && key !== 'ArrowUp') return null\n\n const here = rects[current]\n\n if (!here) return null\n\n const below = key === 'ArrowDown'\n let rowTop: number | null = null\n\n // The nearest row in that direction (options on one row share their top, ±2px).\n rects.forEach((rect) => {\n const ahead = below ? rect.top > here.top + 2 : rect.top < here.top - 2\n\n if (ahead && (rowTop === null || (below ? rect.top < rowTop : rect.top > rowTop))) {\n rowTop = rect.top\n }\n })\n\n if (rowTop === null) return null\n\n let best: number | null = null\n\n rects.forEach((rect, index) => {\n if (Math.abs(rect.top - (rowTop as number)) > 2) return\n\n const candidate = rects[best ?? -1]\n\n if (\n best === null ||\n !candidate ||\n Math.abs(rect.left - here.left) < Math.abs(candidate.left - here.left)\n ) {\n best = index\n }\n })\n\n return best\n}\n\n// ---------------------------------------------------------------- ToggleButtons\n\n/** The next state after a toggle button for `option` was pressed. */\nexport function toggleButtonsNext(\n current: unknown,\n option: string,\n multiple: boolean,\n): string | string[] | null {\n if (multiple) {\n const values = Array.isArray(current) ? current.map(String) : []\n\n return values.includes(option)\n ? values.filter((value) => value !== option)\n : [...values, option]\n }\n\n return toggleButtonsSelected(current, false).includes(option) ? null : option\n}\n\n/** The option values a toggle buttons state selects. */\nexport function toggleButtonsSelected(current: unknown, multiple: boolean): string[] {\n if (multiple) return Array.isArray(current) ? current.map(String) : []\n if (current === null || current === undefined || current === '') return []\n if (typeof current === 'boolean') return [current ? '1' : '0']\n\n return [String(current)]\n}\n\n// ---------------------------------------------------------------- Slider\n\nfunction roundTo(value: number, step: number, min: number): number {\n const decimals = (String(step).split('.')[1] ?? '').length\n\n return Number((Math.round((value - min) / step) * step + min).toFixed(decimals))\n}\n\n/** The slider's thumb values: always numbers within bounds, sorted for ranges. */\nexport function sliderValues(\n value: unknown,\n schema: Pick<SliderSchema, 'min' | 'max' | 'multiple'>,\n): number[] {\n const clamp = (input: unknown, fallback: number): number => {\n const number = typeof input === 'number' ? input : Number(input)\n\n return Number.isFinite(number) && input !== null && input !== ''\n ? Math.min(schema.max, Math.max(schema.min, number))\n : fallback\n }\n\n if (schema.multiple) {\n const list = Array.isArray(value) && value.length > 0 ? value : [schema.min, schema.max]\n\n return list.map((item) => clamp(item, schema.min)).sort((a, b) => a - b)\n }\n\n return [clamp(value, schema.min)]\n}\n\n/** The state to store for the given thumb values. */\nexport function sliderState(\n values: number[],\n schema: Pick<SliderSchema, 'multiple'>,\n): number | number[] | null {\n return schema.multiple ? [...values].sort((a, b) => a - b) : (values[0] ?? null)\n}\n\nexport interface SliderPip {\n value: number\n /** Position from the start of the track, 0–100. */\n percent: number\n /** Whether the pip shows a label (at most 11 are labelled by default). */\n labelled: boolean\n /** A small label (`pipsFilter()` returned 2, or `PipsMode::Steps` between range points). */\n small?: boolean\n}\n\n/**\n * Tick marks along the track: the server's `pipList` (modes, `steppedPips()`,\n * `pipsFilter()`), or every `pipsDensity` units / every step for schemas without one.\n */\nexport function sliderPips(\n schema: Pick<SliderSchema, 'min' | 'max' | 'step' | 'pipsDensity' | 'pipList'>,\n): SliderPip[] {\n if (schema.pipList) {\n return schema.pipList.map((pip) => ({\n value: pip.value,\n percent: pip.percent,\n labelled: pip.type !== 0,\n ...(pip.type === 2 ? { small: true } : {}),\n }))\n }\n\n const range = schema.max - schema.min\n\n if (!(range > 0)) return []\n\n let interval = schema.pipsDensity && schema.pipsDensity > 0 ? schema.pipsDensity : schema.step\n\n // Never draw more than 100 ticks.\n while (range / interval > 100) interval *= 2\n\n const values: number[] = []\n\n for (let value = schema.min; value < schema.max + interval / 1e6; value += interval) {\n values.push(roundTo(Math.min(value, schema.max), schema.step, schema.min))\n }\n\n if (values[values.length - 1] !== schema.max) values.push(schema.max)\n\n const labelEvery = Math.max(1, Math.ceil((values.length - 1) / 10))\n\n return values.map((value, index) => ({\n value,\n percent: ((value - schema.min) / range) * 100,\n labelled: index % labelEvery === 0 || index === values.length - 1,\n }))\n}\n\ntype SliderTrack = Pick<SliderSchema, 'min' | 'max' | 'step' | 'nonLinearPoints'>\n\ninterface SliderSection {\n fromPercent: number\n toPercent: number\n fromValue: number\n toValue: number\n step: number\n}\n\n/** The sections of the track between its ends and `nonLinearPoints`. */\nfunction sliderSections(schema: SliderTrack): SliderSection[] {\n const points = [\n { percent: 0, value: schema.min, step: schema.step },\n ...(schema.nonLinearPoints ?? []),\n { percent: 100, value: schema.max },\n ]\n const sections: SliderSection[] = []\n let step = schema.step\n\n for (let index = 0; index < points.length - 1; index++) {\n const from = points[index]!\n const to = points[index + 1]!\n step = from.step ?? step\n sections.push({\n fromPercent: from.percent,\n toPercent: to.percent,\n fromValue: from.value,\n toValue: to.value,\n step,\n })\n }\n\n return sections\n}\n\n/** Where a value sits on the track, 0–100 (non-linear tracks included). */\nexport function sliderPercent(value: number, schema: SliderTrack): number {\n for (const section of sliderSections(schema)) {\n if (value <= section.toValue || section.toPercent >= 100) {\n if (section.toValue === section.fromValue) return section.fromPercent\n\n const ratio = Math.min(\n 1,\n Math.max(0, (value - section.fromValue) / (section.toValue - section.fromValue)),\n )\n\n return section.fromPercent + ratio * (section.toPercent - section.fromPercent)\n }\n }\n\n return 100\n}\n\n/** The value at a position on the track (0–100), snapped to its section's step by default. */\nexport function sliderValueAt(percent: number, schema: SliderTrack, snap = true): number {\n for (const section of sliderSections(schema)) {\n if (percent <= section.toPercent || section.toPercent >= 100) {\n const span = section.toPercent - section.fromPercent\n const ratio =\n span === 0 ? 0 : Math.min(1, Math.max(0, (percent - section.fromPercent) / span))\n const value = section.fromValue + ratio * (section.toValue - section.fromValue)\n\n return snap && section.step > 0\n ? Math.min(section.toValue, roundTo(value, section.step, section.fromValue))\n : value\n }\n }\n\n return schema.max\n}\n\n/** Round a value to the step of its section of the track. */\nexport function sliderSnap(value: number, schema: SliderTrack): number {\n return sliderValueAt(sliderPercent(value, schema), schema)\n}\n\n/** The lowest and highest value a thumb can take (the track minus `rangePadding()`). */\nexport function sliderBounds(\n schema: Pick<SliderSchema, 'min' | 'max' | 'rangePadding'>,\n): [number, number] {\n const [start, end] = schema.rangePadding ?? [0, 0]\n\n return [schema.min + start, schema.max - end]\n}\n\n/** Whether thumb `index` shows its value (`tooltips()`, or `tooltips([true, false])`). */\nexport function sliderShowsTooltip(schema: Pick<SliderSchema, 'tooltips'>, index: number): boolean {\n return Array.isArray(schema.tooltips) ? schema.tooltips[index] === true : Boolean(schema.tooltips)\n}\n\n/** Whether any thumb shows its value (the slider leaves room above the track). */\nexport function sliderHasTooltips(schema: Pick<SliderSchema, 'tooltips'>): boolean {\n return Array.isArray(schema.tooltips) ? schema.tooltips.includes(true) : Boolean(schema.tooltips)\n}\n\n/**\n * Filled sections of the track for `fillTrack([false, true, false])`, as positions (0–100)\n * from the minimum. Null when `fillTrack` is not an array: the primitive's range is used.\n */\nexport function sliderFillSections(\n values: number[],\n schema: Pick<SliderSchema, 'fillTrack'> & SliderTrack,\n): Array<{ start: number; end: number }> | null {\n if (!Array.isArray(schema.fillTrack)) return null\n\n const stops = [0, ...values.map((value) => sliderPercent(value, schema)), 100]\n\n return schema.fillTrack.flatMap((filled, index) => {\n const start = stops[index]\n const end = stops[index + 1]\n\n return filled && start !== undefined && end !== undefined && end > start ? [{ start, end }] : []\n })\n}\n\n/** Inline position of a fill section: from the minimum's edge of the track. */\nexport function sliderFillStyle(\n section: { start: number; end: number },\n options: { vertical: boolean; topToBottom?: boolean; direction: 'ltr' | 'rtl' },\n): Record<string, string> {\n const size = `${section.end - section.start}%`\n const offset = `${section.start}%`\n\n if (options.vertical) {\n return options.topToBottom ? { top: offset, height: size } : { bottom: offset, height: size }\n }\n\n return options.direction === 'rtl'\n ? { right: offset, width: size }\n : { left: offset, width: size }\n}\n\n/** The pointer behaviours of a slider (`behavior()`); `tap` by default. */\nexport function sliderBehavior(schema: Pick<SliderSchema, 'behavior'>): Set<SliderBehavior> {\n return new Set(schema.behavior ?? ['tap'])\n}\n\n/** The slider's direction: `rtl()` when set, else the panel's. */\nexport function sliderDirection(\n schema: Pick<SliderSchema, 'rtl'>,\n panel: 'ltr' | 'rtl',\n): 'ltr' | 'rtl' {\n return schema.rtl === undefined || schema.rtl === null ? panel : schema.rtl ? 'rtl' : 'ltr'\n}\n\n/**\n * What the slider primitive works with: values directly, or positions (0–100) on a\n * non-linear track. `smooth` (`Behavior::SmoothSteps` while dragging) drops the step.\n */\nexport function sliderPrimitive(\n schema: SliderTrack,\n smooth = false,\n): { min: number; max: number; step: number; nonLinear: boolean } {\n if (schema.nonLinearPoints && schema.nonLinearPoints.length > 0) {\n return { min: 0, max: 100, step: smooth ? 0.01 : 0.1, nonLinear: true }\n }\n\n return {\n min: schema.min,\n max: schema.max,\n step: smooth ? (schema.max - schema.min) / 1000 || schema.step : schema.step,\n nonLinear: false,\n }\n}\n\n/** Thumb values as the primitive's values. */\nexport function sliderToPrimitive(values: number[], schema: SliderTrack): number[] {\n return sliderPrimitive(schema).nonLinear\n ? values.map((value) => sliderPercent(value, schema))\n : values\n}\n\n/** The primitive's values as thumb values (snapped unless dragging smoothly). */\nexport function sliderFromPrimitive(values: number[], schema: SliderTrack, snap = true): number[] {\n return sliderPrimitive(schema).nonLinear\n ? values.map((position) => sliderValueAt(position, schema, snap))\n : values\n}\n\ntype SliderRules = SliderTrack &\n Pick<SliderSchema, 'rangePadding' | 'behavior' | 'minDifference' | 'multiple'>\n\n/**\n * The thumb values to store after the primitive moved from `previous` to `next` (both\n * sorted): kept inside `rangePadding()`; with `fixed` every thumb moves by the same\n * amount; without `unconstrained` a thumb stops at its neighbours (`minDifference()` apart)\n * instead of passing them.\n */\nexport function sliderNextValues(\n previous: number[],\n next: number[],\n schema: SliderRules,\n): number[] {\n const [low, high] = sliderBounds(schema)\n const behavior = sliderBehavior(schema)\n const clamp = (value: number) => Math.min(high, Math.max(low, value))\n\n if (next.length !== previous.length || previous.length < 2) {\n return next.map(clamp)\n }\n\n const moved = sliderMovedThumb(previous, next)\n\n if (!moved) return next.map(clamp)\n\n const [index, value] = moved\n\n if (behavior.has('fixed')) {\n return sliderShift(previous, value - previous[index]!, schema)\n }\n\n if (behavior.has('unconstrained')) {\n return [...next].map(clamp).sort((a, b) => a - b)\n }\n\n const gap = schema.minDifference ?? 0\n const lower = index > 0 ? previous[index - 1]! + gap : low\n const upper = index < previous.length - 1 ? previous[index + 1]! - gap : high\n const result = [...previous]\n result[index] = Math.min(Math.min(high, upper), Math.max(Math.max(low, lower), value))\n\n return result\n}\n\n/**\n * Which thumb of `previous` moved, and where to, when exactly one value changed (the\n * primitive may have re-sorted the thumbs after one passed another).\n */\nfunction sliderMovedThumb(previous: number[], next: number[]): [number, number] | null {\n const remaining = [...next]\n const removed: number[] = []\n\n for (const [index, value] of previous.entries()) {\n const found = remaining.indexOf(value)\n\n if (found === -1) removed.push(index)\n else remaining.splice(found, 1)\n }\n\n if (removed.length !== 1 || remaining.length !== 1) return null\n\n return [removed[0]!, remaining[0]!]\n}\n\n/**\n * Move every thumb by `delta` (in values), keeping their distances and stopping at the\n * ends of the track (minus `rangePadding()`).\n */\nexport function sliderShift(values: number[], delta: number, schema: SliderRules): number[] {\n if (values.length === 0) return values\n\n const [low, high] = sliderBounds(schema)\n const first = Math.min(...values)\n const last = Math.max(...values)\n const allowed = Math.min(high - last, Math.max(low - first, delta))\n\n return values.map((value) => value + allowed)\n}\n\n/**\n * `Behavior::Drag`: follow a pointer that grabbed the filled range between the thumbs and\n * move them together, until the pointer is released. Positions are measured on `track`\n * in the slider's direction (bottom to top when vertical).\n */\nexport function startSliderRangeDrag(\n event: PointerEvent,\n options: {\n track: HTMLElement\n values: number[]\n schema: SliderRules\n direction: 'ltr' | 'rtl'\n vertical: boolean\n /** `vertical(isTopToBottom: true)`: the minimum is at the top. */\n topToBottom?: boolean\n onChange: (values: number[]) => void\n onEnd?: () => void\n },\n): void {\n const { track, values, schema, direction, vertical } = options\n const rect = track.getBoundingClientRect()\n const size = vertical ? rect.height : rect.width\n\n if (!(size > 0) || values.length < 2) return\n\n event.preventDefault()\n event.stopPropagation()\n\n const start = vertical ? event.clientY : event.clientX\n const percents = values.map((value) => sliderPercent(value, schema))\n\n const move = (moveEvent: PointerEvent) => {\n const current = vertical ? moveEvent.clientY : moveEvent.clientX\n let delta = ((current - start) / size) * 100\n\n if (vertical ? !options.topToBottom : direction === 'rtl') delta = -delta\n\n const [low, high] = sliderBounds(schema).map((bound) => sliderPercent(bound, schema)) as [\n number,\n number,\n ]\n const allowed = Math.min(\n high - Math.max(...percents),\n Math.max(low - Math.min(...percents), delta),\n )\n const moved = percents.map((percent) => sliderValueAt(percent + allowed, schema))\n // Snapping each thumb separately could change their distance: keep the first one's.\n options.onChange(\n sliderPrimitive(schema).nonLinear\n ? moved\n : sliderShift(values, moved[0]! - values[0]!, schema),\n )\n }\n\n const end = () => {\n window.removeEventListener('pointermove', move)\n window.removeEventListener('pointerup', end)\n window.removeEventListener('pointercancel', end)\n options.onEnd?.()\n }\n\n window.addEventListener('pointermove', move)\n window.addEventListener('pointerup', end)\n window.addEventListener('pointercancel', end)\n}\n\n/**\n * Whether a pointer press on the slider should be swallowed before the primitive sees it:\n * a press on the track when `tap` (and `snap`) are off. Thumbs always respond.\n */\nexport function sliderBlocksTap(\n target: EventTarget | null,\n schema: Pick<SliderSchema, 'behavior'>,\n): boolean {\n const behavior = sliderBehavior(schema)\n\n if (behavior.has('tap') || behavior.has('snap')) return false\n\n return !(target instanceof Element && target.closest('[role=\"slider\"]'))\n}\n\n// ---------------------------------------------------------------- CodeEditor\n\nexport interface TextEdit {\n value: string\n selectionStart: number\n selectionEnd: number\n}\n\n/**\n * Indent (or with `outdent`, unindent) the lines touched by the selection. A collapsed\n * selection without `outdent` inserts spaces at the caret.\n */\nexport function indentCode(\n value: string,\n selectionStart: number,\n selectionEnd: number,\n tabSize: number,\n outdent = false,\n): TextEdit {\n const indent = ' '.repeat(Math.max(1, tabSize))\n\n if (!outdent && selectionStart === selectionEnd) {\n return {\n value: value.slice(0, selectionStart) + indent + value.slice(selectionEnd),\n selectionStart: selectionStart + indent.length,\n selectionEnd: selectionStart + indent.length,\n }\n }\n\n const lineStart = value.lastIndexOf('\\n', selectionStart - 1) + 1\n const block = value.slice(lineStart, selectionEnd)\n const lines = block.split('\\n')\n let firstDelta = 0\n let totalDelta = 0\n\n const next = lines.map((line, index) => {\n if (outdent) {\n const removable = line.match(new RegExp(`^ {1,${indent.length}}|^\\\\t`))?.[0].length ?? 0\n\n if (index === 0) firstDelta = -removable\n totalDelta -= removable\n\n return line.slice(removable)\n }\n\n if (index === 0) firstDelta = indent.length\n totalDelta += indent.length\n\n return indent + line\n })\n\n return {\n value: value.slice(0, lineStart) + next.join('\\n') + value.slice(selectionEnd),\n selectionStart: Math.max(lineStart, selectionStart + firstDelta),\n selectionEnd: Math.max(lineStart, selectionEnd + totalDelta),\n }\n}\n\n/** Keep the indentation of the current line when Enter is pressed. */\nexport function newlineWithIndent(\n value: string,\n selectionStart: number,\n selectionEnd: number,\n): TextEdit {\n const lineStart = value.lastIndexOf('\\n', selectionStart - 1) + 1\n const indent = value.slice(lineStart, selectionStart).match(/^[ \\t]*/)?.[0] ?? ''\n const insert = `\\n${indent}`\n\n return {\n value: value.slice(0, selectionStart) + insert + value.slice(selectionEnd),\n selectionStart: selectionStart + insert.length,\n selectionEnd: selectionStart + insert.length,\n }\n}\n\n// ---------------------------------------------------------------- ModalTableSelect\n\n/** Query parameters for the picker table, prefixed with the table state identifier. */\nexport function modalTableQuery(identifier: string, query: TableQuery): Record<string, unknown> {\n const params: Record<string, unknown> = {}\n\n for (const [name, value] of Object.entries(query) as [keyof TableQuery, unknown][]) {\n if (value === null || value === undefined || value === '') continue\n\n params[tableQueryKey(identifier, name)] = value\n }\n\n return params\n}\n\n/**\n * The selection after the record `key` was toggled in the picker. Single selects keep\n * one key; multiple selects stop at `maxItems`.\n */\nexport function toggleModalTableKey(\n selected: string[],\n key: string,\n multiple: boolean,\n maxItems?: number,\n): string[] {\n if (selected.includes(key)) {\n return selected.filter((candidate) => candidate !== key)\n }\n\n if (!multiple) return [key]\n\n if (maxItems != null && selected.length >= maxItems) return selected\n\n return [...selected, key]\n}\n\n/**\n * The store options of a picker's filters form. Searchable selects among the filters load\n * their options through the owning form's endpoint, addressed by the ModalTableSelect's\n * `path` (`pickerFilters`), so the server resolves the picker table (and its tenant-scoped\n * relationships) the same way it does for the table itself.\n */\nexport function pickerFilterFormOptions(owner: FormStore, path: string): FormStoreOptions {\n return {\n token: owner.token,\n component: owner.component,\n record: owner.record,\n optionsUrl: owner.optionsUrl,\n params: () => ({\n ...owner.requestContext(),\n pickerFilters: path,\n ownerState: owner.state.values,\n }),\n }\n}\n\n// ---------------------------------------------------------------- FileUpload\n\n/**\n * The stored or temporary paths in a FileUpload state value. The server always holds a\n * list (a single field's is at most one long) and accepts one path too, so a value from\n * either side normalises the same way.\n */\nexport function fileUploadPaths(value: unknown): string[] {\n if (Array.isArray(value)) {\n return value.filter((path): path is string => typeof path === 'string' && path !== '')\n }\n\n return typeof value === 'string' && value !== '' ? [value] : []\n}\n\n/** The picker's `accept`: the accepted types plus the extensions of `mimeTypeMap()`. */\nexport function fileUploadAccept(\n schema: Pick<FileUploadSchema, 'acceptedFileTypes' | 'mimeTypeMap'>,\n): string | undefined {\n if (!schema.acceptedFileTypes || schema.acceptedFileTypes.length === 0) return undefined\n\n const mapped = Object.entries(schema.mimeTypeMap ?? {})\n .filter(([, type]) => schema.acceptedFileTypes!.includes(type))\n .map(([extension]) => `.${extension}`)\n\n return [...new Set([...schema.acceptedFileTypes, ...mapped])].join(',')\n}\n\n/** A `'2:1'` / `'16/9'` ratio as a CSS `aspect-ratio` value. */\nexport function cssAspectRatio(ratio: string | null | undefined): string | undefined {\n const [width, height] = (ratio ?? '').split(/[:/]/).map((part) => Number(part.trim()))\n\n return width && height && width > 0 && height > 0 ? `${width} / ${height}` : undefined\n}\n\n/**\n * The field's paths after an upload finished: the new key after the existing files, or\n * before them with `appendFiles(false)`, keeping at most `maxFiles`.\n */\nexport function insertUploadedFile(\n paths: string[],\n key: string,\n schema: Pick<FileUploadSchema, 'appendFiles'>,\n maxFiles: number,\n): string[] {\n return schema.appendFiles === false\n ? [key, ...paths].slice(0, maxFiles)\n : [...paths, key].slice(-maxFiles)\n}\n\n/** Where a file's \"open\" and \"download\" buttons link to. */\nexport function uploadedFileLinks(file: Pick<UploadedFile, 'url' | 'openUrl' | 'downloadUrl'>): {\n open?: string\n download?: string\n} {\n return { open: file.openUrl ?? file.url, download: file.downloadUrl ?? file.url }\n}\n\n/**\n * A queue that runs at most `limit` tasks at once, in the order they were added\n * (`maxParallelUploads()`).\n */\nexport function concurrencyLimiter(limit: number): <T>(task: () => Promise<T>) => Promise<T> {\n const max = Math.max(1, Math.floor(limit) || 1)\n const waiting: Array<() => void> = []\n let running = 0\n\n const release = () => {\n running--\n waiting.shift()?.()\n }\n\n return <T>(task: () => Promise<T>): Promise<T> =>\n new Promise<T>((resolve, reject) => {\n const start = () => {\n running++\n task().then(resolve, reject).finally(release)\n }\n\n if (running < max) start()\n else waiting.push(start)\n })\n}\n\n/** The selected record keys in a ModalTableSelect state value. */\nexport function modalTableSelected(value: unknown, multiple: boolean): string[] {\n if (multiple) return Array.isArray(value) ? value.map(String) : []\n\n return value === null || value === undefined || value === '' ? [] : [String(value)]\n}\n\nexport interface ModalTablePaging {\n page: number\n hasPrevious: boolean\n hasNext: boolean\n from: number\n to: number\n total?: number\n}\n\n/** Page-by-page navigation for the picker table, whatever the table's pagination mode. */\nexport function modalTablePaging(\n pagination: AnyPaginationSchema | null | undefined,\n): ModalTablePaging | null {\n if (!pagination) return null\n\n const page = 'currentPage' in pagination ? pagination.currentPage : 1\n const hasNext =\n 'lastPage' in pagination\n ? page < pagination.lastPage\n : 'currentPage' in pagination && 'hasMorePages' in pagination && pagination.hasMorePages\n\n return {\n page,\n hasPrevious: 'currentPage' in pagination && page > 1,\n hasNext: Boolean(hasNext),\n from: pagination.from,\n to: pagination.to,\n total: 'total' in pagination ? pagination.total : undefined,\n }\n}\n"],"mappings":";;;;;AAkBA,SAAgB,aAAa,OAAoC;CAC/D,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC;CAEnC,OAAO,MAAM,QACV,SACC,OAAO,SAAS,YAChB,SAAS,QACT,OAAQ,KAA0B,SAAS,QAC/C;AACF;;AAGA,SAAgB,kBACd,OACA,OACA,QAAgB,MAAM,QACF;CACpB,MAAM,OAAO,CAAC,GAAG,KAAK;CACtB,KAAK,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,OAAO,MAAM,MAAM,CAAC,GAAG,GAAG,WAAW,MAAM,KAAK,CAAC;CAElF,OAAO;AACT;;AAGA,SAAgB,mBAAmB,OAAmD;CACpF,MAAM,SAAiC,CAAC;CAExC,KAAK,MAAM,QAAQ,OAAO,OAAO,KAAK,SAAS,OAAO,KAAK,SAAS,KAAK;CAEzE,OAAO;AACT;;;;;AAMA,SAAgB,oBACd,QACA,OACK;CACL,MAAM,SAAS,MAAM,KAAK,CAAC,CAAC,kBAAkB;CAE9C,OAAO,WAAW,KACd,SACA,OAAO,QACJ,UACC,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,MAAM,MAC9C,MAAM,SAAS,GAAA,CAAI,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAC3D;AACN;;;;;AAYA,SAAgB,mBACd,QACwB;CACxB,MAAM,yBAAS,IAAI,IAAwB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;CAEvD,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,QAAQ,MAAM,SAAS;EAE7B,IAAI,CAAC,OAAO,IAAI,KAAK,GAAG,OAAO,IAAI,OAAO,CAAC,CAAC;EAC5C,OAAO,IAAI,KAAK,CAAC,CAAE,KAAK,KAAK;CAC/B;CAEA,OAAO,CAAC,GAAG,OAAO,QAAQ,CAAC,CAAC,CACzB,QAAQ,GAAG,WAAW,MAAM,SAAS,CAAC,CAAC,CACvC,KAAK,CAAC,OAAO,YAAY;EAAE;EAAO,QAAQ;CAAM,EAAE;AACvD;;AAGA,SAAgB,mBACd,WACA,OACA,OACS;CAET,IAAI,UAAU,YAAY,QAAQ,MAAM,UAAU,UAAU,UAAU,OAAO;CAE7E,OAAO,MAAM,YAAY,SAAS,mBAAmB,KAAK,CAAC,CAAC,MAAM,SAAS,KAAK,MAAM;AACxF;;;;;;AAOA,SAAgB,kBACd,WACA,OACA,OACmB;CACnB,MAAM,OAAO,MAAM,MAAM,EAAE;CAC3B,MAAM,SAAS,UAAU,MAAM;CAE/B,IAAI,UAAU,OAAO,SAAS,MAC5B,OAAO,OAAO;CAGhB,MAAM,QAAQ,UAAU,OAAO,MAAM,cAAc,UAAU,SAAS,IAAI;CAE1E,OAAO,QAAQ,iBAAiB,MAAM,QAAQ,KAAK,IAAI,CAAC;AAC1D;;AAGA,SAAgB,iBACd,WACA,OACA,OACe;CACf,MAAM,OAAO,MAAM,MAAM,EAAE;CAC3B,MAAM,SAAS,UAAU,MAAM;CAE/B,IAAI,UAAU,OAAO,SAAS,QAAQ,OAAO,OAAO,OAAO,OAAO;CAElE,OAAO,UAAU,OAAO,MAAM,UAAU,MAAM,SAAS,IAAI,CAAC,EAAE,SAAS,QAAQ;AACjF;;;;;;;AAcA,SAAgB,kBACd,KACA,SACA,OACA,MAAM,OACS;CACf,MAAM,QAAQ,MAAM;CAEpB,IAAI,UAAU,GAAG,OAAO;CACxB,IAAI,QAAQ,QAAQ,OAAO;CAC3B,IAAI,QAAQ,OAAO,OAAO,QAAQ;CAElC,MAAM,UAAU,MAAM,cAAc;CACpC,MAAM,WAAW,MAAM,eAAe;CAEtC,IAAI,QAAQ,SAAS,OAAO,UAAU,QAAQ,IAAI,UAAU,IAAI;CAChE,IAAI,QAAQ,UAAU,OAAO,UAAU,IAAI,UAAU,IAAI;CACzD,IAAI,QAAQ,eAAe,QAAQ,WAAW,OAAO;CAErD,MAAM,OAAO,MAAM;CAEnB,IAAI,CAAC,MAAM,OAAO;CAElB,MAAM,QAAQ,QAAQ;CACtB,IAAI,SAAwB;CAG5B,MAAM,SAAS,SAAS;EAGtB,KAFc,QAAQ,KAAK,MAAM,KAAK,MAAM,IAAI,KAAK,MAAM,KAAK,MAAM,OAExD,WAAW,SAAS,QAAQ,KAAK,MAAM,SAAS,KAAK,MAAM,UACvE,SAAS,KAAK;CAElB,CAAC;CAED,IAAI,WAAW,MAAM,OAAO;CAE5B,IAAI,OAAsB;CAE1B,MAAM,SAAS,MAAM,UAAU;EAC7B,IAAI,KAAK,IAAI,KAAK,MAAO,MAAiB,IAAI,GAAG;EAEjD,MAAM,YAAY,MAAM,QAAQ;EAEhC,IACE,SAAS,QACT,CAAC,aACD,KAAK,IAAI,KAAK,OAAO,KAAK,IAAI,IAAI,KAAK,IAAI,UAAU,OAAO,KAAK,IAAI,GAErE,OAAO;CAEX,CAAC;CAED,OAAO;AACT;;AAKA,SAAgB,kBACd,SACA,QACA,UAC0B;CAC1B,IAAI,UAAU;EACZ,MAAM,SAAS,MAAM,QAAQ,OAAO,IAAI,QAAQ,IAAI,MAAM,IAAI,CAAC;EAE/D,OAAO,OAAO,SAAS,MAAM,IACzB,OAAO,QAAQ,UAAU,UAAU,MAAM,IACzC,CAAC,GAAG,QAAQ,MAAM;CACxB;CAEA,OAAO,sBAAsB,SAAS,KAAK,CAAC,CAAC,SAAS,MAAM,IAAI,OAAO;AACzE;;AAGA,SAAgB,sBAAsB,SAAkB,UAA6B;CACnF,IAAI,UAAU,OAAO,MAAM,QAAQ,OAAO,IAAI,QAAQ,IAAI,MAAM,IAAI,CAAC;CACrE,IAAI,YAAY,QAAQ,YAAY,KAAA,KAAa,YAAY,IAAI,OAAO,CAAC;CACzE,IAAI,OAAO,YAAY,WAAW,OAAO,CAAC,UAAU,MAAM,GAAG;CAE7D,OAAO,CAAC,OAAO,OAAO,CAAC;AACzB;AAIA,SAAS,QAAQ,OAAe,MAAc,KAAqB;CACjE,MAAM,YAAY,OAAO,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,GAAA,CAAI;CAEpD,OAAO,QAAQ,KAAK,OAAO,QAAQ,OAAO,IAAI,IAAI,OAAO,IAAA,CAAK,QAAQ,QAAQ,CAAC;AACjF;;AAGA,SAAgB,aACd,OACA,QACU;CACV,MAAM,SAAS,OAAgB,aAA6B;EAC1D,MAAM,SAAS,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;EAE/D,OAAO,OAAO,SAAS,MAAM,KAAK,UAAU,QAAQ,UAAU,KAC1D,KAAK,IAAI,OAAO,KAAK,KAAK,IAAI,OAAO,KAAK,MAAM,CAAC,IACjD;CACN;CAEA,IAAI,OAAO,UAGT,QAFa,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,IAAI,QAAQ,CAAC,OAAO,KAAK,OAAO,GAAG,EAAA,CAE3E,KAAK,SAAS,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;CAGzE,OAAO,CAAC,MAAM,OAAO,OAAO,GAAG,CAAC;AAClC;;AAGA,SAAgB,YACd,QACA,QAC0B;CAC1B,OAAO,OAAO,WAAW,CAAC,GAAG,MAAM,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC,IAAK,OAAO,MAAM;AAC7E;;;;;AAgBA,SAAgB,WACd,QACa;CACb,IAAI,OAAO,SACT,OAAO,OAAO,QAAQ,KAAK,SAAS;EAClC,OAAO,IAAI;EACX,SAAS,IAAI;EACb,UAAU,IAAI,SAAS;EACvB,GAAI,IAAI,SAAS,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CAC1C,EAAE;CAGJ,MAAM,QAAQ,OAAO,MAAM,OAAO;CAElC,IAAI,EAAE,QAAQ,IAAI,OAAO,CAAC;CAE1B,IAAI,WAAW,OAAO,eAAe,OAAO,cAAc,IAAI,OAAO,cAAc,OAAO;CAG1F,OAAO,QAAQ,WAAW,KAAK,YAAY;CAE3C,MAAM,SAAmB,CAAC;CAE1B,KAAK,IAAI,QAAQ,OAAO,KAAK,QAAQ,OAAO,MAAM,WAAW,KAAK,SAAS,UACzE,OAAO,KAAK,QAAQ,KAAK,IAAI,OAAO,OAAO,GAAG,GAAG,OAAO,MAAM,OAAO,GAAG,CAAC;CAG3E,IAAI,OAAO,OAAO,SAAS,OAAO,OAAO,KAAK,OAAO,KAAK,OAAO,GAAG;CAEpE,MAAM,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,SAAS,KAAK,EAAE,CAAC;CAElE,OAAO,OAAO,KAAK,OAAO,WAAW;EACnC;EACA,UAAW,QAAQ,OAAO,OAAO,QAAS;EAC1C,UAAU,QAAQ,eAAe,KAAK,UAAU,OAAO,SAAS;CAClE,EAAE;AACJ;;AAaA,SAAS,eAAe,QAAsC;CAC5D,MAAM,SAAS;EACb;GAAE,SAAS;GAAG,OAAO,OAAO;GAAK,MAAM,OAAO;EAAK;EACnD,GAAI,OAAO,mBAAmB,CAAC;EAC/B;GAAE,SAAS;GAAK,OAAO,OAAO;EAAI;CACpC;CACA,MAAM,WAA4B,CAAC;CACnC,IAAI,OAAO,OAAO;CAElB,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,SAAS,GAAG,SAAS;EACtD,MAAM,OAAO,OAAO;EACpB,MAAM,KAAK,OAAO,QAAQ;EAC1B,OAAO,KAAK,QAAQ;EACpB,SAAS,KAAK;GACZ,aAAa,KAAK;GAClB,WAAW,GAAG;GACd,WAAW,KAAK;GAChB,SAAS,GAAG;GACZ;EACF,CAAC;CACH;CAEA,OAAO;AACT;;AAGA,SAAgB,cAAc,OAAe,QAA6B;CACxE,KAAK,MAAM,WAAW,eAAe,MAAM,GACzC,IAAI,SAAS,QAAQ,WAAW,QAAQ,aAAa,KAAK;EACxD,IAAI,QAAQ,YAAY,QAAQ,WAAW,OAAO,QAAQ;EAE1D,MAAM,QAAQ,KAAK,IACjB,GACA,KAAK,IAAI,IAAI,QAAQ,QAAQ,cAAc,QAAQ,UAAU,QAAQ,UAAU,CACjF;EAEA,OAAO,QAAQ,cAAc,SAAS,QAAQ,YAAY,QAAQ;CACpE;CAGF,OAAO;AACT;;AAGA,SAAgB,cAAc,SAAiB,QAAqB,OAAO,MAAc;CACvF,KAAK,MAAM,WAAW,eAAe,MAAM,GACzC,IAAI,WAAW,QAAQ,aAAa,QAAQ,aAAa,KAAK;EAC5D,MAAM,OAAO,QAAQ,YAAY,QAAQ;EACzC,MAAM,QACJ,SAAS,IAAI,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,UAAU,QAAQ,eAAe,IAAI,CAAC;EAClF,MAAM,QAAQ,QAAQ,YAAY,SAAS,QAAQ,UAAU,QAAQ;EAErE,OAAO,QAAQ,QAAQ,OAAO,IAC1B,KAAK,IAAI,QAAQ,SAAS,QAAQ,OAAO,QAAQ,MAAM,QAAQ,SAAS,CAAC,IACzE;CACN;CAGF,OAAO,OAAO;AAChB;;AAGA,SAAgB,WAAW,OAAe,QAA6B;CACrE,OAAO,cAAc,cAAc,OAAO,MAAM,GAAG,MAAM;AAC3D;;AAGA,SAAgB,aACd,QACkB;CAClB,MAAM,CAAC,OAAO,OAAO,OAAO,gBAAgB,CAAC,GAAG,CAAC;CAEjD,OAAO,CAAC,OAAO,MAAM,OAAO,OAAO,MAAM,GAAG;AAC9C;;AAGA,SAAgB,mBAAmB,QAAwC,OAAwB;CACjG,OAAO,MAAM,QAAQ,OAAO,QAAQ,IAAI,OAAO,SAAS,WAAW,OAAO,QAAQ,OAAO,QAAQ;AACnG;;AAGA,SAAgB,kBAAkB,QAAiD;CACjF,OAAO,MAAM,QAAQ,OAAO,QAAQ,IAAI,OAAO,SAAS,SAAS,IAAI,IAAI,QAAQ,OAAO,QAAQ;AAClG;;;;;AAMA,SAAgB,mBACd,QACA,QAC8C;CAC9C,IAAI,CAAC,MAAM,QAAQ,OAAO,SAAS,GAAG,OAAO;CAE7C,MAAM,QAAQ;EAAC;EAAG,GAAG,OAAO,KAAK,UAAU,cAAc,OAAO,MAAM,CAAC;EAAG;CAAG;CAE7E,OAAO,OAAO,UAAU,SAAS,QAAQ,UAAU;EACjD,MAAM,QAAQ,MAAM;EACpB,MAAM,MAAM,MAAM,QAAQ;EAE1B,OAAO,UAAU,UAAU,KAAA,KAAa,QAAQ,KAAA,KAAa,MAAM,QAAQ,CAAC;GAAE;GAAO;EAAI,CAAC,IAAI,CAAC;CACjG,CAAC;AACH;;AAGA,SAAgB,gBACd,SACA,SACwB;CACxB,MAAM,OAAO,GAAG,QAAQ,MAAM,QAAQ,MAAM;CAC5C,MAAM,SAAS,GAAG,QAAQ,MAAM;CAEhC,IAAI,QAAQ,UACV,OAAO,QAAQ,cAAc;EAAE,KAAK;EAAQ,QAAQ;CAAK,IAAI;EAAE,QAAQ;EAAQ,QAAQ;CAAK;CAG9F,OAAO,QAAQ,cAAc,QACzB;EAAE,OAAO;EAAQ,OAAO;CAAK,IAC7B;EAAE,MAAM;EAAQ,OAAO;CAAK;AAClC;;AAGA,SAAgB,eAAe,QAA6D;CAC1F,OAAO,IAAI,IAAI,OAAO,YAAY,CAAC,KAAK,CAAC;AAC3C;;AAGA,SAAgB,gBACd,QACA,OACe;CACf,OAAO,OAAO,QAAQ,KAAA,KAAa,OAAO,QAAQ,OAAO,QAAQ,OAAO,MAAM,QAAQ;AACxF;;;;;AAMA,SAAgB,gBACd,QACA,SAAS,OACuD;CAChE,IAAI,OAAO,mBAAmB,OAAO,gBAAgB,SAAS,GAC5D,OAAO;EAAE,KAAK;EAAG,KAAK;EAAK,MAAM,SAAS,MAAO;EAAK,WAAW;CAAK;CAGxE,OAAO;EACL,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,MAAM,UAAU,OAAO,MAAM,OAAO,OAAO,OAAQ,OAAO,OAAO,OAAO;EACxE,WAAW;CACb;AACF;;AAGA,SAAgB,kBAAkB,QAAkB,QAA+B;CACjF,OAAO,gBAAgB,MAAM,CAAC,CAAC,YAC3B,OAAO,KAAK,UAAU,cAAc,OAAO,MAAM,CAAC,IAClD;AACN;;AAGA,SAAgB,oBAAoB,QAAkB,QAAqB,OAAO,MAAgB;CAChG,OAAO,gBAAgB,MAAM,CAAC,CAAC,YAC3B,OAAO,KAAK,aAAa,cAAc,UAAU,QAAQ,IAAI,CAAC,IAC9D;AACN;;;;;;;AAWA,SAAgB,iBACd,UACA,MACA,QACU;CACV,MAAM,CAAC,KAAK,QAAQ,aAAa,MAAM;CACvC,MAAM,WAAW,eAAe,MAAM;CACtC,MAAM,SAAS,UAAkB,KAAK,IAAI,MAAM,KAAK,IAAI,KAAK,KAAK,CAAC;CAEpE,IAAI,KAAK,WAAW,SAAS,UAAU,SAAS,SAAS,GACvD,OAAO,KAAK,IAAI,KAAK;CAGvB,MAAM,QAAQ,iBAAiB,UAAU,IAAI;CAE7C,IAAI,CAAC,OAAO,OAAO,KAAK,IAAI,KAAK;CAEjC,MAAM,CAAC,OAAO,SAAS;CAEvB,IAAI,SAAS,IAAI,OAAO,GACtB,OAAO,YAAY,UAAU,QAAQ,SAAS,QAAS,MAAM;CAG/D,IAAI,SAAS,IAAI,eAAe,GAC9B,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;CAGlD,MAAM,MAAM,OAAO,iBAAiB;CACpC,MAAM,QAAQ,QAAQ,IAAI,SAAS,QAAQ,KAAM,MAAM;CACvD,MAAM,QAAQ,QAAQ,SAAS,SAAS,IAAI,SAAS,QAAQ,KAAM,MAAM;CACzE,MAAM,SAAS,CAAC,GAAG,QAAQ;CAC3B,OAAO,SAAS,KAAK,IAAI,KAAK,IAAI,MAAM,KAAK,GAAG,KAAK,IAAI,KAAK,IAAI,KAAK,KAAK,GAAG,KAAK,CAAC;CAErF,OAAO;AACT;;;;;AAMA,SAAS,iBAAiB,UAAoB,MAAyC;CACrF,MAAM,YAAY,CAAC,GAAG,IAAI;CAC1B,MAAM,UAAoB,CAAC;CAE3B,KAAK,MAAM,CAAC,OAAO,UAAU,SAAS,QAAQ,GAAG;EAC/C,MAAM,QAAQ,UAAU,QAAQ,KAAK;EAErC,IAAI,UAAU,IAAI,QAAQ,KAAK,KAAK;OAC/B,UAAU,OAAO,OAAO,CAAC;CAChC;CAEA,IAAI,QAAQ,WAAW,KAAK,UAAU,WAAW,GAAG,OAAO;CAE3D,OAAO,CAAC,QAAQ,IAAK,UAAU,EAAG;AACpC;;;;;AAMA,SAAgB,YAAY,QAAkB,OAAe,QAA+B;CAC1F,IAAI,OAAO,WAAW,GAAG,OAAO;CAEhC,MAAM,CAAC,KAAK,QAAQ,aAAa,MAAM;CACvC,MAAM,QAAQ,KAAK,IAAI,GAAG,MAAM;CAChC,MAAM,OAAO,KAAK,IAAI,GAAG,MAAM;CAC/B,MAAM,UAAU,KAAK,IAAI,OAAO,MAAM,KAAK,IAAI,MAAM,OAAO,KAAK,CAAC;CAElE,OAAO,OAAO,KAAK,UAAU,QAAQ,OAAO;AAC9C;;;;;;AAOA,SAAgB,qBACd,OACA,SAWM;CACN,MAAM,EAAE,OAAO,QAAQ,QAAQ,WAAW,aAAa;CACvD,MAAM,OAAO,MAAM,sBAAsB;CACzC,MAAM,OAAO,WAAW,KAAK,SAAS,KAAK;CAE3C,IAAI,EAAE,OAAO,MAAM,OAAO,SAAS,GAAG;CAEtC,MAAM,eAAe;CACrB,MAAM,gBAAgB;CAEtB,MAAM,QAAQ,WAAW,MAAM,UAAU,MAAM;CAC/C,MAAM,WAAW,OAAO,KAAK,UAAU,cAAc,OAAO,MAAM,CAAC;CAEnE,MAAM,QAAQ,cAA4B;EAExC,IAAI,UADY,WAAW,UAAU,UAAU,UAAU,WACjC,SAAS,OAAQ;EAEzC,IAAI,WAAW,CAAC,QAAQ,cAAc,cAAc,OAAO,QAAQ,CAAC;EAEpE,MAAM,CAAC,KAAK,QAAQ,aAAa,MAAM,CAAC,CAAC,KAAK,UAAU,cAAc,OAAO,MAAM,CAAC;EAIpF,MAAM,UAAU,KAAK,IACnB,OAAO,KAAK,IAAI,GAAG,QAAQ,GAC3B,KAAK,IAAI,MAAM,KAAK,IAAI,GAAG,QAAQ,GAAG,KAAK,CAC7C;EACA,MAAM,QAAQ,SAAS,KAAK,YAAY,cAAc,UAAU,SAAS,MAAM,CAAC;EAEhF,QAAQ,SACN,gBAAgB,MAAM,CAAC,CAAC,YACpB,QACA,YAAY,QAAQ,MAAM,KAAM,OAAO,IAAK,MAAM,CACxD;CACF;CAEA,MAAM,YAAY;EAChB,OAAO,oBAAoB,eAAe,IAAI;EAC9C,OAAO,oBAAoB,aAAa,GAAG;EAC3C,OAAO,oBAAoB,iBAAiB,GAAG;EAC/C,QAAQ,QAAQ;CAClB;CAEA,OAAO,iBAAiB,eAAe,IAAI;CAC3C,OAAO,iBAAiB,aAAa,GAAG;CACxC,OAAO,iBAAiB,iBAAiB,GAAG;AAC9C;;;;;AAMA,SAAgB,gBACd,QACA,QACS;CACT,MAAM,WAAW,eAAe,MAAM;CAEtC,IAAI,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,MAAM,GAAG,OAAO;CAExD,OAAO,EAAE,kBAAkB,WAAW,OAAO,QAAQ,mBAAiB;AACxE;;;;;AAcA,SAAgB,WACd,OACA,gBACA,cACA,SACA,UAAU,OACA;CACV,MAAM,SAAS,IAAI,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC;CAE9C,IAAI,CAAC,WAAW,mBAAmB,cACjC,OAAO;EACL,OAAO,MAAM,MAAM,GAAG,cAAc,IAAI,SAAS,MAAM,MAAM,YAAY;EACzE,gBAAgB,iBAAiB,OAAO;EACxC,cAAc,iBAAiB,OAAO;CACxC;CAGF,MAAM,YAAY,MAAM,YAAY,MAAM,iBAAiB,CAAC,IAAI;CAEhE,MAAM,QADQ,MAAM,MAAM,WAAW,YACnB,CAAC,CAAC,MAAM,IAAI;CAC9B,IAAI,aAAa;CACjB,IAAI,aAAa;CAEjB,MAAM,OAAO,MAAM,KAAK,MAAM,UAAU;EACtC,IAAI,SAAS;GACX,MAAM,YAAY,KAAK,MAAM,IAAI,OAAO,QAAQ,OAAO,OAAO,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,UAAU;GAEvF,IAAI,UAAU,GAAG,aAAa,CAAC;GAC/B,cAAc;GAEd,OAAO,KAAK,MAAM,SAAS;EAC7B;EAEA,IAAI,UAAU,GAAG,aAAa,OAAO;EACrC,cAAc,OAAO;EAErB,OAAO,SAAS;CAClB,CAAC;CAED,OAAO;EACL,OAAO,MAAM,MAAM,GAAG,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,MAAM,YAAY;EAC7E,gBAAgB,KAAK,IAAI,WAAW,iBAAiB,UAAU;EAC/D,cAAc,KAAK,IAAI,WAAW,eAAe,UAAU;CAC7D;AACF;;AAGA,SAAgB,kBACd,OACA,gBACA,cACU;CACV,MAAM,YAAY,MAAM,YAAY,MAAM,iBAAiB,CAAC,IAAI;CAEhE,MAAM,SAAS,KADA,MAAM,MAAM,WAAW,cAAc,CAAC,CAAC,MAAM,SAAS,CAAC,GAAG,MAAM;CAG/E,OAAO;EACL,OAAO,MAAM,MAAM,GAAG,cAAc,IAAI,SAAS,MAAM,MAAM,YAAY;EACzE,gBAAgB,iBAAiB,OAAO;EACxC,cAAc,iBAAiB,OAAO;CACxC;AACF;;AAKA,SAAgB,gBAAgB,YAAoB,OAA4C;CAC9F,MAAM,SAAkC,CAAC;CAEzC,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,KAAK,GAAoC;EAClF,IAAI,UAAU,QAAQ,UAAU,KAAA,KAAa,UAAU,IAAI;EAE3D,OAAO,cAAc,YAAY,IAAI,KAAK;CAC5C;CAEA,OAAO;AACT;;;;;AAMA,SAAgB,oBACd,UACA,KACA,UACA,UACU;CACV,IAAI,SAAS,SAAS,GAAG,GACvB,OAAO,SAAS,QAAQ,cAAc,cAAc,GAAG;CAGzD,IAAI,CAAC,UAAU,OAAO,CAAC,GAAG;CAE1B,IAAI,YAAY,QAAQ,SAAS,UAAU,UAAU,OAAO;CAE5D,OAAO,CAAC,GAAG,UAAU,GAAG;AAC1B;;;;;;;AAQA,SAAgB,wBAAwB,OAAkB,MAAgC;CACxF,OAAO;EACL,OAAO,MAAM;EACb,WAAW,MAAM;EACjB,QAAQ,MAAM;EACd,YAAY,MAAM;EAClB,eAAe;GACb,GAAG,MAAM,eAAe;GACxB,eAAe;GACf,YAAY,MAAM,MAAM;EAC1B;CACF;AACF;;;;;;AASA,SAAgB,gBAAgB,OAA0B;CACxD,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,QAAQ,SAAyB,OAAO,SAAS,YAAY,SAAS,EAAE;CAGvF,OAAO,OAAO,UAAU,YAAY,UAAU,KAAK,CAAC,KAAK,IAAI,CAAC;AAChE;;AAGA,SAAgB,iBACd,QACoB;CACpB,IAAI,CAAC,OAAO,qBAAqB,OAAO,kBAAkB,WAAW,GAAG,OAAO,KAAA;CAE/E,MAAM,SAAS,OAAO,QAAQ,OAAO,eAAe,CAAC,CAAC,CAAC,CACpD,QAAQ,GAAG,UAAU,OAAO,kBAAmB,SAAS,IAAI,CAAC,CAAC,CAC9D,KAAK,CAAC,eAAe,IAAI,WAAW;CAEvC,OAAO,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAG,OAAO,mBAAmB,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG;AACxE;;AAGA,SAAgB,eAAe,OAAsD;CACnF,MAAM,CAAC,OAAO,WAAW,SAAS,GAAA,CAAI,MAAM,MAAM,CAAC,CAAC,KAAK,SAAS,OAAO,KAAK,KAAK,CAAC,CAAC;CAErF,OAAO,SAAS,UAAU,QAAQ,KAAK,SAAS,IAAI,GAAG,MAAM,KAAK,WAAW,KAAA;AAC/E;;;;;AAMA,SAAgB,mBACd,OACA,KACA,QACA,UACU;CACV,OAAO,OAAO,gBAAgB,QAC1B,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM,GAAG,QAAQ,IACjC,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC,MAAM,CAAC,QAAQ;AACrC;;AAGA,SAAgB,kBAAkB,MAGhC;CACA,OAAO;EAAE,MAAM,KAAK,WAAW,KAAK;EAAK,UAAU,KAAK,eAAe,KAAK;CAAI;AAClF;;;;;AAMA,SAAgB,mBAAmB,OAA0D;CAC3F,MAAM,MAAM,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,KAAK,CAAC;CAC9C,MAAM,UAA6B,CAAC;CACpC,IAAI,UAAU;CAEd,MAAM,gBAAgB;EACpB;EACA,QAAQ,MAAM,CAAC,GAAG;CACpB;CAEA,QAAW,SACT,IAAI,SAAY,SAAS,WAAW;EAClC,MAAM,cAAc;GAClB;GACA,KAAK,CAAC,CAAC,KAAK,SAAS,MAAM,CAAC,CAAC,QAAQ,OAAO;EAC9C;EAEA,IAAI,UAAU,KAAK,MAAM;OACpB,QAAQ,KAAK,KAAK;CACzB,CAAC;AACL;;AAGA,SAAgB,mBAAmB,OAAgB,UAA6B;CAC9E,IAAI,UAAU,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,IAAI,MAAM,IAAI,CAAC;CAEjE,OAAO,UAAU,QAAQ,UAAU,KAAA,KAAa,UAAU,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC;AACpF;;AAYA,SAAgB,iBACd,YACyB;CACzB,IAAI,CAAC,YAAY,OAAO;CAExB,MAAM,OAAO,iBAAiB,aAAa,WAAW,cAAc;CACpE,MAAM,UACJ,cAAc,aACV,OAAO,WAAW,WAClB,iBAAiB,cAAc,kBAAkB,cAAc,WAAW;CAEhF,OAAO;EACL;EACA,aAAa,iBAAiB,cAAc,OAAO;EACnD,SAAS,QAAQ,OAAO;EACxB,MAAM,WAAW;EACjB,IAAI,WAAW;EACf,OAAO,WAAW,aAAa,WAAW,QAAQ,KAAA;CACpD;AACF"}
|
|
1
|
+
{"version":3,"file":"advanced-fields.js","names":[],"sources":["../src/advanced-fields.ts"],"sourcesContent":["import type { FormStore, FormStoreOptions } from './form'\nimport type {\n BuilderBlockSchema,\n BuilderItemState,\n BuilderSchema,\n SliderBehavior,\n SliderSchema,\n} from './schema/advanced-fields'\nimport type { FileUploadSchema, SchemaComponent, UploadedFile } from './schema/components'\nimport type { AnyPaginationSchema } from './schema/table'\nimport type { TableQuery } from './table'\nimport { tableQueryKey } from './table'\nimport { cloneState } from './utils/clone'\nimport { templateForIndex } from './utils/path'\n\n// ---------------------------------------------------------------- Builder\n\n/** The Builder items in a state value, keeping only well-formed `{type, data}` items. */\nexport function builderItems(value: unknown): BuilderItemState[] {\n if (!Array.isArray(value)) return []\n\n return value.filter(\n (item): item is BuilderItemState =>\n typeof item === 'object' &&\n item !== null &&\n typeof (item as BuilderItemState).type === 'string',\n )\n}\n\n/** Insert a fresh item of `block` at `index` (the end by default). */\nexport function insertBuilderItem(\n items: BuilderItemState[],\n block: BuilderBlockSchema,\n index: number = items.length,\n): BuilderItemState[] {\n const copy = [...items]\n copy.splice(Math.max(0, Math.min(index, items.length)), 0, cloneState(block.state))\n\n return copy\n}\n\n/** How many items of each block type the builder holds. */\nexport function builderBlockCounts(items: BuilderItemState[]): Record<string, number> {\n const counts: Record<string, number> = {}\n\n for (const item of items) counts[item.type] = (counts[item.type] ?? 0) + 1\n\n return counts\n}\n\n/**\n * Blocks whose label (or group heading) contains `query` (case-insensitive), for\n * `Builder::searchable()`.\n */\nexport function filterBuilderBlocks<T extends Pick<BuilderBlockSchema, 'label' | 'group'>>(\n blocks: T[],\n query: string,\n): T[] {\n const needle = query.trim().toLocaleLowerCase()\n\n return needle === ''\n ? blocks\n : blocks.filter(\n (block) =>\n block.label.toLocaleLowerCase().includes(needle) ||\n (block.group ?? '').toLocaleLowerCase().includes(needle),\n )\n}\n\nexport interface BuilderBlockGroup<T> {\n /** The heading; null for ungrouped blocks (listed first, without a heading). */\n group: string | null\n blocks: T[]\n}\n\n/**\n * Picker blocks under their headings (`Builder::blocks(['Group' => [...]])`): ungrouped\n * blocks first, then each group in the order it first appears. Empty groups are dropped.\n */\nexport function groupBuilderBlocks<T extends Pick<BuilderBlockSchema, 'group'>>(\n blocks: T[],\n): BuilderBlockGroup<T>[] {\n const groups = new Map<string | null, T[]>([[null, []]])\n\n for (const block of blocks) {\n const group = block.group ?? null\n\n if (!groups.has(group)) groups.set(group, [])\n groups.get(group)!.push(block)\n }\n\n return [...groups.entries()]\n .filter(([, items]) => items.length > 0)\n .map(([group, items]) => ({ group, blocks: items }))\n}\n\n/** Whether another item of `block` may be added (its own `maxItems` and the builder's). */\nexport function canAddBuilderBlock(\n component: Pick<BuilderSchema, 'maxItems'>,\n block: Pick<BuilderBlockSchema, 'name' | 'maxItems'>,\n items: BuilderItemState[],\n): boolean {\n // `null` (sent by PHP for \"no limit\") and `undefined` both mean unlimited.\n if (component.maxItems != null && items.length >= component.maxItems) return false\n\n return block.maxItems == null || (builderBlockCounts(items)[block.name] ?? 0) < block.maxItems\n}\n\n/**\n * The schema to render for the item at `index`: the server's per-item schema when it\n * describes the same block, else the block template with the index filled in (items the\n * browser just added, cloned or moved before the server has seen them).\n */\nexport function builderItemSchema(\n component: Pick<BuilderSchema, 'items' | 'blocks'>,\n items: BuilderItemState[],\n index: number,\n): SchemaComponent[] {\n const type = items[index]?.type\n const server = component.items[index]\n\n if (server && server.type === type) {\n return server.schema\n }\n\n const block = component.blocks.find((candidate) => candidate.name === type)\n\n return block ? templateForIndex(block.schema, index) : []\n}\n\n/** Label of the item at `index`: the server's when it describes the same block. */\nexport function builderItemLabel(\n component: Pick<BuilderSchema, 'items' | 'blocks'>,\n items: BuilderItemState[],\n index: number,\n): string | null {\n const type = items[index]?.type\n const server = component.items[index]\n\n if (server && server.type === type && server.label) return server.label\n\n return component.blocks.find((block) => block.name === type)?.label ?? type ?? null\n}\n\n/** Where an option of the block picker sits (`getBoundingClientRect()` is enough). */\nexport interface BuilderPickerRect {\n top: number\n left: number\n}\n\n/**\n * Keyboard movement in the block picker grid: the index of the option to focus after\n * `key` on option `current`, or null when the key moves nowhere. Left/right follow the\n * reading direction (`rtl`), up/down go to the closest option of the row above/below\n * (rows may be ragged: groups start new rows), Home/End jump to the first/last option.\n */\nexport function builderPickerMove(\n key: string,\n current: number,\n rects: BuilderPickerRect[],\n rtl = false,\n): number | null {\n const count = rects.length\n\n if (count === 0) return null\n if (key === 'Home') return 0\n if (key === 'End') return count - 1\n\n const forward = rtl ? 'ArrowLeft' : 'ArrowRight'\n const backward = rtl ? 'ArrowRight' : 'ArrowLeft'\n\n if (key === forward) return current < count - 1 ? current + 1 : null\n if (key === backward) return current > 0 ? current - 1 : null\n if (key !== 'ArrowDown' && key !== 'ArrowUp') return null\n\n const here = rects[current]\n\n if (!here) return null\n\n const below = key === 'ArrowDown'\n let rowTop: number | null = null\n\n // The nearest row in that direction (options on one row share their top, ±2px).\n rects.forEach((rect) => {\n const ahead = below ? rect.top > here.top + 2 : rect.top < here.top - 2\n\n if (ahead && (rowTop === null || (below ? rect.top < rowTop : rect.top > rowTop))) {\n rowTop = rect.top\n }\n })\n\n if (rowTop === null) return null\n\n let best: number | null = null\n\n rects.forEach((rect, index) => {\n if (Math.abs(rect.top - (rowTop as number)) > 2) return\n\n const candidate = rects[best ?? -1]\n\n if (\n best === null ||\n !candidate ||\n Math.abs(rect.left - here.left) < Math.abs(candidate.left - here.left)\n ) {\n best = index\n }\n })\n\n return best\n}\n\n// ---------------------------------------------------------------- ToggleButtons\n\n/** The next state after a toggle button for `option` was pressed. */\nexport function toggleButtonsNext(\n current: unknown,\n option: string,\n multiple: boolean,\n): string | string[] | null {\n if (multiple) {\n const values = Array.isArray(current) ? current.map(String) : []\n\n return values.includes(option)\n ? values.filter((value) => value !== option)\n : [...values, option]\n }\n\n return toggleButtonsSelected(current, false).includes(option) ? null : option\n}\n\n/** The option values a toggle buttons state selects. */\nexport function toggleButtonsSelected(current: unknown, multiple: boolean): string[] {\n if (multiple) return Array.isArray(current) ? current.map(String) : []\n if (current === null || current === undefined || current === '') return []\n if (typeof current === 'boolean') return [current ? '1' : '0']\n\n return [String(current)]\n}\n\n// ---------------------------------------------------------------- Slider\n\nfunction roundTo(value: number, step: number, min: number): number {\n const decimals = (String(step).split('.')[1] ?? '').length\n\n return Number((Math.round((value - min) / step) * step + min).toFixed(decimals))\n}\n\n/** The slider's thumb values: always numbers within bounds, sorted for ranges. */\nexport function sliderValues(\n value: unknown,\n schema: Pick<SliderSchema, 'min' | 'max' | 'multiple'>,\n): number[] {\n const clamp = (input: unknown, fallback: number): number => {\n const number = typeof input === 'number' ? input : Number(input)\n\n return Number.isFinite(number) && input !== null && input !== ''\n ? Math.min(schema.max, Math.max(schema.min, number))\n : fallback\n }\n\n if (schema.multiple) {\n const list = Array.isArray(value) && value.length > 0 ? value : [schema.min, schema.max]\n\n return list.map((item) => clamp(item, schema.min)).sort((a, b) => a - b)\n }\n\n return [clamp(value, schema.min)]\n}\n\n/** The state to store for the given thumb values. */\nexport function sliderState(\n values: number[],\n schema: Pick<SliderSchema, 'multiple'>,\n): number | number[] | null {\n return schema.multiple ? [...values].sort((a, b) => a - b) : (values[0] ?? null)\n}\n\nexport interface SliderPip {\n value: number\n /** Position from the start of the track, 0–100. */\n percent: number\n /** Whether the pip shows a label (at most 11 are labelled by default). */\n labelled: boolean\n /** A small label (`pipsFilter()` returned 2, or `PipsMode::Steps` between range points). */\n small?: boolean\n}\n\n/**\n * Tick marks along the track: the server's `pipList` (modes, `steppedPips()`,\n * `pipsFilter()`), or every `pipsDensity` units / every step for schemas without one.\n */\nexport function sliderPips(\n schema: Pick<SliderSchema, 'min' | 'max' | 'step' | 'pipsDensity' | 'pipList'>,\n): SliderPip[] {\n if (schema.pipList) {\n return schema.pipList.map((pip) => ({\n value: pip.value,\n percent: pip.percent,\n labelled: pip.type !== 0,\n ...(pip.type === 2 ? { small: true } : {}),\n }))\n }\n\n const range = schema.max - schema.min\n\n if (!(range > 0)) return []\n\n let interval = schema.pipsDensity && schema.pipsDensity > 0 ? schema.pipsDensity : schema.step\n\n // Never draw more than 100 ticks.\n while (range / interval > 100) interval *= 2\n\n const values: number[] = []\n\n for (let value = schema.min; value < schema.max + interval / 1e6; value += interval) {\n values.push(roundTo(Math.min(value, schema.max), schema.step, schema.min))\n }\n\n if (values[values.length - 1] !== schema.max) values.push(schema.max)\n\n const labelEvery = Math.max(1, Math.ceil((values.length - 1) / 10))\n\n return values.map((value, index) => ({\n value,\n percent: ((value - schema.min) / range) * 100,\n labelled: index % labelEvery === 0 || index === values.length - 1,\n }))\n}\n\ntype SliderTrack = Pick<SliderSchema, 'min' | 'max' | 'step' | 'nonLinearPoints'>\n\ninterface SliderSection {\n fromPercent: number\n toPercent: number\n fromValue: number\n toValue: number\n step: number\n}\n\n/** The sections of the track between its ends and `nonLinearPoints`. */\nfunction sliderSections(schema: SliderTrack): SliderSection[] {\n const points = [\n { percent: 0, value: schema.min, step: schema.step },\n ...(schema.nonLinearPoints ?? []),\n { percent: 100, value: schema.max },\n ]\n const sections: SliderSection[] = []\n let step = schema.step\n\n for (let index = 0; index < points.length - 1; index++) {\n const from = points[index]!\n const to = points[index + 1]!\n step = from.step ?? step\n sections.push({\n fromPercent: from.percent,\n toPercent: to.percent,\n fromValue: from.value,\n toValue: to.value,\n step,\n })\n }\n\n return sections\n}\n\n/** Where a value sits on the track, 0–100 (non-linear tracks included). */\nexport function sliderPercent(value: number, schema: SliderTrack): number {\n for (const section of sliderSections(schema)) {\n if (value <= section.toValue || section.toPercent >= 100) {\n if (section.toValue === section.fromValue) return section.fromPercent\n\n const ratio = Math.min(\n 1,\n Math.max(0, (value - section.fromValue) / (section.toValue - section.fromValue)),\n )\n\n return section.fromPercent + ratio * (section.toPercent - section.fromPercent)\n }\n }\n\n return 100\n}\n\n/** The value at a position on the track (0–100), snapped to its section's step by default. */\nexport function sliderValueAt(percent: number, schema: SliderTrack, snap = true): number {\n for (const section of sliderSections(schema)) {\n if (percent <= section.toPercent || section.toPercent >= 100) {\n const span = section.toPercent - section.fromPercent\n const ratio =\n span === 0 ? 0 : Math.min(1, Math.max(0, (percent - section.fromPercent) / span))\n const value = section.fromValue + ratio * (section.toValue - section.fromValue)\n\n return snap && section.step > 0\n ? Math.min(section.toValue, roundTo(value, section.step, section.fromValue))\n : value\n }\n }\n\n return schema.max\n}\n\n/** Round a value to the step of its section of the track. */\nexport function sliderSnap(value: number, schema: SliderTrack): number {\n return sliderValueAt(sliderPercent(value, schema), schema)\n}\n\n/** The lowest and highest value a thumb can take (the track minus `rangePadding()`). */\nexport function sliderBounds(\n schema: Pick<SliderSchema, 'min' | 'max' | 'rangePadding'>,\n): [number, number] {\n const [start, end] = schema.rangePadding ?? [0, 0]\n\n return [schema.min + start, schema.max - end]\n}\n\n/** Whether thumb `index` shows its value (`tooltips()`, or `tooltips([true, false])`). */\nexport function sliderShowsTooltip(schema: Pick<SliderSchema, 'tooltips'>, index: number): boolean {\n return Array.isArray(schema.tooltips) ? schema.tooltips[index] === true : Boolean(schema.tooltips)\n}\n\n/** Whether any thumb shows its value (the slider leaves room above the track). */\nexport function sliderHasTooltips(schema: Pick<SliderSchema, 'tooltips'>): boolean {\n return Array.isArray(schema.tooltips) ? schema.tooltips.includes(true) : Boolean(schema.tooltips)\n}\n\n/**\n * Filled sections of the track for `fillTrack([false, true, false])`, as positions (0–100)\n * from the minimum. Null when `fillTrack` is not an array: the primitive's range is used.\n */\nexport function sliderFillSections(\n values: number[],\n schema: Pick<SliderSchema, 'fillTrack'> & SliderTrack,\n): Array<{ start: number; end: number }> | null {\n if (!Array.isArray(schema.fillTrack)) return null\n\n const stops = [0, ...values.map((value) => sliderPercent(value, schema)), 100]\n\n return schema.fillTrack.flatMap((filled, index) => {\n const start = stops[index]\n const end = stops[index + 1]\n\n return filled && start !== undefined && end !== undefined && end > start ? [{ start, end }] : []\n })\n}\n\n/** Inline position of a fill section: from the minimum's edge of the track. */\nexport function sliderFillStyle(\n section: { start: number; end: number },\n options: { vertical: boolean; topToBottom?: boolean; direction: 'ltr' | 'rtl' },\n): Record<string, string> {\n const size = `${section.end - section.start}%`\n const offset = `${section.start}%`\n\n if (options.vertical) {\n return options.topToBottom ? { top: offset, height: size } : { bottom: offset, height: size }\n }\n\n return options.direction === 'rtl'\n ? { right: offset, width: size }\n : { left: offset, width: size }\n}\n\n/** The pointer behaviours of a slider (`behavior()`); `tap` by default. */\nexport function sliderBehavior(schema: Pick<SliderSchema, 'behavior'>): Set<SliderBehavior> {\n return new Set(schema.behavior ?? ['tap'])\n}\n\n/** The slider's direction: `rtl()` when set, else the panel's. */\nexport function sliderDirection(\n schema: Pick<SliderSchema, 'rtl'>,\n panel: 'ltr' | 'rtl',\n): 'ltr' | 'rtl' {\n return schema.rtl === undefined || schema.rtl === null ? panel : schema.rtl ? 'rtl' : 'ltr'\n}\n\n/**\n * What the slider primitive works with: values directly, or positions (0–100) on a\n * non-linear track. `smooth` (`Behavior::SmoothSteps` while dragging) drops the step.\n */\nexport function sliderPrimitive(\n schema: SliderTrack,\n smooth = false,\n): { min: number; max: number; step: number; nonLinear: boolean } {\n if (schema.nonLinearPoints && schema.nonLinearPoints.length > 0) {\n return { min: 0, max: 100, step: smooth ? 0.01 : 0.1, nonLinear: true }\n }\n\n return {\n min: schema.min,\n max: schema.max,\n step: smooth ? (schema.max - schema.min) / 1000 || schema.step : schema.step,\n nonLinear: false,\n }\n}\n\n/** Thumb values as the primitive's values. */\nexport function sliderToPrimitive(values: number[], schema: SliderTrack): number[] {\n return sliderPrimitive(schema).nonLinear\n ? values.map((value) => sliderPercent(value, schema))\n : values\n}\n\n/** The primitive's values as thumb values (snapped unless dragging smoothly). */\nexport function sliderFromPrimitive(values: number[], schema: SliderTrack, snap = true): number[] {\n return sliderPrimitive(schema).nonLinear\n ? values.map((position) => sliderValueAt(position, schema, snap))\n : values\n}\n\ntype SliderRules = SliderTrack &\n Pick<SliderSchema, 'rangePadding' | 'behavior' | 'minDifference' | 'multiple'>\n\n/**\n * The thumb values to store after the primitive moved from `previous` to `next` (both\n * sorted): kept inside `rangePadding()`; with `fixed` every thumb moves by the same\n * amount; without `unconstrained` a thumb stops at its neighbours (`minDifference()` apart)\n * instead of passing them.\n */\nexport function sliderNextValues(\n previous: number[],\n next: number[],\n schema: SliderRules,\n): number[] {\n const [low, high] = sliderBounds(schema)\n const behavior = sliderBehavior(schema)\n const clamp = (value: number) => Math.min(high, Math.max(low, value))\n\n if (next.length !== previous.length || previous.length < 2) {\n return next.map(clamp)\n }\n\n const moved = sliderMovedThumb(previous, next)\n\n if (!moved) return next.map(clamp)\n\n const [index, value] = moved\n\n if (behavior.has('fixed')) {\n return sliderShift(previous, value - previous[index]!, schema)\n }\n\n if (behavior.has('unconstrained')) {\n return [...next].map(clamp).sort((a, b) => a - b)\n }\n\n const gap = schema.minDifference ?? 0\n const lower = index > 0 ? previous[index - 1]! + gap : low\n const upper = index < previous.length - 1 ? previous[index + 1]! - gap : high\n const result = [...previous]\n result[index] = Math.min(Math.min(high, upper), Math.max(Math.max(low, lower), value))\n\n return result\n}\n\n/**\n * Which thumb of `previous` moved, and where to, when exactly one value changed (the\n * primitive may have re-sorted the thumbs after one passed another).\n */\nfunction sliderMovedThumb(previous: number[], next: number[]): [number, number] | null {\n const remaining = [...next]\n const removed: number[] = []\n\n for (const [index, value] of previous.entries()) {\n const found = remaining.indexOf(value)\n\n if (found === -1) removed.push(index)\n else remaining.splice(found, 1)\n }\n\n if (removed.length !== 1 || remaining.length !== 1) return null\n\n return [removed[0]!, remaining[0]!]\n}\n\n/**\n * Move every thumb by `delta` (in values), keeping their distances and stopping at the\n * ends of the track (minus `rangePadding()`).\n */\nexport function sliderShift(values: number[], delta: number, schema: SliderRules): number[] {\n if (values.length === 0) return values\n\n const [low, high] = sliderBounds(schema)\n const first = Math.min(...values)\n const last = Math.max(...values)\n const allowed = Math.min(high - last, Math.max(low - first, delta))\n\n return values.map((value) => value + allowed)\n}\n\n/**\n * `Behavior::Drag`: follow a pointer that grabbed the filled range between the thumbs and\n * move them together, until the pointer is released. Positions are measured on `track`\n * in the slider's direction (bottom to top when vertical).\n */\nexport function startSliderRangeDrag(\n event: PointerEvent,\n options: {\n track: HTMLElement\n values: number[]\n schema: SliderRules\n direction: 'ltr' | 'rtl'\n vertical: boolean\n /** `vertical(isTopToBottom: true)`: the minimum is at the top. */\n topToBottom?: boolean\n onChange: (values: number[]) => void\n onEnd?: () => void\n },\n): void {\n const { track, values, schema, direction, vertical } = options\n const rect = track.getBoundingClientRect()\n const size = vertical ? rect.height : rect.width\n\n if (!(size > 0) || values.length < 2) return\n\n event.preventDefault()\n event.stopPropagation()\n\n const start = vertical ? event.clientY : event.clientX\n const percents = values.map((value) => sliderPercent(value, schema))\n\n const move = (moveEvent: PointerEvent) => {\n const current = vertical ? moveEvent.clientY : moveEvent.clientX\n let delta = ((current - start) / size) * 100\n\n if (vertical ? !options.topToBottom : direction === 'rtl') delta = -delta\n\n const [low, high] = sliderBounds(schema).map((bound) => sliderPercent(bound, schema)) as [\n number,\n number,\n ]\n const allowed = Math.min(\n high - Math.max(...percents),\n Math.max(low - Math.min(...percents), delta),\n )\n const moved = percents.map((percent) => sliderValueAt(percent + allowed, schema))\n // Snapping each thumb separately could change their distance: keep the first one's.\n options.onChange(\n sliderPrimitive(schema).nonLinear\n ? moved\n : sliderShift(values, moved[0]! - values[0]!, schema),\n )\n }\n\n const end = () => {\n window.removeEventListener('pointermove', move)\n window.removeEventListener('pointerup', end)\n window.removeEventListener('pointercancel', end)\n options.onEnd?.()\n }\n\n window.addEventListener('pointermove', move)\n window.addEventListener('pointerup', end)\n window.addEventListener('pointercancel', end)\n}\n\n/**\n * Whether a pointer press on the slider should be swallowed before the primitive sees it:\n * a press on the track when `tap` (and `snap`) are off. Thumbs always respond.\n */\nexport function sliderBlocksTap(\n target: EventTarget | null,\n schema: Pick<SliderSchema, 'behavior'>,\n): boolean {\n const behavior = sliderBehavior(schema)\n\n if (behavior.has('tap') || behavior.has('snap')) return false\n\n return !(target instanceof Element && target.closest('[role=\"slider\"]'))\n}\n\n// ---------------------------------------------------------------- CodeEditor\n\nexport interface TextEdit {\n value: string\n selectionStart: number\n selectionEnd: number\n}\n\n/**\n * Indent (or with `outdent`, unindent) the lines touched by the selection. A collapsed\n * selection without `outdent` inserts spaces at the caret.\n */\nexport function indentCode(\n value: string,\n selectionStart: number,\n selectionEnd: number,\n tabSize: number,\n outdent = false,\n): TextEdit {\n const indent = ' '.repeat(Math.max(1, tabSize))\n\n if (!outdent && selectionStart === selectionEnd) {\n return {\n value: value.slice(0, selectionStart) + indent + value.slice(selectionEnd),\n selectionStart: selectionStart + indent.length,\n selectionEnd: selectionStart + indent.length,\n }\n }\n\n const lineStart = value.lastIndexOf('\\n', selectionStart - 1) + 1\n const block = value.slice(lineStart, selectionEnd)\n const lines = block.split('\\n')\n let firstDelta = 0\n let totalDelta = 0\n\n const next = lines.map((line, index) => {\n if (outdent) {\n const removable = line.match(new RegExp(`^ {1,${indent.length}}|^\\\\t`))?.[0].length ?? 0\n\n if (index === 0) firstDelta = -removable\n totalDelta -= removable\n\n return line.slice(removable)\n }\n\n if (index === 0) firstDelta = indent.length\n totalDelta += indent.length\n\n return indent + line\n })\n\n return {\n value: value.slice(0, lineStart) + next.join('\\n') + value.slice(selectionEnd),\n selectionStart: Math.max(lineStart, selectionStart + firstDelta),\n selectionEnd: Math.max(lineStart, selectionEnd + totalDelta),\n }\n}\n\n/** Keep the indentation of the current line when Enter is pressed. */\nexport function newlineWithIndent(\n value: string,\n selectionStart: number,\n selectionEnd: number,\n): TextEdit {\n const lineStart = value.lastIndexOf('\\n', selectionStart - 1) + 1\n const indent = value.slice(lineStart, selectionStart).match(/^[ \\t]*/)?.[0] ?? ''\n const insert = `\\n${indent}`\n\n return {\n value: value.slice(0, selectionStart) + insert + value.slice(selectionEnd),\n selectionStart: selectionStart + insert.length,\n selectionEnd: selectionStart + insert.length,\n }\n}\n\n// ---------------------------------------------------------------- ModalTableSelect\n\n/** Query parameters for the picker table, prefixed with the table state identifier. */\nexport function modalTableQuery(identifier: string, query: TableQuery): Record<string, unknown> {\n const params: Record<string, unknown> = {}\n\n for (const [name, value] of Object.entries(query) as [keyof TableQuery, unknown][]) {\n if (value === null || value === undefined || value === '') continue\n\n params[tableQueryKey(identifier, name)] = value\n }\n\n return params\n}\n\n/**\n * The selection after the record `key` was toggled in the picker. Single selects keep\n * one key; multiple selects stop at `maxItems`.\n */\nexport function toggleModalTableKey(\n selected: string[],\n key: string,\n multiple: boolean,\n maxItems?: number,\n): string[] {\n if (selected.includes(key)) {\n return selected.filter((candidate) => candidate !== key)\n }\n\n if (!multiple) return [key]\n\n if (maxItems != null && selected.length >= maxItems) return selected\n\n return [...selected, key]\n}\n\n/**\n * The store options of a picker's filters form. Searchable selects among the filters load\n * their options through the owning form's endpoint, addressed by the ModalTableSelect's\n * `path` (`pickerFilters`), so the server resolves the picker table (and its tenant-scoped\n * relationships) the same way it does for the table itself.\n */\nexport function pickerFilterFormOptions(owner: FormStore, path: string): FormStoreOptions {\n return {\n token: owner.token,\n component: owner.component,\n record: owner.record,\n optionsUrl: owner.optionsUrl,\n params: () => ({\n ...owner.requestContext(),\n pickerFilters: path,\n ownerState: owner.state.values,\n }),\n }\n}\n\n// ---------------------------------------------------------------- FileUpload\n\n/**\n * The stored or temporary paths in a FileUpload state value. The server always holds a\n * list (a single field's is at most one long) and accepts one path too, so a value from\n * either side normalises the same way.\n */\nexport function fileUploadPaths(value: unknown): string[] {\n if (Array.isArray(value)) {\n return value.filter((path): path is string => typeof path === 'string' && path !== '')\n }\n\n return typeof value === 'string' && value !== '' ? [value] : []\n}\n\n/**\n * The file entry of each path: the server's (`component.files`), or the one this session\n * uploaded. A temporary upload comes back from the server without a `url` after a round trip,\n * so the session's entry (with its local image preview) wins over it then (W-S2-02).\n */\nexport function fileUploadEntries<T extends UploadedFile>(\n paths: readonly string[],\n serverFiles: readonly UploadedFile[],\n uploaded: readonly T[],\n): Array<T | UploadedFile> {\n return paths.map((path) => {\n const server = serverFiles.find((file) => file.path === path)\n const local = uploaded.find((file) => file.path === path)\n\n if (local && !server?.url) return { ...server, ...local }\n\n return server ?? local ?? { path, name: path.replace(/^tmp:/, '') }\n })\n}\n\n/** The picker's `accept`: the accepted types plus the extensions of `mimeTypeMap()`. */\nexport function fileUploadAccept(\n schema: Pick<FileUploadSchema, 'acceptedFileTypes' | 'mimeTypeMap'>,\n): string | undefined {\n if (!schema.acceptedFileTypes || schema.acceptedFileTypes.length === 0) return undefined\n\n const mapped = Object.entries(schema.mimeTypeMap ?? {})\n .filter(([, type]) => schema.acceptedFileTypes!.includes(type))\n .map(([extension]) => `.${extension}`)\n\n return [...new Set([...schema.acceptedFileTypes, ...mapped])].join(',')\n}\n\n/** A `'2:1'` / `'16/9'` ratio as a CSS `aspect-ratio` value. */\nexport function cssAspectRatio(ratio: string | null | undefined): string | undefined {\n const [width, height] = (ratio ?? '').split(/[:/]/).map((part) => Number(part.trim()))\n\n return width && height && width > 0 && height > 0 ? `${width} / ${height}` : undefined\n}\n\n/**\n * The field's paths after an upload finished: the new key after the existing files, or\n * before them with `appendFiles(false)`, keeping at most `maxFiles`.\n */\nexport function insertUploadedFile(\n paths: string[],\n key: string,\n schema: Pick<FileUploadSchema, 'appendFiles'>,\n maxFiles: number,\n): string[] {\n return schema.appendFiles === false\n ? [key, ...paths].slice(0, maxFiles)\n : [...paths, key].slice(-maxFiles)\n}\n\n/** Where a file's \"open\" and \"download\" buttons link to. */\nexport function uploadedFileLinks(file: Pick<UploadedFile, 'url' | 'openUrl' | 'downloadUrl'>): {\n open?: string\n download?: string\n} {\n return { open: file.openUrl ?? file.url, download: file.downloadUrl ?? file.url }\n}\n\n/**\n * A queue that runs at most `limit` tasks at once, in the order they were added\n * (`maxParallelUploads()`).\n */\nexport function concurrencyLimiter(limit: number): <T>(task: () => Promise<T>) => Promise<T> {\n const max = Math.max(1, Math.floor(limit) || 1)\n const waiting: Array<() => void> = []\n let running = 0\n\n const release = () => {\n running--\n waiting.shift()?.()\n }\n\n return <T>(task: () => Promise<T>): Promise<T> =>\n new Promise<T>((resolve, reject) => {\n const start = () => {\n running++\n task().then(resolve, reject).finally(release)\n }\n\n if (running < max) start()\n else waiting.push(start)\n })\n}\n\n/** The selected record keys in a ModalTableSelect state value. */\nexport function modalTableSelected(value: unknown, multiple: boolean): string[] {\n if (multiple) return Array.isArray(value) ? value.map(String) : []\n\n return value === null || value === undefined || value === '' ? [] : [String(value)]\n}\n\nexport interface ModalTablePaging {\n page: number\n hasPrevious: boolean\n hasNext: boolean\n from: number\n to: number\n total?: number\n}\n\n/** Page-by-page navigation for the picker table, whatever the table's pagination mode. */\nexport function modalTablePaging(\n pagination: AnyPaginationSchema | null | undefined,\n): ModalTablePaging | null {\n if (!pagination) return null\n\n const page = 'currentPage' in pagination ? pagination.currentPage : 1\n const hasNext =\n 'lastPage' in pagination\n ? page < pagination.lastPage\n : 'currentPage' in pagination && 'hasMorePages' in pagination && pagination.hasMorePages\n\n return {\n page,\n hasPrevious: 'currentPage' in pagination && page > 1,\n hasNext: Boolean(hasNext),\n from: pagination.from,\n to: pagination.to,\n total: 'total' in pagination ? pagination.total : undefined,\n }\n}\n"],"mappings":"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"}
|
package/dist/chart.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chart.d.ts","names":[],"sources":["../src/chart.ts"],"mappings":";;;;;iBAWiB;EACf;EACA,MAAM;EACN,UAAU;EACV;EACA;;EAEA,gBAAgB;;;;;EAKhB;;;;;EAKA,aAAa,eAAe;;;wBAId,mBAAmB;iBAWlB;EACf;EACA,MAAM;EACN,SAAS;;;wBAIK,gBAAgB;;;;;wBAsBhB,kBACd,MAAM,yBACN,UAAU,6CACT;;;;;;;wBAmBa,UAAU,eAAe;;;;;wBAsFzB,iBAAiB,OAAO,mBAAmB;;;;;YAwF/C,qBAAqB;;;;;;
|
|
1
|
+
{"version":3,"file":"chart.d.ts","names":[],"sources":["../src/chart.ts"],"mappings":";;;;;iBAWiB;EACf;EACA,MAAM;EACN,UAAU;EACV;EACA;;EAEA,gBAAgB;;;;;EAKhB;;;;;EAKA,aAAa,eAAe;;;wBAId,mBAAmB;iBAWlB;EACf;EACA,MAAM;EACN,SAAS;;;wBAIK,gBAAgB;;;;;wBAsBhB,kBACd,MAAM,yBACN,UAAU,6CACT;;;;;;;wBAmBa,UAAU,eAAe;;;;;wBAsFzB,iBAAiB,OAAO,mBAAmB;;;;;YAwF/C,qBAAqB;;;;;;wBA8BjB,wBAAwB,YAAY,eAAe;wBAInD,sBAAsB,SAAS,eAAe;;;;;wBAQ9C,qBAAqB;wBAMrB;;wBAKA,gBAAgB;EAC9B,kBAAkB,eAAe;EACjC;;;wBAOc;;;;;;wBAaA,sBACd,gBACA,aAAY,eAAe;;;;;wBAqDb,oBAAoB"}
|
package/dist/chart.js
CHANGED
|
@@ -157,7 +157,7 @@ function buildChartConfig(input) {
|
|
|
157
157
|
return {
|
|
158
158
|
type: input.type,
|
|
159
159
|
data: themeDatasets(input.data, input.type, resolve, input.color),
|
|
160
|
-
options: mergeChartOptions(defaults, resolveChartCallbacks(input.options ?? {}, input.formatters ??
|
|
160
|
+
options: mergeChartOptions(defaults, resolveChartCallbacks(input.options ?? {}, input.formatters ?? registry.formatters))
|
|
161
161
|
};
|
|
162
162
|
}
|
|
163
163
|
/** A unitless number from a CSS custom property (`6`, `1.5`), or undefined. */
|
|
@@ -168,32 +168,40 @@ function chartNumber(value) {
|
|
|
168
168
|
}
|
|
169
169
|
/** `'js:name'`: a reference to a registered chart formatter, never code. */
|
|
170
170
|
const FORMATTER_REFERENCE = /^js:([A-Za-z_$][\w$-]*(?:\.[A-Za-z_$][\w$-]*)*)$/;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
/**
|
|
172
|
+
* The registrations live on `globalThis`, so two copies of `@alfakit/core` share them: under
|
|
173
|
+
* `npm run dev` Vite serves `@alfakit/core` pre-bundled on its own while the renderer package
|
|
174
|
+
* carries another copy, and formatters registered through one must reach charts drawn by the
|
|
175
|
+
* other (W-S3-09).
|
|
176
|
+
*/
|
|
177
|
+
const registry = globalThis.__alfakitCharts ??= {
|
|
178
|
+
formatters: {},
|
|
179
|
+
plugins: [],
|
|
180
|
+
warned: /* @__PURE__ */ new Set()
|
|
181
|
+
};
|
|
174
182
|
/**
|
|
175
183
|
* Register named callbacks for chart options: `ChartWidget::getOptions()` refers to them
|
|
176
184
|
* as `'js:name'` strings (`['scales' => ['y' => ['ticks' => ['callback' => 'js:money']]]]`).
|
|
177
185
|
* Strings are only ever looked up here, never evaluated.
|
|
178
186
|
*/
|
|
179
187
|
function registerChartFormatters(formatters) {
|
|
180
|
-
|
|
181
|
-
...
|
|
188
|
+
registry.formatters = {
|
|
189
|
+
...registry.formatters,
|
|
182
190
|
...formatters
|
|
183
191
|
};
|
|
184
192
|
}
|
|
185
193
|
function getChartFormatters() {
|
|
186
|
-
return
|
|
194
|
+
return registry.formatters;
|
|
187
195
|
}
|
|
188
196
|
/**
|
|
189
197
|
* Chart.js plugins for every chart (`chartjs-plugin-datalabels`…): each renderer passes
|
|
190
198
|
* them to `Chart.register()` before it draws a chart.
|
|
191
199
|
*/
|
|
192
200
|
function registerChartPlugins(plugins) {
|
|
193
|
-
for (const plugin of plugins) if (plugin && !
|
|
201
|
+
for (const plugin of plugins) if (plugin && !registry.plugins.includes(plugin)) registry.plugins.push(plugin);
|
|
194
202
|
}
|
|
195
203
|
function getChartPlugins() {
|
|
196
|
-
return
|
|
204
|
+
return registry.plugins;
|
|
197
205
|
}
|
|
198
206
|
/** `createAlfakit({ chartFormatters, chartPlugins })`. */
|
|
199
207
|
function configureCharts(options) {
|
|
@@ -202,9 +210,9 @@ function configureCharts(options) {
|
|
|
202
210
|
}
|
|
203
211
|
/** Forget registered formatters and plugins (tests). */
|
|
204
212
|
function resetChartRegistrations() {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
213
|
+
registry.formatters = {};
|
|
214
|
+
registry.plugins.length = 0;
|
|
215
|
+
registry.warned.clear();
|
|
208
216
|
}
|
|
209
217
|
const MISSING = Symbol("missing");
|
|
210
218
|
/**
|
|
@@ -212,7 +220,7 @@ const MISSING = Symbol("missing");
|
|
|
212
220
|
* that name. An unknown name is left out (Chart.js then uses its default) with a console
|
|
213
221
|
* warning, once per name; other values are copied unchanged.
|
|
214
222
|
*/
|
|
215
|
-
function resolveChartCallbacks(value, formatters =
|
|
223
|
+
function resolveChartCallbacks(value, formatters = registry.formatters) {
|
|
216
224
|
const resolved = resolveCallback(value, formatters);
|
|
217
225
|
return resolved === MISSING ? void 0 : resolved;
|
|
218
226
|
}
|
|
@@ -223,8 +231,8 @@ function resolveCallback(value, formatters) {
|
|
|
223
231
|
const name = match[1];
|
|
224
232
|
const formatter = Object.prototype.hasOwnProperty.call(formatters, name) ? formatters[name] : void 0;
|
|
225
233
|
if (typeof formatter === "function") return formatter;
|
|
226
|
-
if (!
|
|
227
|
-
|
|
234
|
+
if (!registry.warned.has(name)) {
|
|
235
|
+
registry.warned.add(name);
|
|
228
236
|
console.warn(`Alfakit: chart formatter "${name}" is not registered; pass it to createAlfakit({ chartFormatters }).`);
|
|
229
237
|
}
|
|
230
238
|
return MISSING;
|
package/dist/chart.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chart.js","names":[],"sources":["../src/chart.ts"],"sourcesContent":["/**\n * Framework-agnostic Chart.js configuration shared by every renderer's chart widget.\n * Nothing here imports Chart.js: renderers pass the result to their own `Chart` instance.\n */\n\nconst PALETTE = ['--chart-1', '--chart-2', '--chart-3', '--chart-4', '--chart-5']\n\nconst CIRCULAR = ['pie', 'doughnut', 'polarArea']\n\nconst WITHOUT_CARTESIAN_SCALES = ['pie', 'doughnut', 'polarArea', 'radar']\n\nexport interface ChartConfigInput {\n type: string\n data: Record<string, unknown>\n options?: Record<string, unknown> | null\n rtl: boolean\n locale: string\n /** Resolve a CSS custom property name (`--chart-1`) to a colour Chart.js can paint. */\n resolveColor?: (name: string) => string\n /**\n * `ChartWidget::$color`: the theme colour of datasets without colours of their own\n * (pie-like charts keep one palette colour per slice).\n */\n color?: string | null\n /**\n * Named callbacks that `'js:name'` strings in `options` resolve to. Defaults to the ones\n * registered with `createAlfakit({ chartFormatters })` / `registerChartFormatters()`.\n */\n formatters?: Record<string, ChartFormatter>\n}\n\n/** The CSS custom property of a theme colour name (`danger` → `--destructive`). */\nexport function chartColorVariable(color: string): string {\n switch (color) {\n case 'danger':\n return '--destructive'\n case 'gray':\n return '--muted-foreground'\n default:\n return `--${color}`\n }\n}\n\nexport interface ChartConfigOutput {\n type: string\n data: Record<string, unknown>\n options: Record<string, unknown>\n}\n\n/** Reads a CSS custom property from `<html>`, so the current theme and dark mode apply. */\nexport function resolveCssColor(name: string): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return `var(${name})`\n }\n\n return getComputedStyle(document.documentElement).getPropertyValue(name).trim() || `var(${name})`\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n return false\n }\n\n const prototype = Object.getPrototypeOf(value) as unknown\n\n return prototype === Object.prototype || prototype === null\n}\n\n/**\n * Deep-merges Chart.js options: nested plain objects (`plugins.legend`, `scales.y.ticks`)\n * are merged key by key; arrays, functions and scalars from `override` replace the base.\n */\nexport function mergeChartOptions(\n base: Record<string, unknown>,\n override: Record<string, unknown> | null | undefined,\n): Record<string, unknown> {\n const result: Record<string, unknown> = { ...base }\n\n for (const [key, value] of Object.entries(override ?? {})) {\n const current = result[key]\n\n result[key] =\n isPlainObject(current) && isPlainObject(value) ? mergeChartOptions(current, value) : value\n }\n\n return result\n}\n\n/**\n * A translucent version of a resolved theme colour for line fills. Themes declare chart\n * colours in any CSS syntax (`#rrggbb`, `rgb()`, `hsl()`, `oklch()`, `var()`), so the alpha\n * is applied in that syntax rather than by appending hex digits, which would hand Chart.js\n * an invalid colour (painted black).\n */\nexport function withAlpha(color: string, alpha: number): string {\n const value = color.trim()\n const hex = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(value)\n\n if (hex) {\n const digits = hex[1]!\n const rgb =\n digits.length <= 4\n ? digits\n .slice(0, 3)\n .split('')\n .map((d) => d + d)\n .join('')\n : digits.slice(0, 6)\n const a = Math.round(Math.min(1, Math.max(0, alpha)) * 255)\n .toString(16)\n .padStart(2, '0')\n\n return `#${rgb}${a}`\n }\n\n const fn = /^([a-z-]+)\\((.*)\\)$/i.exec(value)\n\n if (fn) {\n const name = fn[1]!\n const args = fn[2]!\n\n if (name.toLowerCase() === 'var') {\n return `color-mix(in srgb, ${value} ${Math.round(alpha * 100)}%, transparent)`\n }\n\n // Legacy comma syntax: rgb(a)/hsl(a) with commas takes the alpha as a fourth argument.\n if (args.includes(',')) {\n const parts = args.split(',').map((part) => part.trim())\n\n return `${name}(${parts.slice(0, 3).join(', ')}, ${alpha})`\n }\n\n // Modern space syntax: drop an existing `/ alpha` and append ours.\n const channels = args.replace(/\\s*\\/.*$/, '').trim()\n\n return `${name}(${channels} / ${alpha})`\n }\n\n return value\n}\n\n/** Apply theme colours to datasets that don't specify their own. */\nfunction themeDatasets(\n data: Record<string, unknown>,\n type: string,\n resolve: (name: string) => string,\n fixed?: string | null,\n): Record<string, unknown> {\n const datasets = Array.isArray(data.datasets)\n ? (data.datasets as Array<Record<string, unknown>>)\n : []\n const circular = CIRCULAR.includes(type)\n const palette = (index: number) => resolve(PALETTE[index % PALETTE.length] ?? '--chart-1')\n const color = (index: number) =>\n fixed && !circular ? resolve(chartColorVariable(fixed)) : palette(index)\n\n return {\n ...data,\n datasets: datasets.map((dataset, index) => {\n const base = color(index)\n const values = Array.isArray(dataset.data) ? (dataset.data as unknown[]) : []\n const many = circular ? values.map((_, i) => palette(i)) : undefined\n\n return {\n borderColor: many ?? base,\n backgroundColor: many ?? (type === 'line' ? withAlpha(base, 0.2) : base),\n borderWidth: type === 'line' ? 2 : 1,\n tension: 0.3,\n fill: type === 'line',\n pointRadius: 2,\n ...dataset,\n }\n }),\n }\n}\n\n/**\n * Builds the Chart.js `type`, `data` and `options` for a chart widget: theme colours,\n * reading direction and locale defaults, deep-merged with the widget's own `options`.\n */\nexport function buildChartConfig(input: ChartConfigInput): ChartConfigOutput {\n const resolve = input.resolveColor ?? resolveCssColor\n // Chart.js paints legends, tooltips and axes on the canvas, so themes style them through\n // these custom properties (defaults in `themes.css`); without them the base tokens apply.\n const themed = (name: string, fallback: string): string => {\n const value = resolve(name).trim()\n\n return value === '' || value.startsWith('var(') ? fallback : value\n }\n const textColor = themed('--chart-text', resolve('--muted-foreground'))\n const gridColor = themed('--chart-grid', resolve('--border'))\n const textDirection = input.rtl ? 'rtl' : 'ltr'\n const tooltip: Record<string, unknown> = { rtl: input.rtl, textDirection }\n\n for (const [option, name] of [\n ['backgroundColor', '--chart-tooltip-bg'],\n ['titleColor', '--chart-tooltip-foreground'],\n ['bodyColor', '--chart-tooltip-foreground'],\n ['footerColor', '--chart-tooltip-foreground'],\n ['borderColor', '--chart-tooltip-border'],\n ] as const) {\n const value = themed(name, '')\n\n if (value !== '') tooltip[option] = value\n }\n\n for (const [option, name] of [\n ['borderWidth', '--chart-tooltip-border-width'],\n ['cornerRadius', '--chart-tooltip-radius'],\n ] as const) {\n const value = chartNumber(themed(name, ''))\n\n if (value !== undefined) tooltip[option] = value\n }\n\n const defaults: Record<string, unknown> = {\n responsive: true,\n maintainAspectRatio: false,\n locale: input.locale,\n plugins: {\n legend: {\n rtl: input.rtl,\n textDirection,\n labels: { color: themed('--chart-legend-foreground', textColor) },\n },\n tooltip,\n },\n }\n\n if (!WITHOUT_CARTESIAN_SCALES.includes(input.type)) {\n defaults.scales = {\n x: { reverse: input.rtl, ticks: { color: textColor }, grid: { color: gridColor } },\n y: {\n position: input.rtl ? 'right' : 'left',\n ticks: { color: textColor },\n grid: { color: gridColor },\n },\n }\n }\n\n return {\n type: input.type,\n data: themeDatasets(input.data, input.type, resolve, input.color),\n options: mergeChartOptions(\n defaults,\n resolveChartCallbacks(input.options ?? {}, input.formatters ?? chartFormatters) as Record<\n string,\n unknown\n >,\n ),\n }\n}\n\n/** A unitless number from a CSS custom property (`6`, `1.5`), or undefined. */\nfunction chartNumber(value: string): number | undefined {\n if (value === '') return undefined\n\n const number = Number(value)\n\n return Number.isFinite(number) ? number : undefined\n}\n\n// ---------------------------------------------------------------- Formatters & plugins\n\n/**\n * A callback Chart.js calls (tick `callback`, tooltip `label`…), registered by name. Any\n * function signature fits: `(value: number) => string`, `(context: TooltipItem) => string`.\n */\nexport type ChartFormatter = (...args: never[]) => unknown\n\n/** `'js:name'`: a reference to a registered chart formatter, never code. */\nconst FORMATTER_REFERENCE = /^js:([A-Za-z_$][\\w$-]*(?:\\.[A-Za-z_$][\\w$-]*)*)$/\n\nlet chartFormatters: Record<string, ChartFormatter> = {}\nlet chartPlugins: unknown[] = []\nconst warnedFormatters = new Set<string>()\n\n/**\n * Register named callbacks for chart options: `ChartWidget::getOptions()` refers to them\n * as `'js:name'` strings (`['scales' => ['y' => ['ticks' => ['callback' => 'js:money']]]]`).\n * Strings are only ever looked up here, never evaluated.\n */\nexport function registerChartFormatters(formatters: Record<string, ChartFormatter>): void {\n chartFormatters = { ...chartFormatters, ...formatters }\n}\n\nexport function getChartFormatters(): Readonly<Record<string, ChartFormatter>> {\n return chartFormatters\n}\n\n/**\n * Chart.js plugins for every chart (`chartjs-plugin-datalabels`…): each renderer passes\n * them to `Chart.register()` before it draws a chart.\n */\nexport function registerChartPlugins(plugins: readonly unknown[]): void {\n for (const plugin of plugins) {\n if (plugin && !chartPlugins.includes(plugin)) chartPlugins.push(plugin)\n }\n}\n\nexport function getChartPlugins(): readonly unknown[] {\n return chartPlugins\n}\n\n/** `createAlfakit({ chartFormatters, chartPlugins })`. */\nexport function configureCharts(options: {\n chartFormatters?: Record<string, ChartFormatter>\n chartPlugins?: readonly unknown[]\n}): void {\n if (options.chartFormatters) registerChartFormatters(options.chartFormatters)\n if (options.chartPlugins) registerChartPlugins(options.chartPlugins)\n}\n\n/** Forget registered formatters and plugins (tests). */\nexport function resetChartRegistrations(): void {\n chartFormatters = {}\n chartPlugins = []\n warnedFormatters.clear()\n}\n\nconst MISSING = Symbol('missing')\n\n/**\n * Replace `'js:name'` strings anywhere in chart options with the registered formatter of\n * that name. An unknown name is left out (Chart.js then uses its default) with a console\n * warning, once per name; other values are copied unchanged.\n */\nexport function resolveChartCallbacks(\n value: unknown,\n formatters: Record<string, ChartFormatter> = chartFormatters,\n): unknown {\n const resolved = resolveCallback(value, formatters)\n\n return resolved === MISSING ? undefined : resolved\n}\n\nfunction resolveCallback(value: unknown, formatters: Record<string, ChartFormatter>): unknown {\n if (typeof value === 'string') {\n const match = FORMATTER_REFERENCE.exec(value)\n\n if (!match) return value\n\n const name = match[1]!\n const formatter = Object.prototype.hasOwnProperty.call(formatters, name)\n ? formatters[name]\n : undefined\n\n if (typeof formatter === 'function') return formatter\n\n if (!warnedFormatters.has(name)) {\n warnedFormatters.add(name)\n console.warn(\n `Alfakit: chart formatter \"${name}\" is not registered; pass it to createAlfakit({ chartFormatters }).`,\n )\n }\n\n return MISSING\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => resolveCallback(item, formatters)).filter((item) => item !== MISSING)\n }\n\n if (isPlainObject(value)) {\n const result: Record<string, unknown> = {}\n\n for (const [key, item] of Object.entries(value)) {\n const resolved = resolveCallback(item, formatters)\n\n if (resolved !== MISSING) result[key] = resolved\n }\n\n return result\n }\n\n return value\n}\n\n/**\n * Calls `callback` when the theme may have changed: dark mode, theme attributes or inline\n * CSS variables on `<html>`, or the OS colour scheme. Returns an unsubscribe function.\n */\nexport function observeThemeChanges(callback: () => void): () => void {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return () => {}\n }\n\n let frame: number | null = null\n const schedule = () => {\n if (frame !== null) return\n\n frame = window.requestAnimationFrame(() => {\n frame = null\n callback()\n })\n }\n\n const observer =\n typeof MutationObserver === 'undefined' ? null : new MutationObserver(() => schedule())\n\n observer?.observe(document.documentElement, { attributes: true })\n\n const media =\n typeof window.matchMedia === 'function'\n ? window.matchMedia('(prefers-color-scheme: dark)')\n : null\n\n media?.addEventListener('change', schedule)\n\n return () => {\n observer?.disconnect()\n media?.removeEventListener('change', schedule)\n\n if (frame !== null) window.cancelAnimationFrame(frame)\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chart.js","names":[],"sources":["../src/chart.ts"],"sourcesContent":["/**\n * Framework-agnostic Chart.js configuration shared by every renderer's chart widget.\n * Nothing here imports Chart.js: renderers pass the result to their own `Chart` instance.\n */\n\nconst PALETTE = ['--chart-1', '--chart-2', '--chart-3', '--chart-4', '--chart-5']\n\nconst CIRCULAR = ['pie', 'doughnut', 'polarArea']\n\nconst WITHOUT_CARTESIAN_SCALES = ['pie', 'doughnut', 'polarArea', 'radar']\n\nexport interface ChartConfigInput {\n type: string\n data: Record<string, unknown>\n options?: Record<string, unknown> | null\n rtl: boolean\n locale: string\n /** Resolve a CSS custom property name (`--chart-1`) to a colour Chart.js can paint. */\n resolveColor?: (name: string) => string\n /**\n * `ChartWidget::$color`: the theme colour of datasets without colours of their own\n * (pie-like charts keep one palette colour per slice).\n */\n color?: string | null\n /**\n * Named callbacks that `'js:name'` strings in `options` resolve to. Defaults to the ones\n * registered with `createAlfakit({ chartFormatters })` / `registerChartFormatters()`.\n */\n formatters?: Record<string, ChartFormatter>\n}\n\n/** The CSS custom property of a theme colour name (`danger` → `--destructive`). */\nexport function chartColorVariable(color: string): string {\n switch (color) {\n case 'danger':\n return '--destructive'\n case 'gray':\n return '--muted-foreground'\n default:\n return `--${color}`\n }\n}\n\nexport interface ChartConfigOutput {\n type: string\n data: Record<string, unknown>\n options: Record<string, unknown>\n}\n\n/** Reads a CSS custom property from `<html>`, so the current theme and dark mode apply. */\nexport function resolveCssColor(name: string): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return `var(${name})`\n }\n\n return getComputedStyle(document.documentElement).getPropertyValue(name).trim() || `var(${name})`\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n return false\n }\n\n const prototype = Object.getPrototypeOf(value) as unknown\n\n return prototype === Object.prototype || prototype === null\n}\n\n/**\n * Deep-merges Chart.js options: nested plain objects (`plugins.legend`, `scales.y.ticks`)\n * are merged key by key; arrays, functions and scalars from `override` replace the base.\n */\nexport function mergeChartOptions(\n base: Record<string, unknown>,\n override: Record<string, unknown> | null | undefined,\n): Record<string, unknown> {\n const result: Record<string, unknown> = { ...base }\n\n for (const [key, value] of Object.entries(override ?? {})) {\n const current = result[key]\n\n result[key] =\n isPlainObject(current) && isPlainObject(value) ? mergeChartOptions(current, value) : value\n }\n\n return result\n}\n\n/**\n * A translucent version of a resolved theme colour for line fills. Themes declare chart\n * colours in any CSS syntax (`#rrggbb`, `rgb()`, `hsl()`, `oklch()`, `var()`), so the alpha\n * is applied in that syntax rather than by appending hex digits, which would hand Chart.js\n * an invalid colour (painted black).\n */\nexport function withAlpha(color: string, alpha: number): string {\n const value = color.trim()\n const hex = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(value)\n\n if (hex) {\n const digits = hex[1]!\n const rgb =\n digits.length <= 4\n ? digits\n .slice(0, 3)\n .split('')\n .map((d) => d + d)\n .join('')\n : digits.slice(0, 6)\n const a = Math.round(Math.min(1, Math.max(0, alpha)) * 255)\n .toString(16)\n .padStart(2, '0')\n\n return `#${rgb}${a}`\n }\n\n const fn = /^([a-z-]+)\\((.*)\\)$/i.exec(value)\n\n if (fn) {\n const name = fn[1]!\n const args = fn[2]!\n\n if (name.toLowerCase() === 'var') {\n return `color-mix(in srgb, ${value} ${Math.round(alpha * 100)}%, transparent)`\n }\n\n // Legacy comma syntax: rgb(a)/hsl(a) with commas takes the alpha as a fourth argument.\n if (args.includes(',')) {\n const parts = args.split(',').map((part) => part.trim())\n\n return `${name}(${parts.slice(0, 3).join(', ')}, ${alpha})`\n }\n\n // Modern space syntax: drop an existing `/ alpha` and append ours.\n const channels = args.replace(/\\s*\\/.*$/, '').trim()\n\n return `${name}(${channels} / ${alpha})`\n }\n\n return value\n}\n\n/** Apply theme colours to datasets that don't specify their own. */\nfunction themeDatasets(\n data: Record<string, unknown>,\n type: string,\n resolve: (name: string) => string,\n fixed?: string | null,\n): Record<string, unknown> {\n const datasets = Array.isArray(data.datasets)\n ? (data.datasets as Array<Record<string, unknown>>)\n : []\n const circular = CIRCULAR.includes(type)\n const palette = (index: number) => resolve(PALETTE[index % PALETTE.length] ?? '--chart-1')\n const color = (index: number) =>\n fixed && !circular ? resolve(chartColorVariable(fixed)) : palette(index)\n\n return {\n ...data,\n datasets: datasets.map((dataset, index) => {\n const base = color(index)\n const values = Array.isArray(dataset.data) ? (dataset.data as unknown[]) : []\n const many = circular ? values.map((_, i) => palette(i)) : undefined\n\n return {\n borderColor: many ?? base,\n backgroundColor: many ?? (type === 'line' ? withAlpha(base, 0.2) : base),\n borderWidth: type === 'line' ? 2 : 1,\n tension: 0.3,\n fill: type === 'line',\n pointRadius: 2,\n ...dataset,\n }\n }),\n }\n}\n\n/**\n * Builds the Chart.js `type`, `data` and `options` for a chart widget: theme colours,\n * reading direction and locale defaults, deep-merged with the widget's own `options`.\n */\nexport function buildChartConfig(input: ChartConfigInput): ChartConfigOutput {\n const resolve = input.resolveColor ?? resolveCssColor\n // Chart.js paints legends, tooltips and axes on the canvas, so themes style them through\n // these custom properties (defaults in `themes.css`); without them the base tokens apply.\n const themed = (name: string, fallback: string): string => {\n const value = resolve(name).trim()\n\n return value === '' || value.startsWith('var(') ? fallback : value\n }\n const textColor = themed('--chart-text', resolve('--muted-foreground'))\n const gridColor = themed('--chart-grid', resolve('--border'))\n const textDirection = input.rtl ? 'rtl' : 'ltr'\n const tooltip: Record<string, unknown> = { rtl: input.rtl, textDirection }\n\n for (const [option, name] of [\n ['backgroundColor', '--chart-tooltip-bg'],\n ['titleColor', '--chart-tooltip-foreground'],\n ['bodyColor', '--chart-tooltip-foreground'],\n ['footerColor', '--chart-tooltip-foreground'],\n ['borderColor', '--chart-tooltip-border'],\n ] as const) {\n const value = themed(name, '')\n\n if (value !== '') tooltip[option] = value\n }\n\n for (const [option, name] of [\n ['borderWidth', '--chart-tooltip-border-width'],\n ['cornerRadius', '--chart-tooltip-radius'],\n ] as const) {\n const value = chartNumber(themed(name, ''))\n\n if (value !== undefined) tooltip[option] = value\n }\n\n const defaults: Record<string, unknown> = {\n responsive: true,\n maintainAspectRatio: false,\n locale: input.locale,\n plugins: {\n legend: {\n rtl: input.rtl,\n textDirection,\n labels: { color: themed('--chart-legend-foreground', textColor) },\n },\n tooltip,\n },\n }\n\n if (!WITHOUT_CARTESIAN_SCALES.includes(input.type)) {\n defaults.scales = {\n x: { reverse: input.rtl, ticks: { color: textColor }, grid: { color: gridColor } },\n y: {\n position: input.rtl ? 'right' : 'left',\n ticks: { color: textColor },\n grid: { color: gridColor },\n },\n }\n }\n\n return {\n type: input.type,\n data: themeDatasets(input.data, input.type, resolve, input.color),\n options: mergeChartOptions(\n defaults,\n resolveChartCallbacks(input.options ?? {}, input.formatters ?? registry.formatters) as Record<\n string,\n unknown\n >,\n ),\n }\n}\n\n/** A unitless number from a CSS custom property (`6`, `1.5`), or undefined. */\nfunction chartNumber(value: string): number | undefined {\n if (value === '') return undefined\n\n const number = Number(value)\n\n return Number.isFinite(number) ? number : undefined\n}\n\n// ---------------------------------------------------------------- Formatters & plugins\n\n/**\n * A callback Chart.js calls (tick `callback`, tooltip `label`…), registered by name. Any\n * function signature fits: `(value: number) => string`, `(context: TooltipItem) => string`.\n */\nexport type ChartFormatter = (...args: never[]) => unknown\n\n/** `'js:name'`: a reference to a registered chart formatter, never code. */\nconst FORMATTER_REFERENCE = /^js:([A-Za-z_$][\\w$-]*(?:\\.[A-Za-z_$][\\w$-]*)*)$/\n\ninterface ChartRegistry {\n formatters: Record<string, ChartFormatter>\n plugins: unknown[]\n warned: Set<string>\n}\n\n/**\n * The registrations live on `globalThis`, so two copies of `@alfakit/core` share them: under\n * `npm run dev` Vite serves `@alfakit/core` pre-bundled on its own while the renderer package\n * carries another copy, and formatters registered through one must reach charts drawn by the\n * other (W-S3-09).\n */\nconst registry: ChartRegistry = ((\n globalThis as { __alfakitCharts?: ChartRegistry }\n).__alfakitCharts ??= {\n formatters: {},\n plugins: [],\n warned: new Set<string>(),\n})\n\n/**\n * Register named callbacks for chart options: `ChartWidget::getOptions()` refers to them\n * as `'js:name'` strings (`['scales' => ['y' => ['ticks' => ['callback' => 'js:money']]]]`).\n * Strings are only ever looked up here, never evaluated.\n */\nexport function registerChartFormatters(formatters: Record<string, ChartFormatter>): void {\n registry.formatters = { ...registry.formatters, ...formatters }\n}\n\nexport function getChartFormatters(): Readonly<Record<string, ChartFormatter>> {\n return registry.formatters\n}\n\n/**\n * Chart.js plugins for every chart (`chartjs-plugin-datalabels`…): each renderer passes\n * them to `Chart.register()` before it draws a chart.\n */\nexport function registerChartPlugins(plugins: readonly unknown[]): void {\n for (const plugin of plugins) {\n if (plugin && !registry.plugins.includes(plugin)) registry.plugins.push(plugin)\n }\n}\n\nexport function getChartPlugins(): readonly unknown[] {\n return registry.plugins\n}\n\n/** `createAlfakit({ chartFormatters, chartPlugins })`. */\nexport function configureCharts(options: {\n chartFormatters?: Record<string, ChartFormatter>\n chartPlugins?: readonly unknown[]\n}): void {\n if (options.chartFormatters) registerChartFormatters(options.chartFormatters)\n if (options.chartPlugins) registerChartPlugins(options.chartPlugins)\n}\n\n/** Forget registered formatters and plugins (tests). */\nexport function resetChartRegistrations(): void {\n registry.formatters = {}\n registry.plugins.length = 0\n registry.warned.clear()\n}\n\nconst MISSING = Symbol('missing')\n\n/**\n * Replace `'js:name'` strings anywhere in chart options with the registered formatter of\n * that name. An unknown name is left out (Chart.js then uses its default) with a console\n * warning, once per name; other values are copied unchanged.\n */\nexport function resolveChartCallbacks(\n value: unknown,\n formatters: Record<string, ChartFormatter> = registry.formatters,\n): unknown {\n const resolved = resolveCallback(value, formatters)\n\n return resolved === MISSING ? undefined : resolved\n}\n\nfunction resolveCallback(value: unknown, formatters: Record<string, ChartFormatter>): unknown {\n if (typeof value === 'string') {\n const match = FORMATTER_REFERENCE.exec(value)\n\n if (!match) return value\n\n const name = match[1]!\n const formatter = Object.prototype.hasOwnProperty.call(formatters, name)\n ? formatters[name]\n : undefined\n\n if (typeof formatter === 'function') return formatter\n\n if (!registry.warned.has(name)) {\n registry.warned.add(name)\n console.warn(\n `Alfakit: chart formatter \"${name}\" is not registered; pass it to createAlfakit({ chartFormatters }).`,\n )\n }\n\n return MISSING\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => resolveCallback(item, formatters)).filter((item) => item !== MISSING)\n }\n\n if (isPlainObject(value)) {\n const result: Record<string, unknown> = {}\n\n for (const [key, item] of Object.entries(value)) {\n const resolved = resolveCallback(item, formatters)\n\n if (resolved !== MISSING) result[key] = resolved\n }\n\n return result\n }\n\n return value\n}\n\n/**\n * Calls `callback` when the theme may have changed: dark mode, theme attributes or inline\n * CSS variables on `<html>`, or the OS colour scheme. Returns an unsubscribe function.\n */\nexport function observeThemeChanges(callback: () => void): () => void {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return () => {}\n }\n\n let frame: number | null = null\n const schedule = () => {\n if (frame !== null) return\n\n frame = window.requestAnimationFrame(() => {\n frame = null\n callback()\n })\n }\n\n const observer =\n typeof MutationObserver === 'undefined' ? null : new MutationObserver(() => schedule())\n\n observer?.observe(document.documentElement, { attributes: true })\n\n const media =\n typeof window.matchMedia === 'function'\n ? window.matchMedia('(prefers-color-scheme: dark)')\n : null\n\n media?.addEventListener('change', schedule)\n\n return () => {\n observer?.disconnect()\n media?.removeEventListener('change', schedule)\n\n if (frame !== null) window.cancelAnimationFrame(frame)\n }\n}\n"],"mappings":"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"}
|
package/dist/conflicts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conflicts.d.ts","names":[],"sources":["../src/conflicts.ts"],"mappings":";;;;;;;;qBASa;;qBAGA;;wBAUG,iBAAiB;;wBAKjB;;wBAKA,oBAAoB;;wBAKpB;;wBAKA,eAAe;;wBASf,qBAAqB,OAAO;wBAQ5B,mBAAmB;;wBAKnB,kBACd,MAAM,KAAK,4CACV;;
|
|
1
|
+
{"version":3,"file":"conflicts.d.ts","names":[],"sources":["../src/conflicts.ts"],"mappings":";;;;;;;;qBASa;;qBAGA;;wBAUG,iBAAiB;;wBAKjB;;wBAKA,oBAAoB;;wBAKpB;;wBAKA,eAAe;;wBASf,qBAAqB,OAAO;wBAQ5B,mBAAmB;;wBAKnB,kBACd,MAAM,KAAK,4CACV;;iBA8Bc;EACf;EACA;;EAEA;;EAEA;;EAEA;;EAEA;;iBAGe;;EAEf;EACA,OAAO;;EAEP,IAAI;;EAEJ;;EAEA;;iBAGe;EACf,UAAU;;EAEV,QAAQ;EACR;;EAEA;;iBAGe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA,WAAW;;EAEX,OAAO;;EAEP,eAAe,IAAI;EACnB,OAAO;EACP;;;;;;;qBAQW;mBAWkB;WAVpB,OAAO,MAAM;UAOd;UACA;EAEqB,YAAA,SAAS;;EAKtC,SAAS;MAIL,SAAS;EAIb;;EAoBA,SAAS,QAAQ;;EAiBX,UAAU,QAAQ;;EA8BlB,YAAY;;EASlB,UAAU;;EAYV;;EAiBA;UAIQ;UAeA;UAUA;;;;;;wBASM,gBAAgB,UAAU;EAAiB;EAAa"}
|
package/dist/conflicts.js
CHANGED
|
@@ -50,11 +50,9 @@ function findPageFormBlock(page) {
|
|
|
50
50
|
if (!block || typeof block !== "object") continue;
|
|
51
51
|
const candidate = block;
|
|
52
52
|
if (candidate.type === "Form" && candidate.component === "form") return candidate;
|
|
53
|
-
for (const value of Object.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
} else if (value && typeof value === "object" && value.type === "Form") {
|
|
57
|
-
const found = search([value]);
|
|
53
|
+
for (const [key, value] of Object.entries(candidate)) {
|
|
54
|
+
if (key === "state" || !value || typeof value !== "object") continue;
|
|
55
|
+
const found = search(Array.isArray(value) ? value : [value]);
|
|
58
56
|
if (found) return found;
|
|
59
57
|
}
|
|
60
58
|
}
|