slidev-theme-practicum 0.1.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 +270 -0
- package/components/DebugGrid.vue +50 -0
- package/components/DecorLibrary.vue +1117 -0
- package/components/Header.vue +124 -0
- package/components/Image.vue +82 -0
- package/components/ImageRenderer.vue +126 -0
- package/components/Logo.vue +142 -0
- package/components/Person.vue +89 -0
- package/components/Slide.vue +324 -0
- package/components/Slot.vue +419 -0
- package/components/Text.vue +216 -0
- package/components/Timeline.vue +202 -0
- package/composables/decor-catalog.mjs +339 -0
- package/composables/decor-config.mjs +488 -0
- package/composables/decor-file-store.ts +192 -0
- package/composables/decor-http-store.ts +25 -0
- package/composables/decor-store.ts +39 -0
- package/composables/decor-tuning-overrides.mjs +2 -0
- package/composables/decor-workbench.ts +799 -0
- package/composables/grid-placement.ts +396 -0
- package/composables/image-config.mjs +484 -0
- package/composables/layout-authoring.ts +372 -0
- package/composables/layout-markdown.mjs +56 -0
- package/composables/layout-recipes.ts +347 -0
- package/composables/layout-registry.ts +47 -0
- package/composables/layout-shorthands.ts +891 -0
- package/composables/layout-vnode.ts +43 -0
- package/composables/logo-mark.mjs +5 -0
- package/composables/slide-layout.ts +43 -0
- package/composables/slot-placement.ts +195 -0
- package/composables/slot-rules.ts +147 -0
- package/composables/text-fit-runtime.ts +577 -0
- package/composables/text-priority-fit.mjs +166 -0
- package/composables/text-typography.mjs +138 -0
- package/composables/theme-foundation.ts +192 -0
- package/composables/theme-media.mjs +202 -0
- package/composables/use-theme-config.ts +71 -0
- package/decor-library.md +10 -0
- package/env.d.ts +6 -0
- package/example.md +786 -0
- package/layouts/collection.vue +5 -0
- package/layouts/cover.vue +5 -0
- package/layouts/default.vue +5 -0
- package/layouts/explainer.vue +5 -0
- package/layouts/message.vue +5 -0
- package/layouts/none.vue +5 -0
- package/layouts/section.vue +5 -0
- package/layouts/two-cols.vue +11 -0
- package/package.json +84 -0
- package/public/decor/decor-1.svg +83 -0
- package/public/decor/decor-10.svg +40 -0
- package/public/decor/decor-11.svg +162 -0
- package/public/decor/decor-12.svg +83 -0
- package/public/decor/decor-13.svg +5 -0
- package/public/decor/decor-14.svg +7 -0
- package/public/decor/decor-15.svg +10 -0
- package/public/decor/decor-16.svg +9 -0
- package/public/decor/decor-17.png +0 -0
- package/public/decor/decor-18.png +0 -0
- package/public/decor/decor-19.svg +5 -0
- package/public/decor/decor-2.png +0 -0
- package/public/decor/decor-3.svg +9 -0
- package/public/decor/decor-4.svg +31 -0
- package/public/decor/decor-5.png +0 -0
- package/public/decor/decor-6.png +0 -0
- package/public/decor/decor-7.png +0 -0
- package/public/decor/decor-8.svg +21 -0
- package/public/decor/decor-9.svg +38 -0
- package/public/favicon.svg +11 -0
- package/public/photos/photo-1.png +0 -0
- package/public/photos/photo-10.png +0 -0
- package/public/photos/photo-2.png +0 -0
- package/public/photos/photo-3.png +0 -0
- package/public/photos/photo-4.png +0 -0
- package/public/photos/photo-5.png +0 -0
- package/public/photos/photo-6.png +0 -0
- package/public/photos/photo-7.png +0 -0
- package/public/photos/photo-8.png +0 -0
- package/public/photos/photo-9.png +0 -0
- package/setup/main.ts +3 -0
- package/setup/shiki.ts +10 -0
- package/setup/vite-plugins.ts +106 -0
- package/slidev-theme-practicum.png +0 -0
- package/slidev-theme-practicum.svg +102 -0
- package/styles/example.css +0 -0
- package/styles/index.css +192 -0
- package/styles/vars.css +119 -0
- package/types/slidev-client.d.ts +15 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Comment, Text as NativeText, type VNode } from 'vue'
|
|
2
|
+
|
|
3
|
+
export function camelToKebab(key: string) {
|
|
4
|
+
return key.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function readVNodeProp<T = unknown>(node: VNode | null | undefined, key: string) {
|
|
8
|
+
const nodeProps = (node?.props ?? {}) as Record<string, T | undefined>
|
|
9
|
+
if (key in nodeProps)
|
|
10
|
+
return nodeProps[key]
|
|
11
|
+
|
|
12
|
+
const kebabKey = camelToKebab(key)
|
|
13
|
+
if (kebabKey !== key && kebabKey in nodeProps)
|
|
14
|
+
return nodeProps[kebabKey]
|
|
15
|
+
|
|
16
|
+
return undefined
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function readVNodeTypeName(node: VNode | null | undefined) {
|
|
20
|
+
if (typeof node?.type === 'string')
|
|
21
|
+
return node.type
|
|
22
|
+
|
|
23
|
+
if (typeof node?.type === 'object' && node.type) {
|
|
24
|
+
const component = node.type as { name?: string, __name?: string }
|
|
25
|
+
return component.name ?? component.__name ?? 'anonymous-component'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return 'anonymous-node'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isBlankTextVNode(node: VNode | null | undefined) {
|
|
32
|
+
return node?.type === NativeText
|
|
33
|
+
&& typeof node.children === 'string'
|
|
34
|
+
&& !node.children.trim()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isCommentVNode(node: VNode | null | undefined) {
|
|
38
|
+
return node?.type === Comment
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isNamedComponentVNode(node: VNode | null | undefined, name: string, component?: unknown) {
|
|
42
|
+
return node?.type === component || readVNodeTypeName(node) === name
|
|
43
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const shortLogoMarkRectX = 45.8672
|
|
2
|
+
export const shortLogoMarkSize = 43
|
|
3
|
+
export const shortLogoMarkRadius = 16.125
|
|
4
|
+
export const shortLogoMarkTransform = `translate(-${shortLogoMarkRectX} 0)`
|
|
5
|
+
export const shortLogoMarkPath = 'M68.1769 5.26562L68.1769 19.9499L80.117 11.5894L81.0419 12.9103L68.978 21.3575L82.3292 27.5833L81.6477 29.0447L68.1761 22.7627V37.623H66.5636V30.7105L65.3632 37.5179L63.7752 37.2379L65.4808 27.5654L61.3627 36.3967L59.9012 35.7152L65.1864 24.3812L55.5328 32.4815L54.4963 31.2463L64.8914 22.5238L51.3749 23.7063L51.2344 22.1L65.3588 20.8643L54.4989 11.7517L55.5354 10.5164L66.5644 19.7709L66.5644 5.26562H68.1769Z'
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
compileLayoutAuthoring,
|
|
3
|
+
createLayoutSlotPlanState,
|
|
4
|
+
type CompiledLayoutAuthoring,
|
|
5
|
+
type LayoutAuthoringInput,
|
|
6
|
+
} from './layout-authoring'
|
|
7
|
+
import type { LayoutShorthandComponents } from './layout-shorthands'
|
|
8
|
+
import {
|
|
9
|
+
createSlotPlacementSession,
|
|
10
|
+
type SlotPlacementSession,
|
|
11
|
+
} from './slot-placement'
|
|
12
|
+
|
|
13
|
+
export type SlideLayoutInput = Omit<LayoutAuthoringInput, 'components' | 'slotPlanState'>
|
|
14
|
+
export type CompiledSlideLayout = CompiledLayoutAuthoring
|
|
15
|
+
|
|
16
|
+
export type SlideLayout = SlotPlacementSession & {
|
|
17
|
+
compile(input: SlideLayoutInput): CompiledSlideLayout
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createSlideLayout(input: {
|
|
21
|
+
components: LayoutShorthandComponents
|
|
22
|
+
}): SlideLayout {
|
|
23
|
+
const slotPlanState = createLayoutSlotPlanState()
|
|
24
|
+
let current: CompiledSlideLayout | null = null
|
|
25
|
+
const placement = createSlotPlacementSession({
|
|
26
|
+
layout: () => current?.slotPlan ?? null,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
compile(authoringInput) {
|
|
31
|
+
const compiled = compileLayoutAuthoring({
|
|
32
|
+
...authoringInput,
|
|
33
|
+
components: input.components,
|
|
34
|
+
slotPlanState,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
current = compiled
|
|
38
|
+
return compiled
|
|
39
|
+
},
|
|
40
|
+
register: placement.register,
|
|
41
|
+
snapshot: placement.snapshot,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { inject, provide } from 'vue'
|
|
2
|
+
import {
|
|
3
|
+
resolveGridPlacement,
|
|
4
|
+
type GridCursor,
|
|
5
|
+
type GridPlacedItem,
|
|
6
|
+
type GridPlacementInput,
|
|
7
|
+
type GridPlacementStyle,
|
|
8
|
+
type GridRect,
|
|
9
|
+
} from './grid-placement'
|
|
10
|
+
import type { LayoutSlotPlan, ResolvedLayoutSlot } from './layout-authoring'
|
|
11
|
+
import type { ThemeLayoutRole, ThemeLayoutSlotSpec } from './layout-registry'
|
|
12
|
+
import { createSlotRules } from './slot-rules'
|
|
13
|
+
|
|
14
|
+
export type GridFootprint = {
|
|
15
|
+
cols: number
|
|
16
|
+
rows: number
|
|
17
|
+
label: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type SlotPlacementInput = {
|
|
21
|
+
id: string
|
|
22
|
+
label?: string
|
|
23
|
+
role?: ThemeLayoutRole | ''
|
|
24
|
+
area?: string
|
|
25
|
+
col?: string
|
|
26
|
+
row?: string
|
|
27
|
+
overrides?: Partial<ThemeLayoutSlotSpec>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type ResolvedSlotPlacement = {
|
|
31
|
+
rect: GridRect
|
|
32
|
+
style: GridPlacementStyle
|
|
33
|
+
footprint: GridFootprint
|
|
34
|
+
layoutSlot: ResolvedLayoutSlot | null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type SlotPlacementHandle = {
|
|
38
|
+
update(input: SlotPlacementInput): void
|
|
39
|
+
dispose(): void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type SlotPlacementSnapshot = {
|
|
43
|
+
placements: readonly (ResolvedSlotPlacement & { id: string, label: string })[]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type SlotPlacementSession = {
|
|
47
|
+
register(input: SlotPlacementInput, onResolve: (result: ResolvedSlotPlacement) => void): SlotPlacementHandle
|
|
48
|
+
snapshot(): SlotPlacementSnapshot
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type SlotPlacementEntry = {
|
|
52
|
+
input: SlotPlacementInput
|
|
53
|
+
onResolve: (result: ResolvedSlotPlacement) => void
|
|
54
|
+
order: number
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const SlotPlacementSessionKey = Symbol('theme-slot-placement-session')
|
|
58
|
+
const slotRules = createSlotRules({ Slot: null })
|
|
59
|
+
|
|
60
|
+
function resolveEntry(input: SlotPlacementInput, layout: LayoutSlotPlan | null) {
|
|
61
|
+
const intent = slotRules.normalize(input, { layoutActive: Boolean(layout) })
|
|
62
|
+
|
|
63
|
+
if (intent.kind === 'manual') {
|
|
64
|
+
return {
|
|
65
|
+
label: intent.label,
|
|
66
|
+
layoutSlot: null,
|
|
67
|
+
placement: {
|
|
68
|
+
area: intent.area,
|
|
69
|
+
col: intent.col,
|
|
70
|
+
row: intent.row,
|
|
71
|
+
label: intent.label,
|
|
72
|
+
} satisfies GridPlacementInput,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const layoutSlot = layout!.resolveRoleSlot({
|
|
77
|
+
id: intent.id,
|
|
78
|
+
role: intent.role,
|
|
79
|
+
overrides: intent.overrides,
|
|
80
|
+
label: intent.label,
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
label: intent.label,
|
|
85
|
+
layoutSlot,
|
|
86
|
+
placement: {
|
|
87
|
+
area: layoutSlot.area,
|
|
88
|
+
label: intent.label,
|
|
89
|
+
} satisfies GridPlacementInput,
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function footprintFromRect(rect: GridRect, label: string): GridFootprint {
|
|
94
|
+
return {
|
|
95
|
+
cols: rect.colEnd - rect.colStart,
|
|
96
|
+
rows: rect.rowEnd - rect.rowStart,
|
|
97
|
+
label,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function createSlotPlacementSession(input: {
|
|
102
|
+
layout: () => LayoutSlotPlan | null
|
|
103
|
+
}): SlotPlacementSession {
|
|
104
|
+
const entries = new Map<string, SlotPlacementEntry>()
|
|
105
|
+
let nextOrder = 0
|
|
106
|
+
let currentSnapshot: SlotPlacementSnapshot = { placements: [] }
|
|
107
|
+
|
|
108
|
+
function recompute() {
|
|
109
|
+
const layout = input.layout()
|
|
110
|
+
const occupied: GridPlacedItem[] = []
|
|
111
|
+
let cursor: GridCursor = { row: 1, col: 1 }
|
|
112
|
+
const placements: (ResolvedSlotPlacement & { id: string, label: string })[] = []
|
|
113
|
+
|
|
114
|
+
const orderedEntries = [...entries.values()].sort((a, b) => a.order - b.order)
|
|
115
|
+
|
|
116
|
+
for (const entry of orderedEntries) {
|
|
117
|
+
const resolved = resolveEntry(entry.input, layout)
|
|
118
|
+
const result = resolveGridPlacement(resolved.placement, {
|
|
119
|
+
occupied,
|
|
120
|
+
cursor,
|
|
121
|
+
label: resolved.label,
|
|
122
|
+
})
|
|
123
|
+
const placement = {
|
|
124
|
+
rect: result.rect,
|
|
125
|
+
style: result.style,
|
|
126
|
+
footprint: footprintFromRect(result.rect, resolved.label),
|
|
127
|
+
layoutSlot: resolved.layoutSlot,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
occupied.push({
|
|
131
|
+
id: entry.input.id,
|
|
132
|
+
label: resolved.label,
|
|
133
|
+
...result.rect,
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
entry.onResolve(placement)
|
|
137
|
+
placements.push({
|
|
138
|
+
id: entry.input.id,
|
|
139
|
+
label: resolved.label,
|
|
140
|
+
...placement,
|
|
141
|
+
})
|
|
142
|
+
cursor = result.nextCursor
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
currentSnapshot = { placements }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
register(placementInput, onResolve) {
|
|
150
|
+
entries.set(placementInput.id, {
|
|
151
|
+
input: placementInput,
|
|
152
|
+
onResolve,
|
|
153
|
+
order: nextOrder++,
|
|
154
|
+
})
|
|
155
|
+
recompute()
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
update(nextInput) {
|
|
159
|
+
const current = entries.get(placementInput.id)
|
|
160
|
+
if (!current)
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
entries.set(placementInput.id, {
|
|
164
|
+
...current,
|
|
165
|
+
input: nextInput,
|
|
166
|
+
})
|
|
167
|
+
recompute()
|
|
168
|
+
},
|
|
169
|
+
dispose() {
|
|
170
|
+
input.layout()?.unregisterRoleSlot(placementInput.id)
|
|
171
|
+
if (!entries.delete(placementInput.id))
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
recompute()
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
snapshot() {
|
|
179
|
+
return currentSnapshot
|
|
180
|
+
},
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function provideSlotPlacementSession(session: SlotPlacementSession) {
|
|
185
|
+
provide(SlotPlacementSessionKey, session)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function useSlotPlacementSession() {
|
|
189
|
+
const session = inject<SlotPlacementSession | null>(SlotPlacementSessionKey, null)
|
|
190
|
+
|
|
191
|
+
if (!session)
|
|
192
|
+
throw new Error('[Slot] Slot должен находиться внутри Slide.')
|
|
193
|
+
|
|
194
|
+
return session
|
|
195
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { VNode } from 'vue'
|
|
2
|
+
import { isBlankTextVNode, isCommentVNode, isNamedComponentVNode, readVNodeProp, readVNodeTypeName } from './layout-vnode'
|
|
3
|
+
import { THEME_LAYOUT_ROLES, type ThemeLayoutRole, type ThemeLayoutSlotSpec } from './layout-recipes'
|
|
4
|
+
import type { SlotPlacementInput } from './slot-placement'
|
|
5
|
+
|
|
6
|
+
export type SlotNodeIntent = {
|
|
7
|
+
role: string
|
|
8
|
+
hasCoordinates: boolean
|
|
9
|
+
label: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type SlotChildrenRuleInput = {
|
|
13
|
+
label: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type SlotLayoutChildRuleInput = {
|
|
17
|
+
label: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type SlotIntent =
|
|
21
|
+
| { kind: 'manual', id: string, label: string, area: string, col: string, row: string }
|
|
22
|
+
| { kind: 'layout', id: string, label: string, role: ThemeLayoutRole, overrides: Partial<ThemeLayoutSlotSpec> }
|
|
23
|
+
|
|
24
|
+
export type SlotRules = {
|
|
25
|
+
read(node: VNode): SlotNodeIntent | null
|
|
26
|
+
assertChildren(children: readonly VNode[], input: SlotChildrenRuleInput): VNode[]
|
|
27
|
+
assertLayoutChild(node: VNode, input: SlotLayoutChildRuleInput): VNode
|
|
28
|
+
normalize(input: SlotPlacementInput, context: { layoutActive: boolean }): SlotIntent
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const THEME_LAYOUT_ROLE_SET = new Set<ThemeLayoutRole>(THEME_LAYOUT_ROLES)
|
|
32
|
+
|
|
33
|
+
export function isThemeLayoutRole(value: unknown): value is ThemeLayoutRole {
|
|
34
|
+
return typeof value === 'string' && THEME_LAYOUT_ROLE_SET.has(value as ThemeLayoutRole)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readStringProp(node: VNode, key: string) {
|
|
38
|
+
const value = readVNodeProp<string>(node, key)
|
|
39
|
+
return typeof value === 'string' ? value.trim() : ''
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function normalizeLabel(input: SlotPlacementInput) {
|
|
43
|
+
return input.label?.trim() || input.id
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function hasCoordinates(input: SlotPlacementInput | VNode) {
|
|
47
|
+
if ('props' in input) {
|
|
48
|
+
return Boolean(
|
|
49
|
+
readVNodeProp<string>(input, 'area')
|
|
50
|
+
|| readVNodeProp<string>(input, 'col')
|
|
51
|
+
|| readVNodeProp<string>(input, 'row'),
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return Boolean(input.area || input.col || input.row)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function createSlotRules(input: { Slot: unknown }): SlotRules {
|
|
59
|
+
function read(node: VNode): SlotNodeIntent | null {
|
|
60
|
+
if (!isNamedComponentVNode(node, 'Slot', input.Slot))
|
|
61
|
+
return null
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
role: readStringProp(node, 'role'),
|
|
65
|
+
hasCoordinates: hasCoordinates(node),
|
|
66
|
+
label: readStringProp(node, 'label'),
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
read,
|
|
72
|
+
assertChildren(children, options) {
|
|
73
|
+
const unsupportedNodes = children.filter(node =>
|
|
74
|
+
!isCommentVNode(node)
|
|
75
|
+
&& !isBlankTextVNode(node)
|
|
76
|
+
&& !read(node)?.role,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if (unsupportedNodes.length) {
|
|
80
|
+
const unsupportedLabels = unsupportedNodes.map(readVNodeTypeName).join(', ')
|
|
81
|
+
throw new Error(
|
|
82
|
+
`[Slide] ${options.label} ожидает только Slot role="${THEME_LAYOUT_ROLES.join('|')}" children в explicit layout mode. Неподдерживаемые children: ${unsupportedLabels}.`,
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return [...children]
|
|
87
|
+
},
|
|
88
|
+
assertLayoutChild(node, options) {
|
|
89
|
+
const intent = read(node)
|
|
90
|
+
|
|
91
|
+
if (!intent?.role)
|
|
92
|
+
return node
|
|
93
|
+
|
|
94
|
+
const { role } = intent
|
|
95
|
+
|
|
96
|
+
if (!isThemeLayoutRole(role)) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`[Slide] ${options.label} получил неизвестную Slot role "${role}". Разрешены только: ${THEME_LAYOUT_ROLES.join(', ')}.`,
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (intent.hasCoordinates) {
|
|
103
|
+
throw new Error('[Slide] Slot внутри layout-слайда не должен получать area, col или row напрямую.')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return node
|
|
107
|
+
},
|
|
108
|
+
normalize(placementInput, context) {
|
|
109
|
+
const label = normalizeLabel(placementInput)
|
|
110
|
+
|
|
111
|
+
if (placementInput.role && !context.layoutActive) {
|
|
112
|
+
throw new Error('[Slot] role разрешён только внутри layout-слайда; вне layout-слайда используйте area, col или row.')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (context.layoutActive && placementInput.role && !isThemeLayoutRole(placementInput.role)) {
|
|
116
|
+
throw new Error(`[Slot] role внутри layout-слайда должен быть одним из: ${THEME_LAYOUT_ROLES.join(', ')}.`)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (context.layoutActive && placementInput.role && hasCoordinates(placementInput)) {
|
|
120
|
+
throw new Error('[Slot] area, col и row запрещены внутри layout-слайда; задайте role и выберите variant во frontmatter.')
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (context.layoutActive && !placementInput.role) {
|
|
124
|
+
throw new Error('[Slot] area, col и row запрещены внутри layout-слайда; все Slot внутри layout должны использовать role.')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!context.layoutActive || !placementInput.role) {
|
|
128
|
+
return {
|
|
129
|
+
kind: 'manual',
|
|
130
|
+
id: placementInput.id,
|
|
131
|
+
label,
|
|
132
|
+
area: placementInput.area ?? '',
|
|
133
|
+
col: placementInput.col ?? '',
|
|
134
|
+
row: placementInput.row ?? '',
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
kind: 'layout',
|
|
140
|
+
id: placementInput.id,
|
|
141
|
+
label,
|
|
142
|
+
role: placementInput.role,
|
|
143
|
+
overrides: placementInput.overrides ?? {},
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
}
|