@aleph-alpha/settings-kit 0.1.0 → 1.0.0
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/README.md +465 -15
- package/dist/SkLayout.vue_vue_type_script_setup_true_lang-ZjV2AMdR.js +51 -0
- package/dist/SkLayoutPage-DGbCpQFm.js +2545 -0
- package/dist/UseFileConvertor-DN6cCXNi.js +50 -0
- package/dist/components/SkField/SkField.vue.d.ts +28 -0
- package/dist/components/SkField/SkField.vue.d.ts.map +1 -0
- package/dist/components/SkField/index.d.ts +2 -0
- package/dist/components/SkField/index.d.ts.map +1 -0
- package/dist/components/SkGroup/SkGroup.vue.d.ts +29 -0
- package/dist/components/SkGroup/SkGroup.vue.d.ts.map +1 -0
- package/dist/components/SkGroup/index.d.ts +2 -0
- package/dist/components/SkGroup/index.d.ts.map +1 -0
- package/dist/components/SkItem/SkItem.vue.d.ts +22 -0
- package/dist/components/SkItem/SkItem.vue.d.ts.map +1 -0
- package/dist/components/SkItem/index.d.ts +2 -0
- package/dist/components/SkItem/index.d.ts.map +1 -0
- package/dist/components/SkLayout/SkLayout.vue.d.ts +53 -0
- package/dist/components/SkLayout/SkLayout.vue.d.ts.map +1 -0
- package/dist/components/SkLayout/SkLayoutPage.d.ts +23 -0
- package/dist/components/SkLayout/SkLayoutPage.d.ts.map +1 -0
- package/dist/components/SkLayout/context.d.ts +61 -0
- package/dist/components/SkLayout/context.d.ts.map +1 -0
- package/dist/components/SkLayout/index.d.ts +5 -0
- package/dist/components/SkLayout/index.d.ts.map +1 -0
- package/dist/components/SkPage/SkPage.vue.d.ts +35 -0
- package/dist/components/SkPage/SkPage.vue.d.ts.map +1 -0
- package/dist/components/SkPage/index.d.ts +2 -0
- package/dist/components/SkPage/index.d.ts.map +1 -0
- package/dist/components/SkSection/SkSection.vue.d.ts +29 -0
- package/dist/components/SkSection/SkSection.vue.d.ts.map +1 -0
- package/dist/components/SkSection/index.d.ts +2 -0
- package/dist/components/SkSection/index.d.ts.map +1 -0
- package/dist/components/controls/SkActionsControl/SkActionsControl.vue.d.ts +37 -0
- package/dist/components/controls/SkActionsControl/SkActionsControl.vue.d.ts.map +1 -0
- package/dist/components/controls/SkActionsControl/index.d.ts +2 -0
- package/dist/components/controls/SkActionsControl/index.d.ts.map +1 -0
- package/dist/components/controls/SkCheckboxControl/SkCheckboxControl.vue.d.ts +35 -0
- package/dist/components/controls/SkCheckboxControl/SkCheckboxControl.vue.d.ts.map +1 -0
- package/dist/components/controls/SkCheckboxControl/index.d.ts +2 -0
- package/dist/components/controls/SkCheckboxControl/index.d.ts.map +1 -0
- package/dist/components/controls/SkColorControl/SkColorControl.vue.d.ts +35 -0
- package/dist/components/controls/SkColorControl/SkColorControl.vue.d.ts.map +1 -0
- package/dist/components/controls/SkColorControl/index.d.ts +2 -0
- package/dist/components/controls/SkColorControl/index.d.ts.map +1 -0
- package/dist/components/controls/SkFileUpload/SkFilePlaceholder.vue.d.ts +11 -0
- package/dist/components/controls/SkFileUpload/SkFilePlaceholder.vue.d.ts.map +1 -0
- package/dist/components/controls/SkFileUpload/SkFileUpload.vue.d.ts +46 -0
- package/dist/components/controls/SkFileUpload/SkFileUpload.vue.d.ts.map +1 -0
- package/dist/components/controls/SkFileUpload/index.d.ts +2 -0
- package/dist/components/controls/SkFileUpload/index.d.ts.map +1 -0
- package/dist/components/controls/SkPeriodControl/SkPeriodControl.vue.d.ts +35 -0
- package/dist/components/controls/SkPeriodControl/SkPeriodControl.vue.d.ts.map +1 -0
- package/dist/components/controls/SkPeriodControl/index.d.ts +2 -0
- package/dist/components/controls/SkPeriodControl/index.d.ts.map +1 -0
- package/dist/components/controls/SkSelectControl/SkSelectControl.vue.d.ts +40 -0
- package/dist/components/controls/SkSelectControl/SkSelectControl.vue.d.ts.map +1 -0
- package/dist/components/controls/SkSelectControl/index.d.ts +2 -0
- package/dist/components/controls/SkSelectControl/index.d.ts.map +1 -0
- package/dist/components/controls/SkTelControl/SKTelPrefixControl.vue.d.ts +16 -0
- package/dist/components/controls/SkTelControl/SKTelPrefixControl.vue.d.ts.map +1 -0
- package/dist/components/controls/SkTelControl/SkTelControl.vue.d.ts +35 -0
- package/dist/components/controls/SkTelControl/SkTelControl.vue.d.ts.map +1 -0
- package/dist/components/controls/SkTelControl/index.d.ts +4 -0
- package/dist/components/controls/SkTelControl/index.d.ts.map +1 -0
- package/dist/components/controls/SkTelControl/prefixes.d.ts +26 -0
- package/dist/components/controls/SkTelControl/prefixes.d.ts.map +1 -0
- package/dist/components/controls/SkTextControl/SkTextControl.vue.d.ts +45 -0
- package/dist/components/controls/SkTextControl/SkTextControl.vue.d.ts.map +1 -0
- package/dist/components/controls/SkTextControl/index.d.ts +2 -0
- package/dist/components/controls/SkTextControl/index.d.ts.map +1 -0
- package/dist/components/controls/SkUnsupportedControl/SkUnsupportedControl.vue.d.ts +8 -0
- package/dist/components/controls/SkUnsupportedControl/SkUnsupportedControl.vue.d.ts.map +1 -0
- package/dist/components/controls/storyEventLog.d.ts +33 -0
- package/dist/components/controls/storyEventLog.d.ts.map +1 -0
- package/dist/components/index.d.ts +7 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +12 -2
- package/dist/composables/UseFileConvertor/UseFileConvertor.d.ts +7 -0
- package/dist/composables/UseFileConvertor/UseFileConvertor.d.ts.map +1 -0
- package/dist/composables/index.d.ts +14 -0
- package/dist/composables/index.d.ts.map +1 -0
- package/dist/composables/useFieldInheritance/useFieldInheritance.d.ts +24 -0
- package/dist/composables/useFieldInheritance/useFieldInheritance.d.ts.map +1 -0
- package/dist/composables/useFieldValidator/useFieldValidator.d.ts +50 -0
- package/dist/composables/useFieldValidator/useFieldValidator.d.ts.map +1 -0
- package/dist/composables/useSettingField/useSettingField.d.ts +24 -0
- package/dist/composables/useSettingField/useSettingField.d.ts.map +1 -0
- package/dist/composables/useSettingsForm/useSettingsForm.d.ts +12 -0
- package/dist/composables/useSettingsForm/useSettingsForm.d.ts.map +1 -0
- package/dist/composables/useSettingsLayout/useSettingsLayout.d.ts +71 -0
- package/dist/composables/useSettingsLayout/useSettingsLayout.d.ts.map +1 -0
- package/dist/composables/useSettingsLocale/useSettingsLocale.d.ts +38 -0
- package/dist/composables/useSettingsLocale/useSettingsLocale.d.ts.map +1 -0
- package/dist/composables/useStateManager/useStateManager.d.ts +10 -0
- package/dist/composables/useStateManager/useStateManager.d.ts.map +1 -0
- package/dist/core/controls/controlDefinitions.d.ts +33 -0
- package/dist/core/controls/controlDefinitions.d.ts.map +1 -0
- package/dist/core/controls/index.d.ts +26 -0
- package/dist/core/controls/index.d.ts.map +1 -0
- package/dist/core/index.d.ts +18 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/layout/index.d.ts +8 -0
- package/dist/core/layout/index.d.ts.map +1 -0
- package/dist/core/layout/navigation.d.ts +33 -0
- package/dist/core/layout/navigation.d.ts.map +1 -0
- package/dist/core/layout/resolve.d.ts +41 -0
- package/dist/core/layout/resolve.d.ts.map +1 -0
- package/dist/core/layout/types.d.ts +160 -0
- package/dist/core/layout/types.d.ts.map +1 -0
- package/dist/core/layout/validate.d.ts +26 -0
- package/dist/core/layout/validate.d.ts.map +1 -0
- package/dist/core/types.d.ts +541 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/useControlModel.d.ts +13 -0
- package/dist/core/useControlModel.d.ts.map +1 -0
- package/dist/core/utils/a11y.d.ts +25 -0
- package/dist/core/utils/a11y.d.ts.map +1 -0
- package/dist/core/utils/cast.d.ts +8 -0
- package/dist/core/utils/cast.d.ts.map +1 -0
- package/dist/core/utils/consts.d.ts +978 -0
- package/dist/core/utils/consts.d.ts.map +1 -0
- package/dist/core/utils/kitMessages.d.ts +165 -0
- package/dist/core/utils/kitMessages.d.ts.map +1 -0
- package/dist/core/utils/localizedText.d.ts +10 -0
- package/dist/core/utils/localizedText.d.ts.map +1 -0
- package/dist/core/utils/useName.d.ts +7 -0
- package/dist/core/utils/useName.d.ts.map +1 -0
- package/dist/core/validate/constraints.d.ts +20 -0
- package/dist/core/validate/constraints.d.ts.map +1 -0
- package/dist/core/validate/rules.d.ts +29 -0
- package/dist/core/validate/rules.d.ts.map +1 -0
- package/dist/index-BQ6z6MK4.js +214 -0
- package/dist/index-BXs-0JdT.js +168 -0
- package/dist/index-BynIH0EE.js +92 -0
- package/dist/index-C-WLhRod.js +100 -0
- package/dist/index-CFMv8jSO.js +4 -0
- package/dist/index-CGYERet5.js +373 -0
- package/dist/index-KAbhkO1J.js +81 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -3
- package/dist/service/index.d.ts +2 -0
- package/dist/service/index.d.ts.map +1 -0
- package/dist/shell/SkShell.vue.d.ts +80 -0
- package/dist/shell/SkShell.vue.d.ts.map +1 -0
- package/dist/shell/SkShellFrame.vue.d.ts +46 -0
- package/dist/shell/SkShellFrame.vue.d.ts.map +1 -0
- package/dist/shell/SkShellNav.vue.d.ts +19 -0
- package/dist/shell/SkShellNav.vue.d.ts.map +1 -0
- package/dist/shell/history.d.ts +9 -0
- package/dist/shell/history.d.ts.map +1 -0
- package/dist/shell/index.d.ts +12 -0
- package/dist/shell/index.d.ts.map +1 -0
- package/dist/shell/index.js +848 -0
- package/dist/shell/labels.d.ts +25 -0
- package/dist/shell/labels.d.ts.map +1 -0
- package/dist/shell/location.d.ts +18 -0
- package/dist/shell/location.d.ts.map +1 -0
- package/dist/shell/routes.d.ts +19 -0
- package/dist/shell/routes.d.ts.map +1 -0
- package/dist/shell/useMediaQuery.d.ts +4 -0
- package/dist/shell/useMediaQuery.d.ts.map +1 -0
- package/dist/shell/useShellLocation.d.ts +37 -0
- package/dist/shell/useShellLocation.d.ts.map +1 -0
- package/package.json +42 -23
- package/dist/SettingToggleUnit.vue_vue_type_script_setup_true_lang-BM7-6aXj.js +0 -61
- package/dist/components/SettingToggleUnit/SettingToggleUnit.vue.d.ts +0 -16
- package/dist/components/SettingToggleUnit/SettingToggleUnit.vue.d.ts.map +0 -1
- package/dist/components/SettingToggleUnit/index.d.ts +0 -3
- package/dist/components/SettingToggleUnit/index.d.ts.map +0 -1
- package/dist/components/SettingToggleUnit/types.d.ts +0 -11
- package/dist/components/SettingToggleUnit/types.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,34 +1,469 @@
|
|
|
1
1
|
# @aleph-alpha/settings-kit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Turns two backend payloads into an accessible admin screen for Vue 3 applications built on the Aleph Alpha Design System:
|
|
4
4
|
|
|
5
|
-
- **
|
|
6
|
-
- **
|
|
5
|
+
- **Fields** (`SettingField[]`) say _what_ each setting is: type, value, constraints, rules, fallback.
|
|
6
|
+
- **Layout** (`SettingsLayout`) says _where_ each setting renders: category › page › section › group › item.
|
|
7
7
|
|
|
8
|
-
The kit
|
|
9
|
-
|
|
10
|
-
## Installation
|
|
8
|
+
Both are plain JSON. The kit renders them, stages edits in the state manager, validates, and saves through your `onSave`.
|
|
11
9
|
|
|
12
10
|
```bash
|
|
13
|
-
pnpm add @aleph-alpha/settings-kit
|
|
11
|
+
pnpm add @aleph-alpha/settings-kit @aleph-alpha/ui-library vue
|
|
12
|
+
pnpm add vue-router # only for '@aleph-alpha/settings-kit/shell'
|
|
14
13
|
```
|
|
15
14
|
|
|
16
|
-
##
|
|
15
|
+
## Contents
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
- [Quick start](#quick-start)
|
|
18
|
+
- [Layers](#layers)
|
|
19
|
+
- [The schema](#the-schema)
|
|
20
|
+
- [Field](#field)
|
|
21
|
+
- [Layout: category, page, section, group, item](#layout-category-page-section-group-item)
|
|
22
|
+
- [Actions](#actions)
|
|
23
|
+
- [Fallback](#fallback)
|
|
24
|
+
- [Rules](#rules)
|
|
25
|
+
- [Identity, ordering and validation](#identity-ordering-and-validation)
|
|
26
|
+
- [State manager integration](#state-manager-integration)
|
|
27
|
+
- [Actions at runtime](#actions-at-runtime)
|
|
28
|
+
- [Routing (SkShell)](#routing-skshell)
|
|
29
|
+
- [Customisation](#customisation)
|
|
30
|
+
- [Accessibility](#accessibility)
|
|
31
|
+
- [Styles](#styles)
|
|
32
|
+
- [Entry points](#entry-points)
|
|
33
|
+
- [Migration](#migration)
|
|
34
|
+
- [Development](#development)
|
|
35
|
+
|
|
36
|
+
More: [Overview](./docs/overview.md) · [Quick start](./docs/quick-start.md) · [Components](./docs/components.md) · [Generators](./docs/generators.md) · [Reference](./docs/reference.md)
|
|
37
|
+
|
|
38
|
+
## Quick start
|
|
20
39
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
40
|
+
```vue
|
|
41
|
+
<script setup lang="ts">
|
|
42
|
+
import { useSettingsForm } from '@aleph-alpha/settings-kit';
|
|
43
|
+
import type { SettingField, SettingsLayout } from '@aleph-alpha/settings-kit';
|
|
44
|
+
import { SkShell } from '@aleph-alpha/settings-kit/shell';
|
|
45
|
+
|
|
46
|
+
const props = defineProps<{ fields: SettingField[]; layout: SettingsLayout }>();
|
|
47
|
+
|
|
48
|
+
const session = useSettingsForm({
|
|
49
|
+
fields: () => props.fields,
|
|
50
|
+
async onSave(changes) {
|
|
51
|
+
await api.saveSettings(changes); // { 'branding.appName': 'Phoenix', … }
|
|
52
|
+
},
|
|
25
53
|
});
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<template>
|
|
57
|
+
<SkShell :session="session" :layout="layout" base="/settings" back-href="/" />
|
|
58
|
+
</template>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
That renders the sidebar navigation, one page per URL (`/settings/<category>/<page>`), every field with its validation, and the page's Discard / Apply buttons, which call `session.discard()` and `session.save()`.
|
|
62
|
+
|
|
63
|
+
Without a router or chrome, render one page with `SkLayout`:
|
|
64
|
+
|
|
65
|
+
```vue
|
|
66
|
+
<SkLayout v-model:page="pageId" :session="session" :layout="layout" />
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Layers
|
|
70
|
+
|
|
71
|
+
Each layer only depends on the ones below it, so you can stop at any level and build the rest yourself.
|
|
72
|
+
|
|
73
|
+
```mermaid
|
|
74
|
+
flowchart TD
|
|
75
|
+
shell["Shell — SkShell, useShellLocation<br/>chrome, navigation, URLs (vue-router peer)"]
|
|
76
|
+
components["Components — SkLayout, SkPage/SkSection/SkGroup/SkItem, SkField<br/>default markup, accessible by default"]
|
|
77
|
+
composables["Composables — useSettingsForm, useSettingsLayout, useSettingField, useFieldValidator<br/>reactive state, no markup"]
|
|
78
|
+
core["Core — resolveLayout, validateLayout, toNavigation, rules, constraints, control registry<br/>plain TypeScript, no Vue reactivity"]
|
|
79
|
+
shell --> components --> composables --> core
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
| Layer | Use it when | Main exports |
|
|
83
|
+
| ----------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
84
|
+
| Core | You need the data model only: a server check, a custom renderer, tests. | `resolveLayout`, `validateLayout`, `toNavigation`, `evaluateFieldRules`, `checkFieldConstraints`, `provideControls` |
|
|
85
|
+
| Composables | You render your own markup but want the kit's state, rules and validation. | `useSettingsForm`, `createStateManager`, `useSettingsLayout`, `useSettingField`, `useFieldValidator` |
|
|
86
|
+
| Components | You want the default page markup inside your own frame. | `SkLayout`, `SkField`, `SkPage`, `SkSection`, `SkGroup`, `SkItem` |
|
|
87
|
+
| Shell | You want the whole settings screen: top bar, sidebar navigation and one URL per page. | `SkShell` from `@aleph-alpha/settings-kit/shell` |
|
|
88
|
+
|
|
89
|
+
**Core** is pure: `resolveLayout(layout, { fieldByKey, valueOf })` turns the layout JSON into a `ResolvedLayout` tree — ordered, filtered by rules, with labels resolved for a locale and ids assigned. Everything above renders that tree; nothing re-implements ordering or rules.
|
|
90
|
+
|
|
91
|
+
**Composables** wrap the core in reactivity. `useSettingsLayout` is the heart of it: it connects a layout to a state manager and returns the resolved pages, the navigation, layout issues and validation errors. See [State manager integration](#state-manager-integration).
|
|
92
|
+
|
|
93
|
+
**Components** render a resolved page. `SkLayout` = `useSettingsLayout` + the page renderer. The four blocks (`SkPage`, `SkSection`, `SkGroup`, `SkItem`) are presentational and replaceable; `SkField` renders one setting with its control.
|
|
94
|
+
|
|
95
|
+
**Shell** adds the frame and routing. It is a separate entry point because it is the only part that touches `vue-router`, which is an optional peer.
|
|
96
|
+
|
|
97
|
+
## The schema
|
|
98
|
+
|
|
99
|
+
### Field
|
|
100
|
+
|
|
101
|
+
A `SettingField` is one setting. The layout only refers to it by `key`.
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"key": "branding.appName",
|
|
106
|
+
"type": "string",
|
|
107
|
+
"label": { "en": "Application name" },
|
|
108
|
+
"description": { "en": "Shown in the browser tab." },
|
|
109
|
+
"value": "Phoenix chat",
|
|
110
|
+
"default": "",
|
|
111
|
+
"constraints": { "required": true, "maxLength": 30 },
|
|
112
|
+
"presentation": { "control": "text", "charCounter": true },
|
|
113
|
+
"rules": [],
|
|
114
|
+
"errors": { "required": { "en": "Give the application a name." } }
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
| Property | Meaning |
|
|
119
|
+
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
120
|
+
| `key` | Unique id of the setting. Dots are just characters (`branding.appName`), not nesting. |
|
|
121
|
+
| `type` | Stored value type: `string`, `number`, `boolean`, `asset` (`asset://…` or `null`), or `actions` (no value — see [Actions](#actions)). |
|
|
122
|
+
| `label` | Localised name; required for every type except `actions`, so every input has an accessible name. |
|
|
123
|
+
| `value` | Stored value from the server. Edits are staged separately until saved. |
|
|
124
|
+
| `default` | Value used when `value` is absent. |
|
|
125
|
+
| `editable` | `false` disables the control. |
|
|
126
|
+
| `constraints` | Client validation: `required`, `maxLength`, `pattern`, `format`, `enum`, `mimeTypes`. |
|
|
127
|
+
| `errors` | Localised message per constraint name, overriding the defaults. |
|
|
128
|
+
| `presentation` | How it renders: `control`, `placeholder`, `charCounter`, `options` (select and actions), `inheritLabel`/`inheritHint` (fallback toggle). |
|
|
129
|
+
| `fallback` | Inherit another setting's value while a toggle is on. See [Fallback](#fallback). |
|
|
130
|
+
| `rules` | Show/hide and enable/disable from other settings' values. See [Rules](#rules). |
|
|
131
|
+
|
|
132
|
+
`presentation.control` picks the control: `text`, `select`, `checkbox`, `color`, `phone`, `period`, `image-upload`, `actions`, or any name you register with [`provideControls`](#controls). Without one, the type decides (`string` → text, `boolean` → checkbox, `asset` → image upload, `actions` → actions).
|
|
133
|
+
|
|
134
|
+
`LocalizedText` is `Record<locale, string>`. Call `provideSettingsLocale({ locale, fallbackLocale })` to choose the language. The kit uses the first non-empty text in this order: `locale`, `fallbackLocale`, `en`, any translation. See [Labels](#labels).
|
|
135
|
+
|
|
136
|
+
### Layout: category, page, section, group, item
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"categories": [
|
|
141
|
+
{
|
|
142
|
+
"uid": "appearance",
|
|
143
|
+
"label": { "en": "Appearance" },
|
|
144
|
+
"order": 1,
|
|
145
|
+
"pages": [
|
|
146
|
+
{
|
|
147
|
+
"uid": "branding",
|
|
148
|
+
"label": { "en": "Branding" },
|
|
149
|
+
"description": { "en": "How the application looks for your team." },
|
|
150
|
+
"order": 1,
|
|
151
|
+
"presentation": { "sidebar": true, "sidebarMode": "default" },
|
|
152
|
+
"sections": [
|
|
153
|
+
{
|
|
154
|
+
"uid": "general",
|
|
155
|
+
"order": 1,
|
|
156
|
+
"presentation": { "showHeader": false },
|
|
157
|
+
"items": [{ "key": "branding.appName" }]
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"uid": "visual-style",
|
|
161
|
+
"label": { "en": "Visual style" },
|
|
162
|
+
"order": 2,
|
|
163
|
+
"items": [{ "key": "branding.actions" }],
|
|
164
|
+
"groups": [
|
|
165
|
+
{
|
|
166
|
+
"uid": "colors",
|
|
167
|
+
"label": { "en": "Colors" },
|
|
168
|
+
"order": 1,
|
|
169
|
+
"layout": { "columns": 2 },
|
|
170
|
+
"items": [
|
|
171
|
+
{ "key": "branding.brandColor" },
|
|
172
|
+
{ "key": "branding.accentColor", "column": 2 }
|
|
173
|
+
]
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
| Level | What it is | Renders as | Properties |
|
|
186
|
+
| ------------ | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
187
|
+
| **Category** | A navigation group of pages. Never a screen of its own. | A labelled group in the sidebar navigation. | `uid`, `label`, `order`, `pages` |
|
|
188
|
+
| **Page** | One screen and one URL (`/<category>/<page>`). Also the save boundary for its actions. | `SkPage`: a `<form>` with heading, sections and the actions footer. | `uid`, `label`, `description?`, `order`, `presentation?` (`showHeader`, `sidebar`, `sidebarMode`: `default` 8/4, `wide` 7/5, `max` 6/6), `sections` |
|
|
189
|
+
| **Section** | A titled block of a page. Holds items directly, groups, or both. | `SkSection`: heading + grid. | `uid`, `label?`, `description?`, `order`, `presentation?.showHeader`, `layout?.columns`, `items?`, `groups?` |
|
|
190
|
+
| **Group** | A labelled cluster inside a section, with its own grid. | `SkGroup`: sub-heading + grid. | `uid`, `label?`, `description?`, `order`, `presentation?.showHeader`, `layout?.columns`, `items` |
|
|
191
|
+
| **Item** | A placement of one field. The only level that points at a field. | `SkItem`: one grid cell around `SkField`. | `key`, `order?`, `column?` (1-based pin) |
|
|
192
|
+
|
|
193
|
+
- **Labels are optional below the page.** A section or group without a `label`, or with `showHeader: false`, renders no heading. In the navigation, an unlabelled section or group hands its children to its parent.
|
|
194
|
+
- **Grids.** `layout.columns` (default 1) applies from the small breakpoint up; below it every grid is one column, in DOM order. `column` pins an item to a column at that breakpoint; pins outside `1…columns` are dropped.
|
|
195
|
+
- **Sidebar.** `presentation.sidebar` reserves the page's content sidebar; the host fills it through the `sidebar` slot of `SkShell`.
|
|
196
|
+
|
|
197
|
+
### Actions
|
|
198
|
+
|
|
199
|
+
The page buttons are a field too, so the backend decides which buttons exist:
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"key": "branding.actions",
|
|
204
|
+
"type": "actions",
|
|
205
|
+
"presentation": {
|
|
206
|
+
"options": [
|
|
207
|
+
{ "value": "discard", "label": { "en": "Discard" } },
|
|
208
|
+
{ "value": "apply", "label": { "en": "Apply changes" } }
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
- Place it like any item. Wherever it is placed on a page, it renders **once, in that page's footer**, not inline. A page without one has no buttons.
|
|
215
|
+
- Each option is a button; its `value` is the **method** sent with the [action event](#actions-at-runtime). `discard` and `apply` have built-in behaviour; any other name (`export`, `reset-defaults`, …) is yours to handle.
|
|
216
|
+
- The last button is the form's submit button, so <kbd>Enter</kbd> in a text field triggers it.
|
|
217
|
+
- `rules` work as on any field: hidden actions don't render, disabled ones render disabled. All buttons are also disabled while saving.
|
|
218
|
+
|
|
219
|
+
### Fallback
|
|
220
|
+
|
|
221
|
+
A field can inherit another setting's value while a toggle is on — for example, the favicon reuses the logo:
|
|
222
|
+
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"key": "branding.favicon",
|
|
226
|
+
"type": "asset",
|
|
227
|
+
"label": { "en": "Favicon" },
|
|
228
|
+
"value": null,
|
|
229
|
+
"fallback": { "key": "branding.logo" },
|
|
230
|
+
"presentation": { "inheritLabel": { "en": "Same as logo" } }
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
- `SkField` renders the toggle (labelled `inheritLabel`) above the control. While it is on, the field shows and saves the source's value; turning it off restores the field's own value.
|
|
235
|
+
- On an `asset` field, a stored `null` means "inherit", so the toggle starts on.
|
|
236
|
+
- Fallbacks chain (`sales.phone` → `support.phone` → `org.phone`). `session.field(key).inheritedFrom` returns the final source.
|
|
237
|
+
- The toggle state lives in the state manager, so it survives page changes and is reset by `discard`.
|
|
238
|
+
|
|
239
|
+
### Rules
|
|
240
|
+
|
|
241
|
+
A rule lives on the field it governs and names the controlling setting(s) inside `when`:
|
|
242
|
+
|
|
243
|
+
```json
|
|
244
|
+
{
|
|
245
|
+
"key": "accessibility.motionLevel",
|
|
246
|
+
"type": "string",
|
|
247
|
+
"label": { "en": "Motion level" },
|
|
248
|
+
"rules": [
|
|
249
|
+
{
|
|
250
|
+
"effect": "SHOW",
|
|
251
|
+
"when": { "key": "accessibility.reduceMotion", "equals": true }
|
|
252
|
+
}
|
|
253
|
+
]
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
| Effect | Meaning |
|
|
258
|
+
| --------- | ----------------------------------- |
|
|
259
|
+
| `SHOW` | Hidden until the condition holds. |
|
|
260
|
+
| `HIDE` | Shown until the condition holds. |
|
|
261
|
+
| `ENABLE` | Disabled until the condition holds. |
|
|
262
|
+
| `DISABLE` | Enabled until the condition holds. |
|
|
263
|
+
|
|
264
|
+
- **Conditions** compare current, staged values: `equals`, `notEquals`, `in`, `notIn`, `gt`, `gte`, `lt`, `lte`, `isEmpty`, `notEmpty` (several on one leaf are ANDed), and combine with `{ allOf: [] }`, `{ anyOf: [] }`, `{ not: {} }`. Pure data — nothing is evaluated as code.
|
|
265
|
+
- **Several rules all apply**: any hiding rule wins over showing, any disabling rule over enabling.
|
|
266
|
+
- **In the layout**, hidden items are removed from the resolved tree, and a group or section left empty disappears with them. Navigation and validation only see what is visible, so a hidden required field never blocks saving.
|
|
267
|
+
- `validateFieldRules(fields)` throws on a rule naming an unknown key, and on cycles across rules and fallbacks (A gates B gates A).
|
|
268
|
+
|
|
269
|
+
### Identity, ordering and validation
|
|
270
|
+
|
|
271
|
+
- **Ids.** Every resolved node has an `id`: the URI-encoded uid path, e.g. `appearance/branding/colors`. A page id (`appearance/branding`) is what `v-model:page` takes and what the URL shows.
|
|
272
|
+
- **Uids** must be non-empty and unique among siblings. If two siblings share one, the first wins and the rest are skipped.
|
|
273
|
+
- **Keys** may be placed once in the whole layout. Unknown keys and repeated placements are skipped.
|
|
274
|
+
- **Order.** Siblings sort by `order` ascending; ties keep array order. Items without `order` keep their array position and come before the section's groups; give items an `order` to interleave them with groups.
|
|
275
|
+
|
|
276
|
+
`validateLayout(layout, fields)` reports every one of these as a `LayoutIssue` (`{ code, severity, path, message }`) instead of skipping silently. `useSettingsLayout(...).issues` runs it for you — log it in development or reject the payload server-side.
|
|
277
|
+
|
|
278
|
+
| Code | Severity | Meaning |
|
|
279
|
+
| ---------------- | -------- | ------------------------------------------------------------------------------- |
|
|
280
|
+
| `empty-uid` | error | A category, page, section or group has an empty `uid`. |
|
|
281
|
+
| `duplicate-uid` | error | Two siblings share a `uid`; only the first renders. |
|
|
282
|
+
| `unknown-key` | error | An item names a key no field has. |
|
|
283
|
+
| `duplicate-key` | error | A key is placed twice; only the first renders. |
|
|
284
|
+
| `invalid-column` | warning | `column` is outside the parent's `columns`; the pin is dropped. |
|
|
285
|
+
| `path-collision` | warning | Two page URLs differ only in letter case, so they can't be told apart in a URL. |
|
|
286
|
+
| `unplaced-field` | warning | A field is not placed anywhere, so it can never be edited. |
|
|
287
|
+
|
|
288
|
+
## State manager integration
|
|
289
|
+
|
|
290
|
+
The layout never owns values. Every field the layout shows reads from and stages into one **state manager** (`createStateManager`), the same one `useSettingsForm` creates and provides.
|
|
291
|
+
|
|
292
|
+
```mermaid
|
|
293
|
+
flowchart LR
|
|
294
|
+
api[(Backend)] -- fields --> sm["State manager<br/>useSettingsForm"]
|
|
295
|
+
api -- layout --> sl["useSettingsLayout"]
|
|
296
|
+
sm -- fieldByKey / valueOf --> sl
|
|
297
|
+
sl -- resolved page --> ui["SkLayout / SkShell"]
|
|
298
|
+
ui -- edits --> sm
|
|
299
|
+
ui -- "action: apply" --> sm
|
|
300
|
+
sm -- "onSave(changes)" --> api
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
`SkLayout`, `SkShell` and `useSettingsLayout` pick the state manager in this order:
|
|
304
|
+
|
|
305
|
+
1. the `session` prop;
|
|
306
|
+
2. the nearest `useSettingsForm` up the component tree;
|
|
307
|
+
3. a private one built from the `fields` prop (a quick demo; nothing is saved).
|
|
308
|
+
|
|
309
|
+
Fields passed through `fields` that the state manager doesn't know yet are registered into it, so a layout can bring its own fields into a shared form.
|
|
310
|
+
|
|
311
|
+
Because the state lives in the state manager, not in components:
|
|
312
|
+
|
|
313
|
+
- leaving a page and coming back keeps unsaved edits;
|
|
314
|
+
- `session.isDirty`, `session.save()`, `session.discard()` and `session.field(key)` all reflect what the layout shows;
|
|
315
|
+
- several layouts (or a layout plus your own `useSettingField` forms) can share one save.
|
|
316
|
+
|
|
317
|
+
Validation runs over the placed, visible, non-action fields of the layout. Client errors appear once a field is edited, or for every field after a failed Apply. Server issues (`model.validator.applyServerIssues(...)`) show immediately.
|
|
318
|
+
|
|
319
|
+
To render your own markup with the same behaviour, use the composable directly:
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
const model = useSettingsLayout({ layout: () => props.layout, session });
|
|
323
|
+
|
|
324
|
+
model.pages.value; // ResolvedPage[] in display order
|
|
325
|
+
model.page('appearance/branding');
|
|
326
|
+
model.navigation.value; // LayoutNavigationNode[] with `invalid` flags
|
|
327
|
+
model.issues.value; // LayoutIssue[]
|
|
328
|
+
model.errorsByKey.value; // Record<key, string[]> to show right now
|
|
329
|
+
model.revealErrors(); // show client errors for untouched fields too
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
`useSettingsLayout` reads the locale from `provideSettingsLocale` in a parent component. If the same component provides the locale, pass it: `useSettingsLayout({ layout, session, settingsLocale })`, where `settingsLocale` is the value that `provideSettingsLocale` returns.
|
|
333
|
+
|
|
334
|
+
## Actions at runtime
|
|
335
|
+
|
|
336
|
+
Every button press emits one `action` event from `SkLayout` / `SkShell`:
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
interface SkLayoutActionEvent {
|
|
340
|
+
method: string; // the option value, e.g. 'apply'
|
|
341
|
+
key: string; // the actions field, e.g. 'branding.actions'
|
|
342
|
+
pageId: string; // e.g. 'appearance/branding'
|
|
343
|
+
preventDefault(): void;
|
|
344
|
+
readonly defaultPrevented: boolean;
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Your listener runs first. Unless it calls `preventDefault()`, the built-in behaviour follows:
|
|
349
|
+
|
|
350
|
+
| Method | Built-in behaviour |
|
|
351
|
+
| --------- | ------------------------------------------------------------------------------------------------- |
|
|
352
|
+
| `discard` | `session.discard()`, clear server issues, hide revealed errors. |
|
|
353
|
+
| `apply` | Validate. If invalid, reveal every error. Otherwise `session.save()` (your `onSave`), then reset. |
|
|
354
|
+
| other | Nothing — handle it in your listener. |
|
|
355
|
+
|
|
356
|
+
A failed save emits `error` with what `onSave` threw.
|
|
357
|
+
|
|
358
|
+
```vue
|
|
359
|
+
<SkShell
|
|
360
|
+
:session="session"
|
|
361
|
+
:layout="layout"
|
|
362
|
+
@action="onAction"
|
|
363
|
+
@error="toast.error"
|
|
364
|
+
/>
|
|
26
365
|
```
|
|
27
366
|
|
|
367
|
+
```ts
|
|
368
|
+
function onAction(event: SkLayoutActionEvent) {
|
|
369
|
+
if (event.method === 'apply' && !window.confirm('Publish to all users?')) {
|
|
370
|
+
event.preventDefault();
|
|
371
|
+
}
|
|
372
|
+
if (event.method === 'export') downloadSettings(event.pageId);
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Routing (SkShell)
|
|
377
|
+
|
|
378
|
+
`SkShell` publishes each page at `${base}/<category>/<page>`. Paths are always built from the uids; the `base` prop (default `''`) is the prefix.
|
|
379
|
+
|
|
380
|
+
- **With a host `vue-router`**, render `SkShell` in a route that covers its sub-paths (`{ path: '/settings/:page(.*)*', component: SettingsView }`). The shell navigates with your router, only while its own route is active, and returns to the actual location if a guard cancels navigation.
|
|
381
|
+
- **Without a router**, it uses the browser history directly, and never rewrites URLs outside `base`.
|
|
382
|
+
- Links are real `<a href>`s, so open-in-new-tab and copy-link work; plain clicks navigate in place.
|
|
383
|
+
- URL matching ignores letter case and a trailing slash, and a non-canonical URL is replaced by the canonical one, keeping `?query` and `#hash`.
|
|
384
|
+
- An unknown path inside `base` falls back to the first page.
|
|
385
|
+
- `v-model:page` reads and sets the page id, two-way with the URL.
|
|
386
|
+
- Inside `<KeepAlive>`, a deactivated shell stops following the URL and catches up when reactivated.
|
|
387
|
+
|
|
388
|
+
## Customisation
|
|
389
|
+
|
|
390
|
+
### Blocks
|
|
391
|
+
|
|
392
|
+
Replace the component used at any level; the rest keep their defaults:
|
|
393
|
+
|
|
28
394
|
```vue
|
|
29
|
-
<
|
|
395
|
+
<SkLayout :layout="layout" :blocks="{ section: MyCard, item: MyItem }" />
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
A block receives the same props as the default (`SkPage`, `SkSection`, `SkGroup`, `SkItem`) and renders its default slot. Keep the `level` prop on your heading so the outline stays sequential.
|
|
399
|
+
|
|
400
|
+
### Controls
|
|
401
|
+
|
|
402
|
+
Register a control for a `presentation.control` name. Registrations merge over the parent's, so you can override a built-in or add your own:
|
|
403
|
+
|
|
404
|
+
```ts
|
|
405
|
+
import { provideControls } from '@aleph-alpha/settings-kit';
|
|
406
|
+
|
|
407
|
+
provideControls({
|
|
408
|
+
slider: {
|
|
409
|
+
component: MySlider,
|
|
410
|
+
kind: 'input', // edits a value; 'button' emits `action` instead
|
|
411
|
+
props: (field, { label }) => ({ label, max: field.presentation?.max }),
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
A field with `presentation: { control: 'slider' }` now renders `MySlider` with `v-model` wired by `SkField`; `props` maps the schema to the component's props. Unknown names render a visible "unsupported control" placeholder.
|
|
417
|
+
|
|
418
|
+
### Labels
|
|
419
|
+
|
|
420
|
+
Call `provideSettingsLocale({ locale, fallbackLocale, messages })` once, in a parent of the shell. Schema text and the kit wording (`en` and `de`) follow it. The chrome text of `SkShell` (title, back, navigation, "has errors", …) uses the `shell.*` keys of the kit wording. Set `labels` to replace single entries. `labels` wins over `messages`. `defaultShellLabels` holds the `en` text.
|
|
421
|
+
|
|
422
|
+
## Accessibility
|
|
423
|
+
|
|
424
|
+
- Heading levels follow the structure: page = `headingLevel` (default 1), sections one below, groups one below a titled section. Set `headingLevel` when the shell sits under an existing `h1`.
|
|
425
|
+
- Each page is a `<form novalidate>`; errors are announced per field and linked with `aria-errormessage`.
|
|
426
|
+
- The navigation marks the current page with `aria-current="page"`, and pages with errors get a visible dot plus a screen-reader text ("has errors").
|
|
427
|
+
- Grids reflow to one column at narrow widths and 400% zoom, in DOM order, so the reading order never depends on the column pin.
|
|
428
|
+
- Without `backHref` and without a `back` listener, no back control is rendered.
|
|
429
|
+
|
|
430
|
+
## Styles
|
|
431
|
+
|
|
432
|
+
Components use UnoCSS classes from the Aleph Alpha preset; the kit ships no CSS file. Add the kit's `dist` to your UnoCSS content so its classes are generated, as you already do for the ui-library:
|
|
433
|
+
|
|
434
|
+
```ts
|
|
435
|
+
// uno.config.ts
|
|
436
|
+
content: {
|
|
437
|
+
filesystem: [
|
|
438
|
+
'./node_modules/@aleph-alpha/ui-library/dist/**/*.js',
|
|
439
|
+
'./node_modules/@aleph-alpha/settings-kit/dist/**/*.js',
|
|
440
|
+
],
|
|
441
|
+
},
|
|
30
442
|
```
|
|
31
443
|
|
|
444
|
+
## Entry points
|
|
445
|
+
|
|
446
|
+
| Import | Contents | Peers |
|
|
447
|
+
| -------------------------------------- | --------------------------------------------------------- | -------------------------------- |
|
|
448
|
+
| `@aleph-alpha/settings-kit` | Core, composables, components and all types. | `vue`, `@aleph-alpha/ui-library` |
|
|
449
|
+
| `@aleph-alpha/settings-kit/components` | Components only. | same |
|
|
450
|
+
| `@aleph-alpha/settings-kit/shell` | `SkShell`, `SkShellFrame`, `SkShellNav`, routing helpers. | + `vue-router` (optional peer) |
|
|
451
|
+
| `@aleph-alpha/settings-kit/generators` | `defineSettingUnit`, `createToggleUnit` (alias `/units`). | none |
|
|
452
|
+
|
|
453
|
+
## Migration
|
|
454
|
+
|
|
455
|
+
From the earlier preview of the layout builder:
|
|
456
|
+
|
|
457
|
+
| Before | Now |
|
|
458
|
+
| ---------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
459
|
+
| `SkCategory`, `SkCategoryNav`, `SkFormActions` | `SkLayout` (one page) or `SkShell` (screen with navigation). |
|
|
460
|
+
| `useBlockGenerator` | `useSettingsLayout`, or `resolveLayout` in plain TypeScript. |
|
|
461
|
+
| `@discard` / `@apply` events | One `@action` event with `method`; built-in handling unless `preventDefault()`. |
|
|
462
|
+
| `fields` prop holding the state | Values live in the state manager (`session` prop or `useSettingsForm`). |
|
|
463
|
+
| URLs by label, `SkShell` from the root entry | `/<category>/<page>` from uids; import `SkShell` from `/shell`. |
|
|
464
|
+
| `valueFrom` on rules, `faviconSameAsLogo` | `fallback: { key }` on the inheriting field. |
|
|
465
|
+
| `page.label.en` in slots | `page.label` (already resolved). |
|
|
466
|
+
|
|
32
467
|
## Development
|
|
33
468
|
|
|
34
469
|
```bash
|
|
@@ -37,4 +472,19 @@ npx nx run settings-kit:test # Unit tests (Vitest)
|
|
|
37
472
|
npx nx run settings-kit:lint # Prettier + ESLint
|
|
38
473
|
npx nx run settings-kit:build # Library build + type declarations
|
|
39
474
|
npx nx run settings-kit:test-storybook # Storybook interaction & a11y tests
|
|
475
|
+
pnpm --filter @aleph-alpha/settings-kit playground # Live demo against the source
|
|
40
476
|
```
|
|
477
|
+
|
|
478
|
+
The kit builds against the **published** `@aleph-alpha/ui-library` from npm, not the workspace copy, so it always matches what consumers install.
|
|
479
|
+
|
|
480
|
+
## Releasing
|
|
481
|
+
|
|
482
|
+
Releases are automated with [Release Please](https://github.com/googleapis/release-please), like every other package in this repo:
|
|
483
|
+
|
|
484
|
+
1. Merge a PR to `main` with a conventional commit scoped to the kit (`feat(settings-kit): …`, `fix(settings-kit): …`). Only commits touching `packages/settings-kit/**` count.
|
|
485
|
+
2. Release Please opens or updates the `chore(release): settings-kit <version>` PR with the version bump and `CHANGELOG.md` entry.
|
|
486
|
+
3. Merging that PR tags `settings-kit-<version>` and the `Release` workflow publishes `@aleph-alpha/settings-kit` to npm.
|
|
487
|
+
|
|
488
|
+
While the kit is below `1.0.0`, a breaking change (`feat!:` or a `BREAKING CHANGE:` footer) bumps the major version to `1.0.0`.
|
|
489
|
+
|
|
490
|
+
See [GitHub CI Flow](../../.github/documentation/GITHUB_CI_FLOW.md#release-flow) for the full pipeline and troubleshooting.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { defineComponent, useModel, computed, openBlock, createBlock, unref, renderSlot, mergeModels } from "vue";
|
|
2
|
+
import { H as useSettingsLayout, r as provideSkLayoutContext, S as SkLayoutPage } from "./SkLayoutPage-DGbCpQFm.js";
|
|
3
|
+
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
4
|
+
...{
|
|
5
|
+
name: "SkLayout"
|
|
6
|
+
},
|
|
7
|
+
__name: "SkLayout",
|
|
8
|
+
props: /* @__PURE__ */ mergeModels({
|
|
9
|
+
layout: {},
|
|
10
|
+
session: { default: void 0 },
|
|
11
|
+
fields: { default: void 0 },
|
|
12
|
+
validators: { default: void 0 },
|
|
13
|
+
blocks: { default: void 0 },
|
|
14
|
+
headingLevel: { default: 1 }
|
|
15
|
+
}, {
|
|
16
|
+
"page": {},
|
|
17
|
+
"pageModifiers": {}
|
|
18
|
+
}),
|
|
19
|
+
emits: /* @__PURE__ */ mergeModels(["action", "error"], ["update:page"]),
|
|
20
|
+
setup(__props, { expose: __expose, emit: __emit }) {
|
|
21
|
+
const props = __props;
|
|
22
|
+
const page = useModel(__props, "page");
|
|
23
|
+
const emit = __emit;
|
|
24
|
+
const model = useSettingsLayout({
|
|
25
|
+
layout: () => props.layout,
|
|
26
|
+
session: props.session,
|
|
27
|
+
fields: () => props.fields,
|
|
28
|
+
validators: props.validators
|
|
29
|
+
});
|
|
30
|
+
provideSkLayoutContext(model, {
|
|
31
|
+
blocks: () => props.blocks,
|
|
32
|
+
headingLevel: () => props.headingLevel,
|
|
33
|
+
onAction: (event) => emit("action", event),
|
|
34
|
+
onError: (error) => emit("error", error)
|
|
35
|
+
});
|
|
36
|
+
const current = computed(() => model.page(page.value) ?? model.pages.value[0]);
|
|
37
|
+
__expose({
|
|
38
|
+
/** The layout model: session, resolved tree, navigation, validator. */
|
|
39
|
+
model
|
|
40
|
+
});
|
|
41
|
+
return (_ctx, _cache) => {
|
|
42
|
+
return current.value ? (openBlock(), createBlock(unref(SkLayoutPage), {
|
|
43
|
+
key: current.value.id,
|
|
44
|
+
page: current.value
|
|
45
|
+
}, null, 8, ["page"])) : renderSlot(_ctx.$slots, "empty", { key: 1 });
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
export {
|
|
50
|
+
_sfc_main as _
|
|
51
|
+
};
|