slidev-theme-practicum 0.3.0 → 0.4.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 +78 -4
- package/components/Slide.vue +1 -1
- package/components/Slot.vue +2 -2
- package/components/StepsGrid.vue +19 -14
- package/components/Text.vue +5 -0
- package/composables/deck-slot-markup.cjs +19 -5
- package/composables/layout-shorthands.ts +152 -67
- package/composables/text-fit-runtime.ts +7 -3
- package/composables/theme-foundation.ts +7 -3
- package/composables/typography-guard.cjs +59 -0
- package/composables/validate-deck-layouts.cjs +18 -1
- package/composables/validate-deck-typography.cjs +101 -0
- package/example.md +1 -1
- package/package.json +14 -5
- package/scripts/browser-smoke.mjs +20 -23
- package/scripts/check-accessibility.mjs +364 -0
- package/scripts/check-consumer.mjs +159 -0
- package/scripts/check-pixels.mjs +251 -0
- package/scripts/check-typography.mjs +108 -0
- package/scripts/test-typography.mjs +89 -0
- package/scripts/typography-browser.mjs +134 -0
- package/scripts/validate-deck.cjs +22 -2
- package/skills/slidev-practicum/SKILL.md +1 -0
- package/styles/index.css +20 -27
- package/styles/vars.css +6 -2
package/README.md
CHANGED
|
@@ -36,6 +36,64 @@ npm exec -- slidev-practicum-validate path/to/slides.md
|
|
|
36
36
|
|
|
37
37
|
В `example.md` у канонических слайдов в заметках автора (`<!-- Контракт … -->`) описано, сколько пунктов и какой вложенности ждёт каждый variant.
|
|
38
38
|
|
|
39
|
+
## Строгая проверка типографики
|
|
40
|
+
|
|
41
|
+
Режим включается явно. Обычная проверка и внешний вид существующих макетов сохраняются. Галерея показывает весь API темы, поэтому её варианты с `muted` и несколькими уровнями текста могут не проходить строгую политику.
|
|
42
|
+
|
|
43
|
+
Правила для содержательного текста слайда:
|
|
44
|
+
|
|
45
|
+
- Не более двух размеров обычного текста, включая заголовок, подписи, даты и номера шагов.
|
|
46
|
+
- Третий размер разрешён только для явно отмеченных ключевых чисел: `<Text as="data" size="9">42%</Text>`. В этом режиме `as="data"` означает утверждение автора, что это ключевое число. Все такие числа на слайде должны иметь один размер. Содержимое не проверяется на наличие цифр.
|
|
47
|
+
- В каждом `Slot` один цвет текста, без `muted` и токенов `text-muted*`. Между разными слотами цвета могут различаться.
|
|
48
|
+
- Текст должен помещаться в границах содержимого слота. При переполнении измените текст или композицию.
|
|
49
|
+
- Шапка, номер слайда в шапке, логотипы, фоновые и SVG-иллюстрации исключены. Содержательные подписи к иллюстрациям учитываются.
|
|
50
|
+
|
|
51
|
+
Пример явной композиции: два обычных размера `5` и `3`, отдельный размер `7` для ключевого числа, один основной цвет слота.
|
|
52
|
+
|
|
53
|
+
```md
|
|
54
|
+
---
|
|
55
|
+
layout: message
|
|
56
|
+
variant: centered
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
<Slot role="primary">
|
|
60
|
+
<Text as="h1" size="5">Результат эксперимента</Text>
|
|
61
|
+
<Text as="data" size="7">42%</Text>
|
|
62
|
+
<Text size="3">Участники завершили практическое задание</Text>
|
|
63
|
+
</Slot>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Проверка объявленных атрибутов:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm exec -- slidev-practicum-validate slides.md --typography-guard
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Она считает только явно объявленные `Text`, сравнивает токены цветов внутри `Slot` и ловит запрещённый `muted`. Диапазон `size="7-8"` считается одной политикой подбора, `max-size` — политикой `0-12`. Литеральные привязки вроде `:size="7"` и `:muted="false"` поддерживаются. Динамические размеры, цвета, теги, стили и условные ветви получают сообщение о невозможности статической проверки. Они не подменяются значениями по умолчанию.
|
|
73
|
+
|
|
74
|
+
Для итоговой проверки сначала соберите эту же колоду, затем проверьте результат в Chromium:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm install -D playwright-chromium
|
|
78
|
+
npm exec -- slidev build slides.md --out dist
|
|
79
|
+
npm exec -- slidev-practicum-check-typography slides.md --dist dist
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Команда ожидает загрузки шрифтов и стабильного подбора текста, проверяет вычисленные размеры и цвета видимых текстовых фрагментов, затем их выход за границы слотов. Учитываются также вложенная разметка, ссылки и текст, созданный сокращёнными Markdown-макетами и компонентами темы. Разные токены одного вычисленного цвета в браузере объединяются. Сообщения содержат номер слайда, фрагмент текста и найденные значения. Нарушение или ошибка отображения дают код выхода `1`.
|
|
83
|
+
|
|
84
|
+
Используйте свежую сборку указанного файла с корнем сайта `/`. Поддерживаются маршруты `hash` и `history`. Проверяется начальное видимое состояние каждого слайда, без перебора кликов, анимаций и интерактивных состояний. Включения слайдов через `src` пока явно отклоняются. Статический результат не заменяет браузерный. При динамических атрибутах итоговый вердикт даёт браузерная команда для показанного состояния.
|
|
85
|
+
|
|
86
|
+
Порядок работы агента:
|
|
87
|
+
|
|
88
|
+
1. Включайте режим, когда он выбран для колоды. При копировании примера из галереи проверяйте его по строгим правилам: сама галерея не гарантирует их соблюдение.
|
|
89
|
+
2. Запустите статическую проверку, затем сборку и браузерную проверку. Если строгая статическая проверка не может вычислить динамические атрибуты, отдельно запустите обычный `slidev-practicum-validate` через `npm exec --` для проверки макетов, а типографику подтвердите браузерной командой. В отчёте укажите эту границу проверки.
|
|
90
|
+
3. Исправляйте замечания в колоде. Если сокращённый макет создаёт `muted` или лишние размеры, используйте явные `Slot` и `Text` либо локальный вариант по контракту темы. Не редактируйте установленный пакет и не маскируйте замечание дополнительным мелким размером.
|
|
91
|
+
4. После исправлений повторите проверку объявлений, пересоберите колоду и проверьте новую сборку. Успех — отсутствие диагностик в применимых проверках и код выхода `0`. Интерактивные состояния проверьте отдельно.
|
|
92
|
+
|
|
93
|
+
Если команда или флаг отсутствуют, проверьте установленную ревизию `slidev-theme-practicum`: она должна содержать этот режим. Обновите зависимость на нужную ревизию перед проверкой. Отсутствие инструмента не означает, что колода прошла проверку.
|
|
94
|
+
|
|
95
|
+
В репозитории темы доступны `npm run check-typography -- slides.md --dist dist` и `npm run test:accessibility -- --typography-guard`. Последняя команда проверяет готовую сборку галереи `example.md` с дополнительной строгой политикой. Автоматические проверки самого механизма запускаются через `npm run test:typography` и входят в `npm test`.
|
|
96
|
+
|
|
39
97
|
Для использования в другой Slidev-презентации:
|
|
40
98
|
|
|
41
99
|
```bash
|
|
@@ -46,6 +104,8 @@ npm install -D slidev-theme-practicum
|
|
|
46
104
|
theme: practicum
|
|
47
105
|
```
|
|
48
106
|
|
|
107
|
+
Тема не загружает шрифты из сети. Если `YS Text` установлен в системе, браузер использует его локально. Иначе стек последовательно переходит к локальному `Inter`, системному интерфейсному шрифту и `sans-serif`. Поэтому сборка и показ остаются офлайн-воспроизводимыми, но метрики текста без `YS Text` могут немного отличаться. Перед выпуском проверяйте галерею тем же окружением Chromium, которым сформированы пиксельные эталоны.
|
|
108
|
+
|
|
49
109
|
## Структура проекта колоды
|
|
50
110
|
|
|
51
111
|
Для презентации, которая использует установленную тему, каноническая точка входа — `slides.md`, а локальные медиа лежат в `public/` и подключаются абсолютным путём от корня сайта:
|
|
@@ -230,7 +290,7 @@ defineSlots<DeckLayoutVariantSlots>()
|
|
|
230
290
|
| `comparison` | `before-after` | Markdown heading; `comparison.from` и `comparison.to` с обязательным `title`; необязательные `kicker`, `body` и `relation.label` |
|
|
231
291
|
| `comparison` | `stable-variable` | Тот же контракт; вертикальное направление задаёт arrangement |
|
|
232
292
|
| `steps` | `linear` | Markdown heading; `items` из 3–6 объектов с обязательным `title`, необязательными `body`, `label`; не больше одного `active: true` |
|
|
233
|
-
| `steps` | `staggered` | Тот же контракт, но
|
|
293
|
+
| `steps` | `staggered` | Тот же контракт, но от 2 до 6 элементов; label сверху, title и body снизу |
|
|
234
294
|
| `metrics` | `dashboard` | Markdown heading; ровно 5 `metrics` с обязательными `value` и одним из `body`, `label`, `title`; без `media` и произвольных spans |
|
|
235
295
|
| `facts` | `numbered-quartet` | Markdown heading; ровно 4 пункта вида «значение → вложенная подпись»; номера 1–4 добавляет тема |
|
|
236
296
|
|
|
@@ -336,6 +396,10 @@ arrangement: numbered-quartet
|
|
|
336
396
|
| `header` | `default`, `cover`, `none` | задаёт макет | служебный верхний слой |
|
|
337
397
|
| `decor` | объект: `id` или семантический запрос | нет | декоративный слой для сокращённой записи |
|
|
338
398
|
|
|
399
|
+
Пакет объявляет `slidev.colorSchema: light`: системное переключение светлой и тёмной темы не поддерживается. Значения `mode: dark` и `mode: color` — проверенные семантические варианты отдельных слайдов внутри одной цветовой схемы, а не альтернативные карты для `prefers-color-scheme`. Тёмный и акцентный варианты используют светлый текст, логотип и служебные элементы.
|
|
400
|
+
|
|
401
|
+
Белый текст на ярких фирменных фонах сохраняется как осознанное визуальное правило. На оранжевом, зелёном и особенно жёлтом тоне отдельные пары не достигают порогов WCAG. Автоматическая проверка продолжает измерять их и выводит число `акцентные исключения`, но разрешает низкий контраст только внутри `mode: color`, `surface: color` и активного шага. Во всех остальных контекстах недостаточный контраст остаётся ошибкой.
|
|
402
|
+
|
|
339
403
|
## Slot API
|
|
340
404
|
|
|
341
405
|
| Свойство | Тип | По умолчанию | Смысл |
|
|
@@ -373,7 +437,7 @@ arrangement: numbered-quartet
|
|
|
373
437
|
|
|
374
438
|
В `Text` можно писать блочный Markdown темы (списки, абзацы, цитаты) — не полный GFM Slidev. Это не layout shorthand: на `cover:*` по-прежнему нужны явные `Slot` / `Text`.
|
|
375
439
|
|
|
376
|
-
Inline-разметка `**жирный**` сохраняется как `<strong>` и отображается
|
|
440
|
+
Inline-разметка `**жирный**` сохраняется как `<strong>` и отображается доступным локальным полужирным начертанием с весом `600`.
|
|
377
441
|
|
|
378
442
|
Slidev парсит markdown внутри компонента только если контент **отделён пустой строкой** от открывающего и закрывающего тега (как в [доке Slidev](https://sli.dev/builtin/components)). Без пустых строк список схлопнется в одну строку и сломается. Не добавляйте лишний отступ в 4 пробела у пунктов — Slidev превратит блок в code.
|
|
379
443
|
|
|
@@ -527,7 +591,7 @@ Endpoint сохранения принимает только точное зн
|
|
|
527
591
|
| `light-1` | `#f0f0f0` |
|
|
528
592
|
| `dark-0` | `#1e1e1e` |
|
|
529
593
|
| `dark-1` | `#3c3c3c` |
|
|
530
|
-
| `dark-2` | `#
|
|
594
|
+
| `dark-2` | `#646464` |
|
|
531
595
|
| `blue-0` | `#027ef2` |
|
|
532
596
|
| `blue-1` | `#98d2fe` |
|
|
533
597
|
| `orange-0` | `#ff6c26` |
|
|
@@ -549,4 +613,14 @@ npm test
|
|
|
549
613
|
- модульные, контрактные и архитектурные тесты;
|
|
550
614
|
- продукционную сборку, локальный вариант внешней презентации и состав артефактов;
|
|
551
615
|
- репрезентативные слайды в Chromium, переполнение холста и загрузку медиа по `/theme/...`;
|
|
552
|
-
-
|
|
616
|
+
- контраст всех видимых текстовых пар, семантику медиа и `alt`, видимый фокус, `prefers-reduced-motion` и отсутствие внешних запросов;
|
|
617
|
+
- точное совпадение всех 47 слайдов `example.md` с PNG-эталонами при зафиксированных Chromium, viewport, DPR, sRGB и доступности локальных шрифтов;
|
|
618
|
+
- состав и размер npm-пакета, затем установку созданного локального архива во временную внешнюю презентацию, её сборку, показ без внешних запросов и ресурсы `/theme/...`.
|
|
619
|
+
|
|
620
|
+
## Обновление пиксельных эталонов
|
|
621
|
+
|
|
622
|
+
Пиксельные эталоны меняются только осознанно после визуального просмотра результата:
|
|
623
|
+
|
|
624
|
+
```bash
|
|
625
|
+
npm run test:pixels:update
|
|
626
|
+
```
|
package/components/Slide.vue
CHANGED
|
@@ -197,7 +197,7 @@ const slideClass = computed(() => ({
|
|
|
197
197
|
<slot v-if="!isCompiledMode && slots.header" name="header" />
|
|
198
198
|
<Header v-else-if="resolvedHeader !== 'none'"
|
|
199
199
|
:variant="resolvedHeader === 'cover' ? 'cover' : 'default'"
|
|
200
|
-
:inverted="
|
|
200
|
+
:inverted="resolvedTheme.foreground === 'light'" />
|
|
201
201
|
</div>
|
|
202
202
|
<DebugGrid v-if="showDebugGrid" />
|
|
203
203
|
<div class="Slide-Grid">
|
package/components/Slot.vue
CHANGED
|
@@ -334,8 +334,8 @@ const surfaceClass = computed(() => {
|
|
|
334
334
|
|
|
335
335
|
.Slot_surface_color {
|
|
336
336
|
background: var(--theme-slot-color, var(--theme-current-color));
|
|
337
|
-
--theme-text: var(--theme-text-on-
|
|
338
|
-
--theme-text-muted: var(--theme-text-muted-on-
|
|
337
|
+
--theme-text: var(--theme-text-on-color);
|
|
338
|
+
--theme-text-muted: var(--theme-text-muted-on-color);
|
|
339
339
|
--theme-inline-code-text: var(--theme-text);
|
|
340
340
|
color: var(--theme-text);
|
|
341
341
|
}
|
package/components/StepsGrid.vue
CHANGED
|
@@ -29,7 +29,7 @@ const props = defineProps<{
|
|
|
29
29
|
:class="{ 'Slide-Step_active': item.active }"
|
|
30
30
|
:data-index="index + 1">
|
|
31
31
|
<Text size="4">{{ item.label }}</Text>
|
|
32
|
-
<TextFitGroup class="Slide-Step-FitGroup"
|
|
32
|
+
<TextFitGroup class="Slide-Step-FitGroup Slide-Step-TitleFitGroup"
|
|
33
33
|
:fit-group="`${stepsFitGroup}-titles`">
|
|
34
34
|
<Text size="3-5">{{ item.title }}</Text>
|
|
35
35
|
</TextFitGroup>
|
|
@@ -78,35 +78,40 @@ const props = defineProps<{
|
|
|
78
78
|
min-width: 0;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
.Slide-StepsGrid_staggered .Slide-Step-TitleFitGroup {
|
|
82
|
+
margin-top: auto;
|
|
83
|
+
}
|
|
84
|
+
|
|
81
85
|
.Slide-StepsGrid_staggered {
|
|
82
86
|
grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
83
87
|
grid-template-rows: repeat(2, minmax(0, 1fr));
|
|
84
88
|
}
|
|
85
89
|
|
|
86
|
-
.Slide-StepsGrid_staggered .Slide-Step
|
|
87
|
-
grid-
|
|
90
|
+
.Slide-StepsGrid_staggered .Slide-Step {
|
|
91
|
+
grid-column: span 2;
|
|
88
92
|
}
|
|
89
93
|
|
|
90
|
-
.Slide-StepsGrid_staggered .Slide-Step
|
|
91
|
-
grid-
|
|
94
|
+
.Slide-StepsGrid_staggered[data-count='2'] .Slide-Step {
|
|
95
|
+
grid-column: 2 / 6;
|
|
92
96
|
}
|
|
93
97
|
|
|
94
|
-
.Slide-StepsGrid_staggered .Slide-Step
|
|
95
|
-
|
|
98
|
+
.Slide-StepsGrid_staggered[data-count='3'] .Slide-Step,
|
|
99
|
+
.Slide-StepsGrid_staggered[data-count='4'] .Slide-Step {
|
|
100
|
+
grid-column: span 3;
|
|
96
101
|
}
|
|
97
102
|
|
|
98
|
-
.Slide-StepsGrid_staggered .Slide-Step:nth-child(
|
|
99
|
-
grid-
|
|
103
|
+
.Slide-StepsGrid_staggered[data-count='3'] .Slide-Step:nth-child(3) {
|
|
104
|
+
grid-column: 2 / 6;
|
|
100
105
|
}
|
|
101
106
|
|
|
102
|
-
.Slide-StepsGrid_staggered .Slide-Step:nth-child(
|
|
103
|
-
grid-
|
|
107
|
+
.Slide-StepsGrid_staggered[data-count='5'] .Slide-Step:nth-child(4) {
|
|
108
|
+
grid-column: 2 / 4;
|
|
104
109
|
}
|
|
105
110
|
|
|
106
111
|
.Slide-Step_active {
|
|
107
112
|
background: var(--theme-current-color);
|
|
108
|
-
color: var(--theme-text-on-
|
|
109
|
-
--theme-text: var(--theme-text-on-
|
|
110
|
-
--theme-text-muted: var(--theme-text-muted-on-
|
|
113
|
+
color: var(--theme-text-on-color);
|
|
114
|
+
--theme-text: var(--theme-text-on-color);
|
|
115
|
+
--theme-text-muted: var(--theme-text-muted-on-color);
|
|
111
116
|
}
|
|
112
117
|
</style>
|
package/components/Text.vue
CHANGED
|
@@ -16,7 +16,9 @@ type TextColorToken =
|
|
|
16
16
|
| 'text'
|
|
17
17
|
| 'text-muted'
|
|
18
18
|
| 'text-on-dark'
|
|
19
|
+
| 'text-on-color'
|
|
19
20
|
| 'text-muted-on-contrast'
|
|
21
|
+
| 'text-muted-on-color'
|
|
20
22
|
| 'link'
|
|
21
23
|
| 'light-0'
|
|
22
24
|
| 'light-1'
|
|
@@ -37,7 +39,9 @@ const TEXT_COLOR_MAP: Record<TextColorToken, string> = {
|
|
|
37
39
|
'text': 'var(--theme-text)',
|
|
38
40
|
'text-muted': 'var(--theme-text-muted)',
|
|
39
41
|
'text-on-dark': 'var(--theme-text-on-dark)',
|
|
42
|
+
'text-on-color': 'var(--theme-text-on-color)',
|
|
40
43
|
'text-muted-on-contrast': 'var(--theme-text-muted-on-contrast)',
|
|
44
|
+
'text-muted-on-color': 'var(--theme-text-muted-on-color)',
|
|
41
45
|
'link': 'var(--theme-link)',
|
|
42
46
|
'light-0': 'var(--theme-color-light-0)',
|
|
43
47
|
'light-1': 'var(--theme-color-light-1)',
|
|
@@ -119,6 +123,7 @@ const textStyle = computed(() => ({
|
|
|
119
123
|
<component :is="as"
|
|
120
124
|
ref="textElement"
|
|
121
125
|
class="Text"
|
|
126
|
+
:data-text-color="resolvedColor"
|
|
122
127
|
:class="[
|
|
123
128
|
`Text_size_${normalizedSize}`,
|
|
124
129
|
{
|
|
@@ -7,7 +7,7 @@ const { parse } = require('vue/compiler-sfc')
|
|
|
7
7
|
* @typedef {(
|
|
8
8
|
* | { kind: 'comment' }
|
|
9
9
|
* | { kind: 'text', content: string }
|
|
10
|
-
* | { kind: 'element', tag: string, props: Record<string, unknown>, children: DeckLiveNode[] }
|
|
10
|
+
* | { kind: 'element', tag: string, props: Record<string, unknown>, dynamicProps?: string[], children: DeckLiveNode[] }
|
|
11
11
|
* )} DeckLiveNode
|
|
12
12
|
*/
|
|
13
13
|
/** @typedef {{ children: DeckLiveNode[], intents: ExplicitSlotIntent[] }} DeckLiveStructure */
|
|
@@ -26,6 +26,11 @@ function formatCompilerError(error) {
|
|
|
26
26
|
* @returns {unknown}
|
|
27
27
|
*/
|
|
28
28
|
function readLiteralBinding(prop) {
|
|
29
|
+
const source = prop.exp?.loc.source.trim()
|
|
30
|
+
if (source === 'true' || source === 'false')
|
|
31
|
+
return source === 'true'
|
|
32
|
+
if (source === 'null')
|
|
33
|
+
return null
|
|
29
34
|
const ast = prop.exp?.ast
|
|
30
35
|
if (!ast)
|
|
31
36
|
return undefined
|
|
@@ -75,11 +80,12 @@ function containsSlot(node) {
|
|
|
75
80
|
|
|
76
81
|
/**
|
|
77
82
|
* @param {import('@vue/compiler-core').ElementNode} node
|
|
78
|
-
* @returns {Record<string, unknown
|
|
83
|
+
* @returns {{ props: Record<string, unknown>, dynamicProps?: string[] }}
|
|
79
84
|
*/
|
|
80
85
|
function elementProps(node) {
|
|
81
86
|
/** @type {Record<string, unknown>} */
|
|
82
87
|
const props = {}
|
|
88
|
+
const dynamicProps = []
|
|
83
89
|
|
|
84
90
|
for (const prop of node.props) {
|
|
85
91
|
if (prop.type === 6) {
|
|
@@ -87,8 +93,11 @@ function elementProps(node) {
|
|
|
87
93
|
continue
|
|
88
94
|
}
|
|
89
95
|
|
|
90
|
-
if (prop.name !== 'bind')
|
|
96
|
+
if (prop.name !== 'bind') {
|
|
97
|
+
if (SLOT_CONTROL_FLOW_DIRECTIVES.has(prop.name))
|
|
98
|
+
dynamicProps.push(`v-${prop.name}`)
|
|
91
99
|
continue
|
|
100
|
+
}
|
|
92
101
|
|
|
93
102
|
const key = prop.arg?.type === 4 && prop.arg.isStatic
|
|
94
103
|
? prop.arg.content
|
|
@@ -96,6 +105,11 @@ function elementProps(node) {
|
|
|
96
105
|
|
|
97
106
|
if (node.tag !== 'Slot'
|
|
98
107
|
|| (!SLOT_CONTRACT_PROPS.has(key) && key !== 'label' && key)) {
|
|
108
|
+
const value = readLiteralBinding(prop)
|
|
109
|
+
if (key && value !== undefined)
|
|
110
|
+
props[key] = value
|
|
111
|
+
else
|
|
112
|
+
dynamicProps.push(key ? `v-bind:${key}` : 'v-bind')
|
|
99
113
|
continue
|
|
100
114
|
}
|
|
101
115
|
|
|
@@ -118,7 +132,7 @@ function elementProps(node) {
|
|
|
118
132
|
props[key] = value
|
|
119
133
|
}
|
|
120
134
|
|
|
121
|
-
return props
|
|
135
|
+
return dynamicProps.length ? { props, dynamicProps } : { props }
|
|
122
136
|
}
|
|
123
137
|
|
|
124
138
|
/**
|
|
@@ -134,7 +148,7 @@ function liveNode(node) {
|
|
|
134
148
|
return {
|
|
135
149
|
kind: 'element',
|
|
136
150
|
tag: node.tag,
|
|
137
|
-
|
|
151
|
+
...elementProps(node),
|
|
138
152
|
children: node.children
|
|
139
153
|
.map(liveNode)
|
|
140
154
|
.filter(isLiveNode),
|