@cocoar/vue-ui 3.1.0 → 3.2.0-beta.11
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/bin/cli.mjs +53 -0
- package/dist/components/checkbox/CoarCheckbox.vue.d.ts +1 -1
- package/dist/components/checkbox/CoarCheckbox.vue.d.ts.map +1 -1
- package/dist/components/code-block/CoarCodeBlock.vue.d.ts +1 -1
- package/dist/components/code-block/CoarCodeBlock.vue.d.ts.map +1 -1
- package/dist/components/data-list/CoarDataList.vue.d.ts +158 -0
- package/dist/components/data-list/CoarDataList.vue.d.ts.map +1 -0
- package/dist/components/data-list/CoarDataListToolbar.vue.d.ts +30 -0
- package/dist/components/data-list/CoarDataListToolbar.vue.d.ts.map +1 -0
- package/dist/components/data-list/data-list-builder.d.ts +288 -0
- package/dist/components/data-list/data-list-builder.d.ts.map +1 -0
- package/dist/components/data-list/index.d.ts +12 -0
- package/dist/components/data-list/index.d.ts.map +1 -0
- package/dist/components/data-list/internal/compare.d.ts +14 -0
- package/dist/components/data-list/internal/compare.d.ts.map +1 -0
- package/dist/components/data-list/internal/reorder-core.d.ts +46 -0
- package/dist/components/data-list/internal/reorder-core.d.ts.map +1 -0
- package/dist/components/data-list/internal/search.d.ts +16 -0
- package/dist/components/data-list/internal/search.d.ts.map +1 -0
- package/dist/components/data-list/internal/useDataListLines.d.ts +92 -0
- package/dist/components/data-list/internal/useDataListLines.d.ts.map +1 -0
- package/dist/components/data-list/internal/useDataListReorder.d.ts +68 -0
- package/dist/components/data-list/internal/useDataListReorder.d.ts.map +1 -0
- package/dist/components/data-list/internal/useSearchHighlight.d.ts +19 -0
- package/dist/components/data-list/internal/useSearchHighlight.d.ts.map +1 -0
- package/dist/components/data-list/types.d.ts +185 -0
- package/dist/components/data-list/types.d.ts.map +1 -0
- package/dist/components/data-list/useDataListModel.d.ts +79 -0
- package/dist/components/data-list/useDataListModel.d.ts.map +1 -0
- package/dist/components/date-time/_shared/maskito-config.d.ts +7 -2
- package/dist/components/date-time/_shared/maskito-config.d.ts.map +1 -1
- package/dist/components/date-time/_shared/time-helpers.d.ts +11 -4
- package/dist/components/date-time/_shared/time-helpers.d.ts.map +1 -1
- package/dist/components/date-time/_shared/use-date-picker-base.d.ts +1 -1
- package/dist/components/date-time/plain-date-picker/CoarPlainDatePicker.vue.d.ts +1 -1
- package/dist/components/date-time/plain-date-picker/CoarPlainDatePicker.vue.d.ts.map +1 -1
- package/dist/components/date-time/plain-date-time-picker/CoarPlainDateTimePicker.vue.d.ts +1 -1
- package/dist/components/date-time/plain-date-time-picker/CoarPlainDateTimePicker.vue.d.ts.map +1 -1
- package/dist/components/date-time/zoned-date-time-picker/CoarZonedDateTimePicker.vue.d.ts +1 -1
- package/dist/components/date-time/zoned-date-time-picker/CoarZonedDateTimePicker.vue.d.ts.map +1 -1
- package/dist/components/dual-listbox/CoarDualListbox.vue.d.ts +3 -0
- package/dist/components/dual-listbox/CoarDualListbox.vue.d.ts.map +1 -1
- package/dist/components/form-field/CoarFormField.vue.d.ts +1 -1
- package/dist/components/form-field/CoarFormField.vue.d.ts.map +1 -1
- package/dist/components/icon/CoarIcon.vue.d.ts +2 -2
- package/dist/components/icon/CoarIcon.vue.d.ts.map +1 -1
- package/dist/components/listbox/CoarListbox.vue.d.ts +7 -0
- package/dist/components/listbox/CoarListbox.vue.d.ts.map +1 -1
- package/dist/components/number-input/CoarNumberInput.vue.d.ts +1 -1
- package/dist/components/number-input/CoarNumberInput.vue.d.ts.map +1 -1
- package/dist/components/otp-input/CoarOtpInput.vue.d.ts +1 -1
- package/dist/components/otp-input/CoarOtpInput.vue.d.ts.map +1 -1
- package/dist/components/password-input/CoarPasswordInput.vue.d.ts +1 -1
- package/dist/components/password-input/CoarPasswordInput.vue.d.ts.map +1 -1
- package/dist/components/switch/CoarSwitch.vue.d.ts +1 -1
- package/dist/components/switch/CoarSwitch.vue.d.ts.map +1 -1
- package/dist/components/text-input/CoarTextInput.vue.d.ts +1 -1
- package/dist/components/text-input/CoarTextInput.vue.d.ts.map +1 -1
- package/dist/composables/dragRegistry.d.ts +12 -0
- package/dist/composables/dragRegistry.d.ts.map +1 -1
- package/dist/composables/index.d.ts +2 -2
- package/dist/composables/index.d.ts.map +1 -1
- package/dist/composables/useDragDrop.d.ts +62 -6
- package/dist/composables/useDragDrop.d.ts.map +1 -1
- package/dist/composables/useVirtualList.d.ts +21 -1
- package/dist/composables/useVirtualList.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4508 -2039
- package/package.json +9 -3
- package/skills/cocoar-vue-ui/SKILL.md +244 -0
- package/skills/cocoar-vue-ui/references/components/avatar.md +178 -0
- package/skills/cocoar-vue-ui/references/components/badge.md +290 -0
- package/skills/cocoar-vue-ui/references/components/breadcrumb.md +238 -0
- package/skills/cocoar-vue-ui/references/components/button.md +252 -0
- package/skills/cocoar-vue-ui/references/components/calendar/agenda-view.md +372 -0
- package/skills/cocoar-vue-ui/references/components/calendar/coar-calendar.md +1647 -0
- package/skills/cocoar-vue-ui/references/components/calendar/day-view.md +361 -0
- package/skills/cocoar-vue-ui/references/components/calendar/index.md +312 -0
- package/skills/cocoar-vue-ui/references/components/calendar/month-view.md +486 -0
- package/skills/cocoar-vue-ui/references/components/calendar/performance.md +186 -0
- package/skills/cocoar-vue-ui/references/components/calendar/timeline-view.md +301 -0
- package/skills/cocoar-vue-ui/references/components/calendar/week-view.md +382 -0
- package/skills/cocoar-vue-ui/references/components/calendar/work-week-view.md +145 -0
- package/skills/cocoar-vue-ui/references/components/calendar/year-view.md +63 -0
- package/skills/cocoar-vue-ui/references/components/card.md +151 -0
- package/skills/cocoar-vue-ui/references/components/checkbox-group.md +103 -0
- package/skills/cocoar-vue-ui/references/components/checkbox.md +279 -0
- package/skills/cocoar-vue-ui/references/components/code-block.md +241 -0
- package/skills/cocoar-vue-ui/references/components/context-menu.md +355 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/checkbox.md +213 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/date-columns.md +272 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/editing.md +236 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/multi-select.md +193 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/number.md +147 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/select.md +202 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/text.md +127 -0
- package/skills/cocoar-vue-ui/references/components/data-grid.md +1225 -0
- package/skills/cocoar-vue-ui/references/components/data-list.md +2121 -0
- package/skills/cocoar-vue-ui/references/components/date-or-time-picker.md +200 -0
- package/skills/cocoar-vue-ui/references/components/date-picker.md +203 -0
- package/skills/cocoar-vue-ui/references/components/date-time-picker.md +154 -0
- package/skills/cocoar-vue-ui/references/components/date-views.md +212 -0
- package/skills/cocoar-vue-ui/references/components/dialog.md +148 -0
- package/skills/cocoar-vue-ui/references/components/divider.md +157 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/annotations.md +400 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/coar-document-viewer.md +327 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/index.md +234 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/toolbar.md +221 -0
- package/skills/cocoar-vue-ui/references/components/drag-drop.md +267 -0
- package/skills/cocoar-vue-ui/references/components/dual-listbox.md +449 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/asset-store.md +327 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/in-memory-store.md +176 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/index.md +311 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/use-file-explorer.md +300 -0
- package/skills/cocoar-vue-ui/references/components/form-field.md +1025 -0
- package/skills/cocoar-vue-ui/references/components/fragment-parser.md +298 -0
- package/skills/cocoar-vue-ui/references/components/link.md +235 -0
- package/skills/cocoar-vue-ui/references/components/listbox.md +575 -0
- package/skills/cocoar-vue-ui/references/components/map/editor.md +448 -0
- package/skills/cocoar-vue-ui/references/components/map/index.md +351 -0
- package/skills/cocoar-vue-ui/references/components/markdown-diagrams.md +210 -0
- package/skills/cocoar-vue-ui/references/components/markdown-editor.md +1478 -0
- package/skills/cocoar-vue-ui/references/components/markdown-embeds.md +387 -0
- package/skills/cocoar-vue-ui/references/components/markdown-form.md +447 -0
- package/skills/cocoar-vue-ui/references/components/markdown.md +276 -0
- package/skills/cocoar-vue-ui/references/components/menu.md +380 -0
- package/skills/cocoar-vue-ui/references/components/mermaid.md +172 -0
- package/skills/cocoar-vue-ui/references/components/navbar.md +147 -0
- package/skills/cocoar-vue-ui/references/components/note.md +96 -0
- package/skills/cocoar-vue-ui/references/components/notice.md +131 -0
- package/skills/cocoar-vue-ui/references/components/number-input.md +215 -0
- package/skills/cocoar-vue-ui/references/components/otp-input.md +324 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/authoring-contract.md +271 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/coar-page-builder.md +476 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/coar-page-renderer.md +677 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/custom-elements.md +441 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/idp-integration.md +122 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/index.md +573 -0
- package/skills/cocoar-vue-ui/references/components/pagination.md +150 -0
- package/skills/cocoar-vue-ui/references/components/panel-layout.md +372 -0
- package/skills/cocoar-vue-ui/references/components/password-input.md +175 -0
- package/skills/cocoar-vue-ui/references/components/popconfirm.md +186 -0
- package/skills/cocoar-vue-ui/references/components/popover.md +173 -0
- package/skills/cocoar-vue-ui/references/components/progress-bar.md +178 -0
- package/skills/cocoar-vue-ui/references/components/radio-group.md +225 -0
- package/skills/cocoar-vue-ui/references/components/script-editor.md +1271 -0
- package/skills/cocoar-vue-ui/references/components/segmented-control.md +238 -0
- package/skills/cocoar-vue-ui/references/components/select.md +463 -0
- package/skills/cocoar-vue-ui/references/components/sidebar.md +421 -0
- package/skills/cocoar-vue-ui/references/components/spinner.md +132 -0
- package/skills/cocoar-vue-ui/references/components/switch.md +195 -0
- package/skills/cocoar-vue-ui/references/components/table.md +170 -0
- package/skills/cocoar-vue-ui/references/components/tabs.md +382 -0
- package/skills/cocoar-vue-ui/references/components/tag.md +178 -0
- package/skills/cocoar-vue-ui/references/components/text-input.md +256 -0
- package/skills/cocoar-vue-ui/references/components/toast.md +160 -0
- package/skills/cocoar-vue-ui/references/components/tooltip.md +121 -0
- package/skills/cocoar-vue-ui/references/components/transitions.md +193 -0
- package/skills/cocoar-vue-ui/references/components/tree.md +2388 -0
- package/skills/cocoar-vue-ui/references/components/virtual-list.md +212 -0
- package/skills/cocoar-vue-ui/references/components/wizard.md +251 -0
- package/skills/cocoar-vue-ui/references/components/zoned-date-time-picker.md +177 -0
- package/skills/cocoar-vue-ui/references/foundations/colors.md +708 -0
- package/skills/cocoar-vue-ui/references/foundations/design-principles.md +115 -0
- package/skills/cocoar-vue-ui/references/foundations/icons.md +381 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/formatting.md +530 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/setup.md +86 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/timezones.md +182 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/translations.md +305 -0
- package/skills/cocoar-vue-ui/references/foundations/motion.md +549 -0
- package/skills/cocoar-vue-ui/references/foundations/spacing.md +330 -0
- package/skills/cocoar-vue-ui/references/foundations/theming.md +140 -0
- package/skills/cocoar-vue-ui/references/foundations/typography.md +206 -0
- package/skills/cocoar-vue-ui/references/guide/error-handling.md +162 -0
- package/skills/cocoar-vue-ui/references/guide/getting-started.md +116 -0
- package/skills/cocoar-vue-ui/references/guide/migration-page-builder-3.md +207 -0
- package/skills/cocoar-vue-ui/references/guide/migration.md +140 -0
- package/skills/cocoar-vue-ui/references/guide/theming.md +98 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
<!-- Generated from apps/docs/components/page-builder/custom-elements.md by apps/docs/scripts/sync-skill.mjs. Do not edit; edit the docs page. -->
|
|
2
|
+
|
|
3
|
+
# Custom elements
|
|
4
|
+
|
|
5
|
+
`@cocoar/vue-page-builder` dispatches every element — built-in and consumer-defined alike — through one **element registry**. A registration is a plain object (`PageElementDefinition`) that packages everything the renderer and the builder need to know about one element type: the runtime component, its value-model participation, and optionally its editor appearance (palette entry, canvas preview, inspector).
|
|
6
|
+
|
|
7
|
+
Built-ins (`heading`, `text-input`, `card`, …) are pre-registered definitions on exactly this contract. A consumer element you register is therefore a **first-class element**: it appears in the palette, renders on the canvas, gets its own inspector section, participates in required-field validation, and contributes its value to action payloads — indistinguishable from a built-in.
|
|
8
|
+
|
|
9
|
+
A complete working example (a star-rating input) lives in the playground at `apps/playground/src/components/rating/`, registered in `apps/playground/src/views/PageBuilderView.vue`. This page walks through that element.
|
|
10
|
+
|
|
11
|
+
## Wire format
|
|
12
|
+
|
|
13
|
+
Custom nodes use the same node grammar as every other element — there is no `custom` envelope. The `type` is the registry key; everything element-specific lives in the `props` bag:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"id": "3f2c9a6e-…",
|
|
18
|
+
"type": "acme-rating",
|
|
19
|
+
"props": { "label": "How did we do?", "max": 5 },
|
|
20
|
+
"name": "field_ab12",
|
|
21
|
+
"defaultValue": 3,
|
|
22
|
+
"validation": { "required": true },
|
|
23
|
+
"style": { "alignSelf": "start" }
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The **host vocabulary** stays at node level and is the same for every element:
|
|
28
|
+
|
|
29
|
+
| Field | Owner | Meaning |
|
|
30
|
+
|-------|-------|---------|
|
|
31
|
+
| `id` | host | Stable UUID, minted by the builder |
|
|
32
|
+
| `type` | host | Registry key (built-in type or consumer key) |
|
|
33
|
+
| `props` | **element** | The element's own vocabulary — JSON-safe, persisted verbatim |
|
|
34
|
+
| `style` | host | [`NodeStyle`](./coar-page-renderer.md#json-schema) — sizing, alignment, gap, padding |
|
|
35
|
+
| `name` / `defaultValue` / `validation` | host | Value-model trio; only meaningful when the definition has a `value` spec |
|
|
36
|
+
| `children` | host | Only when the definition declares `container` |
|
|
37
|
+
|
|
38
|
+
The bag exists so element props can never collide with host fields — an element prop named `style` or `children` is just `props.style` / `props.children`. The unified props-bag grammar was introduced in v2; version 4 added a stable name to every element, and version 5 added builder-only origin metadata for reusable compositions, and current `schemaVersion: 6` renamed the repeat's `props.source` to `props.contextPath`. Older documents normalize transparently (see [Degradation and compatibility](#degradation-and-compatibility)).
|
|
39
|
+
|
|
40
|
+
## Walkthrough: a rating element
|
|
41
|
+
|
|
42
|
+
The element is an interactive star-rating field. Four small pieces:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
rating/
|
|
46
|
+
├── ratingProps.ts the props-bag type (shared vocabulary)
|
|
47
|
+
├── RatingRenderer.vue runtime component ← renderer half
|
|
48
|
+
├── RatingPreview.vue builder canvas preview ┐
|
|
49
|
+
├── RatingInspector.vue props-panel section │ builder half
|
|
50
|
+
├── RatingDefaultInput.vue typed default-value edit ┘
|
|
51
|
+
└── ratingElement.ts the definition
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 1. The props bag
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
// ratingProps.ts
|
|
58
|
+
/** The rating element's props bag (JSON-safe, lives in `node.props`). */
|
|
59
|
+
export type RatingProps = {
|
|
60
|
+
label?: string;
|
|
61
|
+
max: number;
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Keep it JSON-safe — the bag is persisted verbatim. Non-JSON values (dates, functions) don't survive the round-trip; use string wire values and convert at the component boundary (that's what the built-in date inputs do with ISO strings).
|
|
66
|
+
|
|
67
|
+
### 2. The runtime renderer
|
|
68
|
+
|
|
69
|
+
The renderer receives `{ node }` and wires itself into the renderer's value model via `usePageElement()` — a curated, stable context with `getValue` / `setValue` / `getError` / `markTouched` / `triggerAction` / `triggerElementAction` / `isValidating` / `isSubmitting` / `pendingAction` / `formError` / `resolveAsset` / `config`:
|
|
70
|
+
|
|
71
|
+
```vue
|
|
72
|
+
<!-- RatingRenderer.vue -->
|
|
73
|
+
<script setup lang="ts">
|
|
74
|
+
import { computed } from 'vue';
|
|
75
|
+
import { CoarFormField } from '@cocoar/vue-ui';
|
|
76
|
+
import { usePageElement, type ElementNode } from '@cocoar/vue-page-builder';
|
|
77
|
+
import RatingStars from './RatingStars.vue';
|
|
78
|
+
import type { RatingProps } from './ratingProps';
|
|
79
|
+
|
|
80
|
+
const props = defineProps<{ node: ElementNode<string, RatingProps> }>();
|
|
81
|
+
|
|
82
|
+
const ctx = usePageElement();
|
|
83
|
+
const name = computed(() => props.node.name);
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
<template>
|
|
87
|
+
<CoarFormField
|
|
88
|
+
:label="node.props.label"
|
|
89
|
+
:required="node.validation?.required"
|
|
90
|
+
:error="name ? ctx.getError(name) : ''"
|
|
91
|
+
>
|
|
92
|
+
<RatingStars
|
|
93
|
+
:model-value="name ? Number(ctx.getValue(name) ?? 0) : 0"
|
|
94
|
+
:max="node.props.max"
|
|
95
|
+
@update:model-value="(v) => { if (name) { ctx.setValue(name, v); ctx.markTouched(name); } }"
|
|
96
|
+
/>
|
|
97
|
+
</CoarFormField>
|
|
98
|
+
</template>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Notes:
|
|
102
|
+
|
|
103
|
+
- `node.name` is optional — a rating dropped without a field name still renders, it just doesn't participate in the value model. Guard your `getValue`/`setValue` calls.
|
|
104
|
+
- `getError(name)` returns a non-empty message only once the field is *touched*; call `markTouched(name)` at the interaction that counts (blur for typing inputs, change for choose-is-the-interaction inputs like this one).
|
|
105
|
+
- `usePageElement()` throws outside a `<CoarPageRenderer>` — element renderers only run inside the page renderer (the builder canvas uses the preview instead, never this component).
|
|
106
|
+
- Submit-affordance elements read the busy state: `isSubmitting` is true while an action's Promise is pending, `pendingAction` names the action id in flight (spin when it matches your own), `isValidating` covers the `onValidate` window. `formError` carries the [form-level error](./coar-page-renderer.md#async-actions-the-form-level-error-channel) — a small consumer element can render the banner *inside* the page (e.g. right above the submit button) instead of the host-level default.
|
|
107
|
+
- Choice-style elements can resolve `optionsSourceId` props exactly like the built-ins via the exported `useResolvedOptions(() => props.node.props)` composable.
|
|
108
|
+
|
|
109
|
+
#### Action-capable consumer elements
|
|
110
|
+
|
|
111
|
+
Compose your props with the exported `ActionProps`, declare `action: true` on the definition, and trigger through `triggerElementAction`. The builder then adds its universal Action and JSON key/value editor; runtime merging behaves exactly like built-in buttons and links.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { definePageElement, type ActionProps } from '@cocoar/vue-page-builder'
|
|
115
|
+
|
|
116
|
+
type ActionChipProps = ActionProps & { label: string }
|
|
117
|
+
|
|
118
|
+
export const actionChip = definePageElement<ActionChipProps>({
|
|
119
|
+
action: true,
|
|
120
|
+
renderer: ActionChipRenderer,
|
|
121
|
+
builder: {
|
|
122
|
+
label: { key: 'app.pb.actionChip', fallback: 'Action chip' },
|
|
123
|
+
icon: 'bolt',
|
|
124
|
+
defaults: () => ({ label: 'Run' }),
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```vue
|
|
130
|
+
<script setup lang="ts">
|
|
131
|
+
import { usePageElement, type ActionProps, type ElementNode } from '@cocoar/vue-page-builder'
|
|
132
|
+
|
|
133
|
+
defineProps<{ node: ElementNode<string, ActionProps & { label: string }> }>()
|
|
134
|
+
const ctx = usePageElement()
|
|
135
|
+
</script>
|
|
136
|
+
|
|
137
|
+
<template>
|
|
138
|
+
<button type="button" @click="ctx.triggerElementAction(node.props)">{{ node.props.label }}</button>
|
|
139
|
+
</template>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Do not manually merge form data with `actionValues`: the shared trigger path applies the documented precedence (**form < resolved per-key action values < legacy dynamic action value**), removes invalid runtime values, and preserves the renderer's async-action guard.
|
|
143
|
+
|
|
144
|
+
### 3. Value-model participation
|
|
145
|
+
|
|
146
|
+
Declaring a `value` spec makes the element a managed field whenever the node carries a `name`: it seeds defaults, joins `required`/`matchField` gating, and its value is included in action payloads.
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
value: {
|
|
150
|
+
// A rating edits number fields (field-contract compatibility).
|
|
151
|
+
types: ['number'],
|
|
152
|
+
// What "empty" means for validation.required — a 0-star rating is empty.
|
|
153
|
+
isEmpty: (v) => !v || Number(v) === 0,
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The full spec:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
interface ElementValueSpec<P> {
|
|
161
|
+
/** Value types this element can edit — matched against the field
|
|
162
|
+
contract's `PageFieldSpec.valueType`. Omitted = unconstrained. */
|
|
163
|
+
types?: PageValueType[];
|
|
164
|
+
/** Opt into the host-enforced string rules
|
|
165
|
+
(validation.minLength / maxLength / pattern, and the built-in
|
|
166
|
+
email check when the bag carries inputType: 'email'). */
|
|
167
|
+
textRules?: boolean;
|
|
168
|
+
/** May a plain Enter inside this element submit the page (fire the
|
|
169
|
+
default button)? Only effective when the page root sets
|
|
170
|
+
`enterSubmits`. Boolean, or a per-props predicate
|
|
171
|
+
(text-input: (p) => (p.rows ?? 1) <= 1). Undeclared = never. */
|
|
172
|
+
submitOnEnter?: boolean | ((props: P) => boolean);
|
|
173
|
+
/** Fallback default when the node carries no `defaultValue`. Declare it —
|
|
174
|
+
it keeps untouched fields PRESENT in ActionValues (the built-ins seed
|
|
175
|
+
'' for strings, false for booleans, [] for multi-select, null for
|
|
176
|
+
"nothing picked/entered"). */
|
|
177
|
+
defaultValue?: (props: P) => unknown;
|
|
178
|
+
/** Emptiness test for `validation.required`.
|
|
179
|
+
Default: undefined | null | '' | false | [] count as empty. */
|
|
180
|
+
isEmpty?: (value: unknown, props: P) => boolean;
|
|
181
|
+
/** Element-owned validation, run after required/matchField.
|
|
182
|
+
Return an error message, or null when valid. Crash-guarded by the host. */
|
|
183
|
+
validate?: (value: unknown, node: ElementNode<string, P>, values: ActionValues) => string | null;
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The host enforces `validation.required` (via your `isEmpty`) and `matchField` for every valued element; the string rules (`minLength` / `maxLength` / `pattern`) are host-enforced only for definitions that opt in via `textRules` — they need the localized message pipeline and the crash-safe pattern compiler, so the host runs them (the built-in `text-input` and `password-input` set it; any consumer element with a string value can too). `types` declares which [field-contract](./index.md#field-contract) value types the element can edit — omitted means unconstrained (compatible with every field), so declare them to show up for the right DTO fields: the rating's `types: ['number']` is exactly what makes it a representation for `number` fields. Everything else goes through `validate`; error messages it returns are yours to localize.
|
|
188
|
+
|
|
189
|
+
### 4. The builder half
|
|
190
|
+
|
|
191
|
+
Everything editor-facing sits under `builder`. Omit the whole block for renderer-only registrations (see [the contract-package pattern](#the-two-module-contract-package-pattern)).
|
|
192
|
+
|
|
193
|
+
**Canvas preview** — receives `{ node, resolveAsset? }` as props and is mounted **inert** (`pointer-events: none`; clicks select the node). It renders from the node alone — it never gets the runtime renderer context, so it must not use `usePageElement()`:
|
|
194
|
+
|
|
195
|
+
```vue
|
|
196
|
+
<!-- RatingPreview.vue -->
|
|
197
|
+
<script setup lang="ts">
|
|
198
|
+
import { CoarFormField } from '@cocoar/vue-ui';
|
|
199
|
+
import type { ElementNode } from '@cocoar/vue-page-builder';
|
|
200
|
+
import RatingStars from './RatingStars.vue';
|
|
201
|
+
import type { RatingProps } from './ratingProps';
|
|
202
|
+
|
|
203
|
+
defineProps<{
|
|
204
|
+
node: ElementNode<string, RatingProps>;
|
|
205
|
+
resolveAsset?: (id: string) => string;
|
|
206
|
+
}>();
|
|
207
|
+
</script>
|
|
208
|
+
|
|
209
|
+
<template>
|
|
210
|
+
<CoarFormField :label="node.props.label" :required="node.validation?.required">
|
|
211
|
+
<RatingStars :model-value="Number(node.defaultValue ?? 0)" :max="node.props.max" readonly />
|
|
212
|
+
</CoarFormField>
|
|
213
|
+
</template>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
If you skip `preview`, the canvas shows a neutral icon + label chip — perfectly serviceable for elements whose appearance doesn't matter while authoring.
|
|
217
|
+
|
|
218
|
+
**Inspector** — the element's own section in the props panel. Receives `{ node, patch }`; `patch({ props: {…} })` merges into the props bag with delete-on-empty semantics per key and goes through the builder's undo history:
|
|
219
|
+
|
|
220
|
+
```vue
|
|
221
|
+
<!-- RatingInspector.vue -->
|
|
222
|
+
<script setup lang="ts">
|
|
223
|
+
import { CoarFormField, CoarTextInput, CoarNumberInput } from '@cocoar/vue-ui';
|
|
224
|
+
import type { ElementNode } from '@cocoar/vue-page-builder';
|
|
225
|
+
import type { RatingProps } from './ratingProps';
|
|
226
|
+
|
|
227
|
+
defineProps<{
|
|
228
|
+
node: ElementNode<string, RatingProps>;
|
|
229
|
+
patch: (update: { props?: Partial<RatingProps> }) => void;
|
|
230
|
+
}>();
|
|
231
|
+
</script>
|
|
232
|
+
|
|
233
|
+
<template>
|
|
234
|
+
<CoarFormField label="Label">
|
|
235
|
+
<CoarTextInput
|
|
236
|
+
:model-value="node.props.label ?? ''"
|
|
237
|
+
@update:model-value="(v) => patch({ props: { label: v } })"
|
|
238
|
+
/>
|
|
239
|
+
</CoarFormField>
|
|
240
|
+
<CoarFormField label="Max stars">
|
|
241
|
+
<CoarNumberInput
|
|
242
|
+
:model-value="node.props.max"
|
|
243
|
+
:min="1"
|
|
244
|
+
:max="10"
|
|
245
|
+
@update:model-value="(v) => patch({ props: { max: v ?? 5 } })"
|
|
246
|
+
/>
|
|
247
|
+
</CoarFormField>
|
|
248
|
+
</template>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The inspector only edits the **props bag**. Field name, required and default value live in the host-owned Field section — see [What the host owns](#what-the-host-owns). Option-list elements can reuse the exported `OptionsEditor` component (`import { OptionsEditor } from '@cocoar/vue-page-builder'`).
|
|
252
|
+
|
|
253
|
+
**Typed default-value editor** — the Field section's "Default value" control is a plain text input unless the definition supplies one. Contract: `{ modelValue, props }` in, `update:modelValue` out:
|
|
254
|
+
|
|
255
|
+
```vue
|
|
256
|
+
<!-- RatingDefaultInput.vue -->
|
|
257
|
+
<script setup lang="ts">
|
|
258
|
+
import RatingStars from './RatingStars.vue';
|
|
259
|
+
import type { RatingProps } from './ratingProps';
|
|
260
|
+
|
|
261
|
+
defineProps<{
|
|
262
|
+
modelValue: unknown;
|
|
263
|
+
props: RatingProps;
|
|
264
|
+
}>();
|
|
265
|
+
|
|
266
|
+
const emit = defineEmits<{ 'update:modelValue': [value: unknown] }>();
|
|
267
|
+
</script>
|
|
268
|
+
|
|
269
|
+
<template>
|
|
270
|
+
<RatingStars
|
|
271
|
+
:model-value="Number(modelValue ?? 0)"
|
|
272
|
+
:max="props.max"
|
|
273
|
+
@update:model-value="(v) => emit('update:modelValue', v || undefined)"
|
|
274
|
+
/>
|
|
275
|
+
</template>
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### 5. The definition
|
|
279
|
+
|
|
280
|
+
`definePageElement` is an identity helper that preserves the props-bag generic for the hooks and `markRaw`s the component fields (definitions travel through reactive config objects):
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
// ratingElement.ts
|
|
284
|
+
import { definePageElement } from '@cocoar/vue-page-builder';
|
|
285
|
+
import type { RatingProps } from './ratingProps';
|
|
286
|
+
import RatingRenderer from './RatingRenderer.vue';
|
|
287
|
+
import RatingPreview from './RatingPreview.vue';
|
|
288
|
+
import RatingInspector from './RatingInspector.vue';
|
|
289
|
+
import RatingDefaultInput from './RatingDefaultInput.vue';
|
|
290
|
+
|
|
291
|
+
export const ratingElement = definePageElement<RatingProps>({
|
|
292
|
+
renderer: RatingRenderer,
|
|
293
|
+
value: {
|
|
294
|
+
// A rating edits number fields (field-contract compatibility).
|
|
295
|
+
types: ['number'],
|
|
296
|
+
// required = at least one star.
|
|
297
|
+
isEmpty: (v) => !v || Number(v) === 0,
|
|
298
|
+
},
|
|
299
|
+
builder: {
|
|
300
|
+
label: { key: 'acme.pb.type.rating', fallback: 'Rating' },
|
|
301
|
+
icon: 'star',
|
|
302
|
+
defaults: () => ({ label: 'Rating', max: 5 }),
|
|
303
|
+
preview: RatingPreview,
|
|
304
|
+
inspector: RatingInspector,
|
|
305
|
+
inspectorTitle: { key: 'acme.pb.section.rating', fallback: 'Rating' },
|
|
306
|
+
defaultValueInput: RatingDefaultInput,
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
The remaining definition fields at a glance:
|
|
312
|
+
|
|
313
|
+
| Field | Effect |
|
|
314
|
+
|-------|--------|
|
|
315
|
+
| `container: true` | Node gets a `children` array, canvas dropzones, and the container style fields (gap, justify, align). The **host** renders the children into your renderer's default slot — recursion, allow-gating and direction context stay centralized. |
|
|
316
|
+
| `inline: true` | Leaf gets the `width: fit-content` treatment (like button/link) instead of stretching. |
|
|
317
|
+
| `builder.group` | Palette grouping: `'container'` or `'element'` (default). The `'element'` group is presented split by value-spec presence: value elements show under **Inputs** (the offering `hideElementPicker` removes), the rest under **Elements**. |
|
|
318
|
+
| `builder.hideStyleSection` | Suppresses the universal Style section for spacer-style minimal elements. |
|
|
319
|
+
| `builder.quickProperties` | Optional Properties-panel shortcuts that write locked Element Code assignments. Mark human copy with `valueKind: 'localized-text'`; the Builder then stores a translation key and edits its values in the central Translations tab. Leave layout, identifiers and enum values literal. |
|
|
320
|
+
| `builder.lint` | `(node, config?) => { severity: 'error' \| 'warning'; message: I18nText }[]` — authoring diagnostics merged into the builder's validation panel. Lint never blocks saving. |
|
|
321
|
+
| `normalizeProps` | `(raw: unknown) => P` — ingest healing for the props bag (untrusted JSON). Runs crash-guarded inside `normalizePageSchema` on every builder ingest path (v-model, JSON tab) and wherever the server passes `{ elements }`. Return the input unchanged (same reference) when nothing needs healing. |
|
|
322
|
+
|
|
323
|
+
Labels and lint messages are `I18nText` pairs — `{ key, fallback }`, translated via `@cocoar/vue-localization` with the fallback used when the key isn't registered. English-only apps need no i18n setup.
|
|
324
|
+
|
|
325
|
+
> **Warning: `value` + `container` on one element**
|
|
326
|
+
>
|
|
327
|
+
> The flags compose — an element can carry a value **and** accept children (say, a toggleable group), and the runtime handles both. Two authoring caveats, though: the builder **canvas** renders the generic children body for containers, so such an element's own chrome only shows in the Preview tab (a `builder.preview` on a container definition is never mounted — DEV warns about it), and the representation switcher refuses targets that would drop children (`convertTo` guards against it). If you hit this combination in earnest, tell us — full canvas support needs a designed preview-with-children contract.
|
|
328
|
+
|
|
329
|
+
## Registering
|
|
330
|
+
|
|
331
|
+
There is deliberately **no global `register()`** — registries are per-instance data, so two builders with different element sets can coexist. Two channels:
|
|
332
|
+
|
|
333
|
+
**Per instance, via `config.elementTypes`** (wins when both are present):
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
import type { PageConfig } from '@cocoar/vue-page-builder';
|
|
337
|
+
import { ratingElement } from './rating/ratingElement';
|
|
338
|
+
|
|
339
|
+
const config: PageConfig = {
|
|
340
|
+
elements: { 'acme-rating': ratingElement },
|
|
341
|
+
// …
|
|
342
|
+
};
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Pass the same `config` to both `<CoarPageBuilder>` and `<CoarPageRenderer>` — one registration serves palette, canvas, inspector and runtime.
|
|
346
|
+
|
|
347
|
+
**App-wide default, via `app.provide`:**
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
// main.ts
|
|
351
|
+
import { PAGE_ELEMENT_TYPES_KEY } from '@cocoar/vue-page-builder';
|
|
352
|
+
import { ratingElement } from './rating/ratingElement';
|
|
353
|
+
|
|
354
|
+
app.provide(PAGE_ELEMENT_TYPES_KEY, { 'acme-rating': ratingElement });
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Every builder/renderer in the app without its own `config.elementTypes` picks these up.
|
|
358
|
+
|
|
359
|
+
### `allowedElements` composition
|
|
360
|
+
|
|
361
|
+
`config.allowedElements` takes built-in types and consumer keys in one list — a consumer element is gated exactly like a built-in (hidden from the palette, blocked on the canvas, skipped by the renderer when absent):
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
allowedElements: [
|
|
365
|
+
'stack', 'card', 'heading', 'paragraph',
|
|
366
|
+
'text-input', 'checkbox', 'button',
|
|
367
|
+
'acme-rating',
|
|
368
|
+
],
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Registration makes an element *available*; `allowedElements` (when present) decides whether it's *permitted*. Omitting `allowedElements` allows everything registered.
|
|
372
|
+
|
|
373
|
+
### Keys
|
|
374
|
+
|
|
375
|
+
Consumer keys must match `^[a-z][a-z0-9-]*$` — lowercase kebab-case, colon-free (so keys survive stricter grammars such as markdown embeds). **Prefix your keys with a vendor namespace** (`acme-rating`, not `rating`): registration merges *additively* over the built-in set, and shadowing an existing key triggers a DEV warning — an unprefixed `rating` would silently collide the day the library ships a built-in of the same name. `mergeElementRegistries` is exported if you need to compose registries yourself.
|
|
376
|
+
|
|
377
|
+
## The two-module contract-package pattern
|
|
378
|
+
|
|
379
|
+
The definition splits into a **renderer half** (top level: `renderer`, `value`, `container`, `inline`) and an optional **builder half** (`builder: {…}`). Runtime apps that only ever render pages should not pull inspector/preview components into their bundle. Ship elements as a small contract package with two entry points:
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
// @acme/page-elements/renderer — imported by BOTH apps
|
|
383
|
+
import { definePageElement } from '@cocoar/vue-page-builder';
|
|
384
|
+
import RatingRenderer from './RatingRenderer.vue';
|
|
385
|
+
|
|
386
|
+
export const ratingRendererHalf = definePageElement<RatingProps>({
|
|
387
|
+
renderer: RatingRenderer,
|
|
388
|
+
value: { types: ['number'], isEmpty: (v) => !v || Number(v) === 0 },
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
// @acme/page-elements/builder — imported by the ADMIN app only
|
|
394
|
+
import { definePageElement } from '@cocoar/vue-page-builder';
|
|
395
|
+
import { ratingRendererHalf } from './renderer';
|
|
396
|
+
import RatingPreview from './RatingPreview.vue';
|
|
397
|
+
import RatingInspector from './RatingInspector.vue';
|
|
398
|
+
import RatingDefaultInput from './RatingDefaultInput.vue';
|
|
399
|
+
|
|
400
|
+
export const ratingElement = definePageElement<RatingProps>({
|
|
401
|
+
...ratingRendererHalf,
|
|
402
|
+
builder: {
|
|
403
|
+
label: { key: 'acme.pb.type.rating', fallback: 'Rating' },
|
|
404
|
+
icon: 'star',
|
|
405
|
+
defaults: () => ({ label: 'Rating', max: 5 }),
|
|
406
|
+
preview: RatingPreview,
|
|
407
|
+
inspector: RatingInspector,
|
|
408
|
+
defaultValueInput: RatingDefaultInput,
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
The runtime app registers `{ 'acme-rating': ratingRendererHalf }`, the admin app `{ 'acme-rating': ratingElement }` — same key, same wire format, no builder-only code in the runtime bundle. A definition without a `builder` half renders fine but never appears in the palette.
|
|
414
|
+
|
|
415
|
+
## Degradation and compatibility
|
|
416
|
+
|
|
417
|
+
A document is data; the registry is code. The two can disagree — a document authored against a newer library version, an element the current app didn't register, or a type outside `allowedElements`. The policy is **lossless and lenient**:
|
|
418
|
+
|
|
419
|
+
| Situation | Builder canvas | Builder JSON tab | Runtime renderer | Value model |
|
|
420
|
+
|-----------|----------------|------------------|------------------|-------------|
|
|
421
|
+
| **Unregistered type** (unknown to this app) | Red "Unknown type — skipped at runtime" treatment; node stays selectable, movable, deletable | Normalize *warning* — Apply is **not** blocked (only structural errors block), node round-trips byte-for-byte incl. its `children` | Skipped, one `console.warn` per type | Excluded — contributes no defaults, **cannot veto submit** |
|
|
422
|
+
| **Registered but not in `allowedElements`** | Same blocked treatment + validation error; hidden from palette/add menu | Pasteable, kept | Skipped (this is the security boundary) | Excluded, same non-veto rule |
|
|
423
|
+
| **v1 document** (pre-GA flat props, `schemaVersion` absent or `1`) | Migrated to the v2 props bag on ingest (`migrateV1PropsBag`, idempotent), missing names added and `schemaVersion: 6` stamped | Migrated on Apply | Migrated on the fly | Normal |
|
|
424
|
+
|
|
425
|
+
The non-veto rule matters: a `required` field of a type this deployment can't render would otherwise permanently block every `validates: true` button. Submission is therefore **lenient by design** — an invisible field can't veto submit. If a document must not execute with fields missing, enforce that server-side against the payload.
|
|
426
|
+
|
|
427
|
+
Server-side, the same ingest pipeline is exported: `normalizePageSchema(value, options?)` returns `{ schema, issues, changed }` with per-issue `severity: 'error' | 'warning'` (`error` = data was dropped, `warning` = healed or lossless), and runs the legacy-type and v1→v2 migrations first. Pass `{ elements }` to make it registry-aware: registered consumer types then count as known (no unknown-type warning, container recursion honours `container: true`) and each definition's `normalizeProps(raw)` hook runs as a crash-guarded healing pass over the props bag — the element's own ingest gate for untrusted JSON. Without the option you get the registry-less structural pass. `migrateV1PropsBag` is exported separately.
|
|
428
|
+
|
|
429
|
+
## What the host owns
|
|
430
|
+
|
|
431
|
+
When writing an element, you own the props bag and the components. Everything else is host machinery you get for free — and must not re-implement:
|
|
432
|
+
|
|
433
|
+
- **Field section** — the props panel renders name / required / default-value controls automatically whenever the definition has a `value` spec. Your inspector never edits `node.name`, `node.validation` or `node.defaultValue`; supply `defaultValueInput` if the default needs a typed editor.
|
|
434
|
+
- **Style section** — the universal `NodeStyle` editor (size, alignment, gap, padding; container fields keyed off `container`). Elements don't define their own layout props — `props` is for element vocabulary, `style` is host-owned.
|
|
435
|
+
- **Drag and drop** — palette entry, canvas dropzones, outline reorder, duplicate, undo/redo. Dropzone legality derives from `container`; a freshly dropped node gets `id`, `props` from your `defaults()`, a minted `name` when the definition has a `value` spec (under a strict [field contract](./index.md#field-contract) — `config.dataContract` set, `allowCustomFields` off — fresh value elements start unbound instead, and the author binds a contract field), and `children: []` when it's a container.
|
|
436
|
+
- **The allow gate** — `allowedElements` filtering in palette, canvas and renderer, including the value-model exclusion. Renderers never need to check it.
|
|
437
|
+
- **Conditional visibility** — [`visibleWhen`](./coar-page-renderer.md#conditional-visibility-visiblewhen) is host vocabulary: the host hides the node (your renderer simply isn't mounted) and excludes its subtree from the value model. Never re-implement show/hide inside a renderer — a hidden-by-you `required` child would still veto submits.
|
|
438
|
+
- **Enter-to-submit** — declare eligibility (`value.submitOnEnter`); the host wires the keydown routing and the default-button resolution. No key handlers needed in your renderer.
|
|
439
|
+
- **Value plumbing** — default seeding (`node.defaultValue ?? value.defaultValue?.(props)`), the renderer's [`initialValues` host prefill](./coar-page-renderer.md), touched-state, error computation ordering (required → string rules/email → matchField → your `validate`), the busy/submit lifecycle (`isSubmitting`, reentry guards, the form-error banner), and action payload assembly (allowed + visible fields, snapshot).
|
|
440
|
+
|
|
441
|
+
Registered element components are **trusted consumer code** — the library does not sandbox them. The tenant-facing security boundary remains `allowedElements` plus the renderer's `actions` map; the registry is a developer-facing extension surface.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<!-- Generated from apps/docs/components/page-builder/idp-integration.md by apps/docs/scripts/sync-skill.mjs. Do not edit; edit the docs page. -->
|
|
2
|
+
|
|
3
|
+
# IDP integration
|
|
4
|
+
|
|
5
|
+
Page Builder ships the complete browser runtime and nothing auth-specific.
|
|
6
|
+
Login, consent and the rest are built from the same generic registry, Repeat,
|
|
7
|
+
selection, action and styling contracts as every other consumer page.
|
|
8
|
+
|
|
9
|
+
## Public integration surface
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import {
|
|
13
|
+
definePageRuntimeHost,
|
|
14
|
+
usePageCodeRuntime,
|
|
15
|
+
validatePageDocument,
|
|
16
|
+
} from '@cocoar/vue-page-builder';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The package ships no auth-specific configuration or documents. An IDP builds
|
|
20
|
+
its own `PageConfig` — fields, actions, context and states for `login`,
|
|
21
|
+
`password-forgot`, `logout` and `consent` — and stores the starting documents
|
|
22
|
+
itself; `apps/playground/src/views/auth-customization/` is a worked example.
|
|
23
|
+
The runtime contains no auth element types or branches.
|
|
24
|
+
|
|
25
|
+
## Ownership boundary
|
|
26
|
+
|
|
27
|
+
| Concern | Owner |
|
|
28
|
+
|---|---|
|
|
29
|
+
| Structure, styles, translations, Page State and Element Code | Page document |
|
|
30
|
+
| Element/action/context allowlists | IDP `PageConfig` |
|
|
31
|
+
| API facades and grants | Application `PageRuntimeHost` |
|
|
32
|
+
| Login, consent, tickets, redirects and authorization | IDP backend |
|
|
33
|
+
| Drafts, revisions, publish, rollback and audit | IDP persistence layer |
|
|
34
|
+
|
|
35
|
+
Resolve the application/client theme in the host, then use the generic UI
|
|
36
|
+
scope for runtime and the Builder's preview-only prop for authoring:
|
|
37
|
+
|
|
38
|
+
```vue
|
|
39
|
+
<CoarThemeScope :theme="applicationTheme" mode="auto">
|
|
40
|
+
<CoarPageRenderer :schema :config />
|
|
41
|
+
</CoarThemeScope>
|
|
42
|
+
|
|
43
|
+
<CoarPageBuilder
|
|
44
|
+
v-model="schema"
|
|
45
|
+
:config="config"
|
|
46
|
+
:preview-theme="applicationTheme"
|
|
47
|
+
preview-theme-mode="dark"
|
|
48
|
+
/>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The Builder applies `previewTheme` only around its embedded renderer; toolbar,
|
|
52
|
+
properties, dialogs and Monaco retain the administration application's theme.
|
|
53
|
+
Brand colours, radii and fonts travel through `CoarTheme`; anything beyond it
|
|
54
|
+
is authored as node styles or as a `visual-markup` element. The document never
|
|
55
|
+
carries a CSS class from the host.
|
|
56
|
+
|
|
57
|
+
## Monaco workers in the authoring application
|
|
58
|
+
|
|
59
|
+
The Builder uses Monaco in JavaScript and JSON mode. A Vite consumer must route
|
|
60
|
+
both languages to their matching workers before the first Builder mounts:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
|
|
64
|
+
import TsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
|
|
65
|
+
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
|
|
66
|
+
|
|
67
|
+
self.MonacoEnvironment = {
|
|
68
|
+
getWorker(_workerId, label) {
|
|
69
|
+
if (label === 'typescript' || label === 'javascript') return new TsWorker();
|
|
70
|
+
if (label === 'json') return new JsonWorker();
|
|
71
|
+
return new EditorWorker();
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Routing `json` to the generic editor worker causes diagnostics such as
|
|
77
|
+
`Missing requestHandler or method: doValidation`, `findDocumentColors` or
|
|
78
|
+
`getFoldingRanges`. This is a consumer worker configuration error, not a
|
|
79
|
+
PageBuilder runtime or sandbox failure.
|
|
80
|
+
|
|
81
|
+
Create one application-wide host. Each `usePageCodeRuntime()` instance creates
|
|
82
|
+
an isolated Worker session for one rendered page; sessions do not share globals
|
|
83
|
+
or Page State. A host object is available to tenant code only when it exists in
|
|
84
|
+
the host catalogue and the grant policy returns its name for that exact runtime
|
|
85
|
+
definition.
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
const runtimeHost = definePageRuntimeHost({
|
|
89
|
+
endowments: { api: idpPageApiFacade },
|
|
90
|
+
grants: ({ pageId, definition }) =>
|
|
91
|
+
pageId.startsWith('auth:') && definition.id.startsWith('element-action:')
|
|
92
|
+
? ['api']
|
|
93
|
+
: [],
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Without a grant there is no `api`; there is never ambient `window`, `fetch`, DOM
|
|
98
|
+
or filesystem access in Element Code.
|
|
99
|
+
|
|
100
|
+
For Vite 8, keep only the Worker-bearing runtime subpath outside dependency
|
|
101
|
+
pre-bundling:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
optimizeDeps: {
|
|
105
|
+
exclude: ['@cocoar/vue-page-builder/runtime-worker'],
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The main PageBuilder package and all UI/editor dependencies remain optimized.
|
|
110
|
+
The packed-consumer matrix verifies forced development optimization and the
|
|
111
|
+
production Worker asset on both Linux and Windows.
|
|
112
|
+
|
|
113
|
+
## Publication boundary
|
|
114
|
+
|
|
115
|
+
Save drafts as immutable, versioned documents and activate a revision only after
|
|
116
|
+
trusted server validation. The browser's `validatePageDocument()` provides the
|
|
117
|
+
same fast feedback to the author but cannot authorize publication. Keep a known
|
|
118
|
+
good default and published revision so invalid or unavailable customizations can
|
|
119
|
+
fall back without breaking authentication.
|
|
120
|
+
|
|
121
|
+
The complete storage lifecycle, Vue wiring, capability example and production security
|
|
122
|
+
boundary are included in the package's `IDP_INTEGRATION.md`.
|