tutuca 0.15.0 → 1.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +132 -0
- package/LICENSE +1 -1
- package/README.md +64 -170
- package/{dist → deps}/immer.js +674 -249
- package/dist/tutuca.js +2342 -4057
- package/dist/tutuca.min.js +3 -3
- package/package.json +78 -96
- package/src/component.js +66 -0
- package/src/css/classes.js +77 -0
- package/src/dev/formatters.js +393 -0
- package/src/dev/jsonml.js +35 -0
- package/src/dev/panel.js +400 -0
- package/src/dev/summary.js +239 -0
- package/src/dev/vdom-formatters.js +102 -0
- package/src/drag.js +23 -0
- package/src/embed/element.js +111 -0
- package/src/env.js +30 -0
- package/src/events.js +85 -0
- package/src/history/bar.js +198 -0
- package/src/history/index.js +149 -0
- package/src/host.js +360 -0
- package/src/lint/index.js +470 -0
- package/src/meta.js +12 -0
- package/src/optimize.js +58 -0
- package/src/paths.js +205 -0
- package/src/playground/element.js +360 -0
- package/src/playground/index.js +5 -0
- package/src/playground/sources.js +107 -0
- package/src/playground/ui/playground.js +287 -0
- package/src/schema.js +460 -0
- package/src/scope.js +92 -0
- package/src/storybook/index.js +80 -0
- package/src/storybook/runner.js +452 -0
- package/src/storybook/stories.js +208 -0
- package/src/storybook/stress.js +76 -0
- package/src/storybook/ui/common.js +37 -0
- package/src/storybook/ui/link.js +80 -0
- package/src/storybook/ui/panels.js +32 -0
- package/src/storybook/ui/story-panel.js +432 -0
- package/src/storybook/ui/storybook.js +191 -0
- package/src/storybook/ui/stress-panel.js +231 -0
- package/src/sync/automerge.js +159 -0
- package/src/sync/codec.js +206 -0
- package/src/sync/diff.js +153 -0
- package/src/sync/index.js +97 -0
- package/src/sync/migrate.js +184 -0
- package/src/sync/ops.js +160 -0
- package/src/sync/remote.js +206 -0
- package/src/tag.js +24 -0
- package/src/tasks.js +244 -0
- package/src/test/driver.js +141 -0
- package/src/test/pbt.js +589 -0
- package/src/tutuca.js +66 -0
- package/src/vdom.js +349 -0
- package/src/view.js +354 -0
- package/src/walk.js +79 -0
- package/dist/chai.js +0 -4210
- package/dist/tutuca-cli.js +0 -16315
- package/dist/tutuca-components.js +0 -2232
- package/dist/tutuca-dev.ext.js +0 -8476
- package/dist/tutuca-dev.js +0 -13220
- package/dist/tutuca-dev.min.js +0 -5
- package/dist/tutuca-extra.ext.js +0 -4261
- package/dist/tutuca-extra.js +0 -4964
- package/dist/tutuca-extra.min.js +0 -3
- package/dist/tutuca-storybook.js +0 -970
- package/dist/tutuca.ext.js +0 -4183
- package/skill/margaui/SKILL.md +0 -105
- package/skill/margaui/components/accordion.md +0 -127
- package/skill/margaui/components/alert.md +0 -174
- package/skill/margaui/components/aura.md +0 -97
- package/skill/margaui/components/avatar.md +0 -220
- package/skill/margaui/components/badge.md +0 -193
- package/skill/margaui/components/breadcrumbs.md +0 -103
- package/skill/margaui/components/button.md +0 -322
- package/skill/margaui/components/calendar.md +0 -67
- package/skill/margaui/components/card.md +0 -373
- package/skill/margaui/components/carousel.md +0 -387
- package/skill/margaui/components/chat.md +0 -171
- package/skill/margaui/components/checkbox.md +0 -101
- package/skill/margaui/components/collapse.md +0 -172
- package/skill/margaui/components/countdown.md +0 -165
- package/skill/margaui/components/diff.md +0 -53
- package/skill/margaui/components/divider.md +0 -107
- package/skill/margaui/components/dock.md +0 -173
- package/skill/margaui/components/drawer.md +0 -184
- package/skill/margaui/components/dropdown.md +0 -388
- package/skill/margaui/components/fab.md +0 -346
- package/skill/margaui/components/fieldset.md +0 -88
- package/skill/margaui/components/file-input.md +0 -84
- package/skill/margaui/components/filter.md +0 -52
- package/skill/margaui/components/footer.md +0 -583
- package/skill/margaui/components/hero.md +0 -135
- package/skill/margaui/components/hover-3d.md +0 -129
- package/skill/margaui/components/hover-gallery.md +0 -49
- package/skill/margaui/components/indicator.md +0 -265
- package/skill/margaui/components/input.md +0 -389
- package/skill/margaui/components/join.md +0 -100
- package/skill/margaui/components/kbd.md +0 -127
- package/skill/margaui/components/label.md +0 -102
- package/skill/margaui/components/link.md +0 -96
- package/skill/margaui/components/list.md +0 -182
- package/skill/margaui/components/loading.md +0 -105
- package/skill/margaui/components/mask.md +0 -168
- package/skill/margaui/components/megamenu.md +0 -131
- package/skill/margaui/components/menu.md +0 -887
- package/skill/margaui/components/mockup-browser.md +0 -39
- package/skill/margaui/components/mockup-code.md +0 -81
- package/skill/margaui/components/mockup-phone.md +0 -39
- package/skill/margaui/components/mockup-window.md +0 -33
- package/skill/margaui/components/modal.md +0 -196
- package/skill/margaui/components/navbar.md +0 -282
- package/skill/margaui/components/otp.md +0 -171
- package/skill/margaui/components/pagination.md +0 -122
- package/skill/margaui/components/progress.md +0 -135
- package/skill/margaui/components/radial-progress.md +0 -67
- package/skill/margaui/components/radio.md +0 -133
- package/skill/margaui/components/range.md +0 -134
- package/skill/margaui/components/rating.md +0 -170
- package/skill/margaui/components/select.md +0 -225
- package/skill/margaui/components/skeleton.md +0 -64
- package/skill/margaui/components/stack.md +0 -142
- package/skill/margaui/components/stat.md +0 -254
- package/skill/margaui/components/status.md +0 -73
- package/skill/margaui/components/steps.md +0 -138
- package/skill/margaui/components/swap.md +0 -152
- package/skill/margaui/components/tab.md +0 -248
- package/skill/margaui/components/table.md +0 -1018
- package/skill/margaui/components/text-rotate.md +0 -91
- package/skill/margaui/components/textarea.md +0 -85
- package/skill/margaui/components/theme-controller.md +0 -266
- package/skill/margaui/components/timeline.md +0 -1356
- package/skill/margaui/components/toast.md +0 -165
- package/skill/margaui/components/toggle.md +0 -135
- package/skill/margaui/components/tooltip.md +0 -181
- package/skill/margaui/components/validator.md +0 -163
- package/skill/tutuca/SKILL.md +0 -56
- package/skill/tutuca/advanced.md +0 -292
- package/skill/tutuca/cli.md +0 -239
- package/skill/tutuca/component-design.md +0 -168
- package/skill/tutuca/core.md +0 -935
- package/skill/tutuca/iteration.md +0 -207
- package/skill/tutuca/macros.md +0 -86
- package/skill/tutuca/margaui.md +0 -175
- package/skill/tutuca/messages-and-intents.md +0 -414
- package/skill/tutuca/patterns/README.md +0 -48
- package/skill/tutuca/patterns/add-a-story.md +0 -26
- package/skill/tutuca/patterns/bind-text-and-attributes.md +0 -30
- package/skill/tutuca/patterns/conditional-attribute-value.md +0 -29
- package/skill/tutuca/patterns/coordinate-components.md +0 -54
- package/skill/tutuca/patterns/edit-through-a-dynamic-target.md +0 -32
- package/skill/tutuca/patterns/enrich-each-item.md +0 -25
- package/skill/tutuca/patterns/file-input.md +0 -39
- package/skill/tutuca/patterns/filter-a-list.md +0 -25
- package/skill/tutuca/patterns/filter-and-paginate.md +0 -60
- package/skill/tutuca/patterns/handle-events.md +0 -45
- package/skill/tutuca/patterns/iterate-a-list.md +0 -18
- package/skill/tutuca/patterns/paginate-a-list.md +0 -29
- package/skill/tutuca/patterns/render-a-child-component.md +0 -21
- package/skill/tutuca/patterns/reuse-markup-with-macros.md +0 -36
- package/skill/tutuca/patterns/share-state-across-the-tree.md +0 -53
- package/skill/tutuca/patterns/show-or-hide-content.md +0 -23
- package/skill/tutuca/patterns/switch-between-views.md +0 -30
- package/skill/tutuca/patterns/tabbed-interface.md +0 -43
- package/skill/tutuca/semantics.md +0 -219
- package/skill/tutuca/storybook.md +0 -270
- package/skill/tutuca/styles.md +0 -48
- package/skill/tutuca/testing.md +0 -345
- package/skill/tutuca-source/SKILL.md +0 -33
- package/skill/tutuca-source/tutuca.ext.js +0 -4183
package/dist/tutuca-storybook.js
DELETED
|
@@ -1,970 +0,0 @@
|
|
|
1
|
-
// src/storybook/components.js
|
|
2
|
-
import { component, dispatchPhase, html } from "tutuca";
|
|
3
|
-
var Storybook = component({
|
|
4
|
-
name: "Storybook",
|
|
5
|
-
fields: {
|
|
6
|
-
selectedSectionIndex: 0,
|
|
7
|
-
sections: [],
|
|
8
|
-
// Retained navigation tree (SidebarGroup → SidebarEntry), built once from
|
|
9
|
-
// `sections` and then mutated in place: selection flips entry `selected`, the
|
|
10
|
-
// filter walk sets entry/group `visible`, a header click flips group `collapsed`.
|
|
11
|
-
// It owns its own UI state — never rebuilt or projected from `sections`.
|
|
12
|
-
sidebar: [],
|
|
13
|
-
filter: "",
|
|
14
|
-
sectionId: null,
|
|
15
|
-
exampleId: null,
|
|
16
|
-
focusExample: null,
|
|
17
|
-
sidebarCollapsed: false,
|
|
18
|
-
// The active margaui palette, and the names the switcher offers. `themes` is
|
|
19
|
-
// seeded by mountStorybook from its `themes` option and left EMPTY when no
|
|
20
|
-
// margaui theme CSS is on the page (`--no-margaui`) — an empty list hides the
|
|
21
|
-
// switcher, so nothing offers a theme that can't load.
|
|
22
|
-
theme: "",
|
|
23
|
-
themes: []
|
|
24
|
-
},
|
|
25
|
-
statics: {
|
|
26
|
-
// Build a storybook whose sidebar tree is derived from `sections` once. Use this
|
|
27
|
-
// instead of a bare `make` anywhere a Storybook is constructed by hand (the
|
|
28
|
-
// engine itself, the getRoot/Inception demo) so the sidebar is never empty.
|
|
29
|
-
withSections(sections) {
|
|
30
|
-
return this.make({ sections, sidebar: buildSidebar(sections) });
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
methods: {
|
|
34
|
-
// Assemble the full URL snapshot from current state. `overrides` carries the
|
|
35
|
-
// change the calling handler is about to make, since `this` is still the
|
|
36
|
-
// pre-change state when the persistState intent is issued.
|
|
37
|
-
//
|
|
38
|
-
// `theme` is deliberately NOT part of the base snapshot: it is resolved on load
|
|
39
|
-
// (from the OS preference when the URL says nothing), and writing that resolved
|
|
40
|
-
// value back would stamp `?theme=light` onto the URL the first time anyone types
|
|
41
|
-
// in the filter. persistState only touches the keys it is handed, so omitting it
|
|
42
|
-
// here leaves an already-chosen `?theme=` alone — onSelectTheme passes it as an
|
|
43
|
-
// override, which is the only way it enters the URL.
|
|
44
|
-
toUrlState(overrides = {}) {
|
|
45
|
-
const section = this.sections[this.selectedSectionIndex];
|
|
46
|
-
return {
|
|
47
|
-
section: section?.id ?? "",
|
|
48
|
-
example: this.exampleId ?? "",
|
|
49
|
-
sectionFilter: this.filter ?? "",
|
|
50
|
-
exampleFilter: section?.filter ?? "",
|
|
51
|
-
...overrides
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
receive: {
|
|
56
|
-
onApplyFilter(draft, value, ctx) {
|
|
57
|
-
ctx.intent("persistState", [this.toUrlState({ sectionFilter: value }), this, false], {
|
|
58
|
-
route: ["lex"]
|
|
59
|
-
});
|
|
60
|
-
draft.filter = value;
|
|
61
|
-
applyFilterToSidebar(draft, value);
|
|
62
|
-
},
|
|
63
|
-
onClearFilter(draft, ctx) {
|
|
64
|
-
ctx.intent("persistState", [this.toUrlState({ sectionFilter: "" }), this, false], {
|
|
65
|
-
route: ["lex"]
|
|
66
|
-
});
|
|
67
|
-
draft.filter = "";
|
|
68
|
-
applyFilterToSidebar(draft, "");
|
|
69
|
-
},
|
|
70
|
-
onFocusClose(draft, ctx) {
|
|
71
|
-
ctx.intent("persistState", [this.toUrlState({ example: "" }), this, true], {
|
|
72
|
-
route: ["lex"]
|
|
73
|
-
});
|
|
74
|
-
draft.sectionId = null;
|
|
75
|
-
draft.exampleId = null;
|
|
76
|
-
draft.focusExample = null;
|
|
77
|
-
},
|
|
78
|
-
// Switching a palette is a DOM effect (set data-theme, load the stylesheet), so it
|
|
79
|
-
// goes out as an intent — the host handler in mountStorybook owns the document.
|
|
80
|
-
// Unregistered (no `themes` option), the intent no-ops via the 404 path and the
|
|
81
|
-
// switcher isn't rendered anyway.
|
|
82
|
-
onSelectTheme(draft, value, ctx) {
|
|
83
|
-
ctx.intent("applyTheme", [value, this], { route: ["lex"] });
|
|
84
|
-
ctx.intent("persistState", [this.toUrlState({ theme: value }), this, false], {
|
|
85
|
-
route: ["lex"]
|
|
86
|
-
});
|
|
87
|
-
draft.theme = value;
|
|
88
|
-
},
|
|
89
|
-
init(draft, ctx) {
|
|
90
|
-
ctx.intent("loadState", [], { route: ["lex"] });
|
|
91
|
-
},
|
|
92
|
-
// The answer to the `loadState` intent, arriving as an ordinary message. There is
|
|
93
|
-
// no `err` to branch on: a failure reaches `loadStateError` instead, and nothing
|
|
94
|
-
// that ran out of route reaches here at all.
|
|
95
|
-
loadStateOk(draft, state, ctx) {
|
|
96
|
-
if (!state) return;
|
|
97
|
-
selectSectionWithId(draft, state.section);
|
|
98
|
-
draft.theme = state.theme ?? "";
|
|
99
|
-
draft.filter = state.sectionFilter ?? "";
|
|
100
|
-
applyFilterToSidebar(draft, draft.filter);
|
|
101
|
-
setSelectedSectionFilter(draft, state.exampleFilter ?? "");
|
|
102
|
-
if (state.example) focusExampleByIds(draft, state.section, state.example);
|
|
103
|
-
else {
|
|
104
|
-
draft.sectionId = null;
|
|
105
|
-
draft.exampleId = null;
|
|
106
|
-
draft.focusExample = null;
|
|
107
|
-
}
|
|
108
|
-
transitionSections(ctx, draft, this.selectedSectionIndex, draft.selectedSectionIndex);
|
|
109
|
-
},
|
|
110
|
-
toggleSidebarCollapsed(draft) {
|
|
111
|
-
draft.sidebarCollapsed = !draft.sidebarCollapsed;
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
intent: {
|
|
115
|
-
// A sidebar entry was clicked; it bubbles its section id. Resolve to the content
|
|
116
|
-
// index and select. Sidebar highlight is updated by selectSectionAtIndex.
|
|
117
|
-
sectionSelected(draft, sectionId, ctx) {
|
|
118
|
-
ctx.stop();
|
|
119
|
-
const section = this.sections.find((s) => s.id === sectionId);
|
|
120
|
-
ctx.intent(
|
|
121
|
-
"persistState",
|
|
122
|
-
[this.toUrlState({ section: sectionId, exampleFilter: section?.filter ?? "" }), this, true],
|
|
123
|
-
{ route: ["lex"] }
|
|
124
|
-
);
|
|
125
|
-
const oldIndex = this.selectedSectionIndex;
|
|
126
|
-
selectSectionAtIndex(
|
|
127
|
-
draft,
|
|
128
|
-
this.sections.findIndex((s) => s.id === sectionId)
|
|
129
|
-
);
|
|
130
|
-
transitionSections(ctx, draft, oldIndex, draft.selectedSectionIndex);
|
|
131
|
-
},
|
|
132
|
-
// Toggle a group open/closed in place — the collapse lives on the SidebarGroup.
|
|
133
|
-
groupToggled(draft, name, ctx) {
|
|
134
|
-
ctx.stop();
|
|
135
|
-
toggleSidebarGroup(draft, name);
|
|
136
|
-
},
|
|
137
|
-
exampleFocusRequested(draft, example, ctx) {
|
|
138
|
-
ctx.stop();
|
|
139
|
-
const section = this.sections[this.selectedSectionIndex];
|
|
140
|
-
const sectionId = section?.id ?? null;
|
|
141
|
-
ctx.intent("persistState", [this.toUrlState({ example: example.id }), this, true], {
|
|
142
|
-
route: ["lex"]
|
|
143
|
-
});
|
|
144
|
-
draft.sectionId = sectionId;
|
|
145
|
-
draft.exampleId = example.id;
|
|
146
|
-
draft.focusExample = example.value;
|
|
147
|
-
},
|
|
148
|
-
exampleFilterChanged(draft, value, ctx) {
|
|
149
|
-
ctx.stop();
|
|
150
|
-
ctx.intent("persistState", [this.toUrlState({ exampleFilter: value }), this, false], {
|
|
151
|
-
route: ["lex"]
|
|
152
|
-
});
|
|
153
|
-
setSelectedSectionFilter(draft, value);
|
|
154
|
-
}
|
|
155
|
-
},
|
|
156
|
-
alter: {
|
|
157
|
-
// The sidebar render-each shows only visible groups (kept positional via `when`
|
|
158
|
-
// so a click resolves to the right group — see SidebarGroup.rowVisible).
|
|
159
|
-
groupVisible(_key, group) {
|
|
160
|
-
return group.visible;
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
view: html`<div>
|
|
164
|
-
<div class="flex flex-col gap-3 p-3 h-screen" @show="truthy? .focusExample">
|
|
165
|
-
<div class="flex justify-end">
|
|
166
|
-
<button class="btn btn-ghost btn-sm" @on.click="onFocusClose">
|
|
167
|
-
close
|
|
168
|
-
</button>
|
|
169
|
-
</div>
|
|
170
|
-
<div class="flex-1 overflow-y-auto">
|
|
171
|
-
<x render=".focusExample"></x>
|
|
172
|
-
</div>
|
|
173
|
-
</div>
|
|
174
|
-
<div class="flex gap-3 p-3 h-screen" @hide="truthy? .focusExample">
|
|
175
|
-
<button
|
|
176
|
-
class="btn btn-ghost btn-sm self-start"
|
|
177
|
-
title="Show sections"
|
|
178
|
-
@show=".sidebarCollapsed"
|
|
179
|
-
@on.click="toggleSidebarCollapsed"
|
|
180
|
-
>
|
|
181
|
-
»
|
|
182
|
-
</button>
|
|
183
|
-
<div
|
|
184
|
-
class="w-1/4 flex flex-col gap-3 bg-base-100 shadow-md h-full overflow-hidden"
|
|
185
|
-
@hide=".sidebarCollapsed"
|
|
186
|
-
>
|
|
187
|
-
<div class="flex gap-2 items-center">
|
|
188
|
-
<button
|
|
189
|
-
class="btn btn-ghost btn-sm"
|
|
190
|
-
title="Hide sections"
|
|
191
|
-
@on.click="toggleSidebarCollapsed"
|
|
192
|
-
>
|
|
193
|
-
«
|
|
194
|
-
</button>
|
|
195
|
-
<input
|
|
196
|
-
class="input flex-1 focus:outline-none focus-within:outline-none focus:bg-base-200"
|
|
197
|
-
type="search"
|
|
198
|
-
placeholder="Filter sections"
|
|
199
|
-
:value=".filter"
|
|
200
|
-
@on.input="onApplyFilter e.value"
|
|
201
|
-
@on.keydown.cancel="onClearFilter"
|
|
202
|
-
/>
|
|
203
|
-
<select
|
|
204
|
-
class="select w-auto"
|
|
205
|
-
title="Theme"
|
|
206
|
-
@hide="empty? .themes"
|
|
207
|
-
:value=".theme"
|
|
208
|
-
@on.input="onSelectTheme e.value"
|
|
209
|
-
>
|
|
210
|
-
<option @each=".themes" :value="@value" @text="@value"></option>
|
|
211
|
-
</select>
|
|
212
|
-
</div>
|
|
213
|
-
<div class="list h-full flex-1 overflow-y-auto">
|
|
214
|
-
<x render-each=".sidebar" @when="groupVisible"></x>
|
|
215
|
-
</div>
|
|
216
|
-
</div>
|
|
217
|
-
<div class="w-full h-full overflow-y-auto">
|
|
218
|
-
<x render=".sections[.selectedSectionIndex]"></x>
|
|
219
|
-
</div>
|
|
220
|
-
</div>
|
|
221
|
-
</div>`
|
|
222
|
-
});
|
|
223
|
-
var Section = component({
|
|
224
|
-
name: "Section",
|
|
225
|
-
fields: {
|
|
226
|
-
id: "?",
|
|
227
|
-
title: "No Title Section",
|
|
228
|
-
description: "",
|
|
229
|
-
// Optional 2-level grouping: sections sharing a `group` name cluster under one
|
|
230
|
-
// collapsible sidebar header. Empty string = ungrouped (top-level, flat — the
|
|
231
|
-
// default and the backward-compatible behavior).
|
|
232
|
-
group: "",
|
|
233
|
-
items: [],
|
|
234
|
-
filter: "",
|
|
235
|
-
initialized: false
|
|
236
|
-
},
|
|
237
|
-
statics: {
|
|
238
|
-
fromData(raw) {
|
|
239
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw) || raw.title == null) {
|
|
240
|
-
throw new Error(
|
|
241
|
-
`Section.fromData: expected a section object { title, items }, got ${JSON.stringify(raw)}. getExamples() must return a section object or an array of section objects.`
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
const { id, title, description = "", group, items = [] } = raw;
|
|
245
|
-
return this.make({
|
|
246
|
-
id: id ?? slugify(title),
|
|
247
|
-
title,
|
|
248
|
-
description,
|
|
249
|
-
// `group` is an optional string (single 2-level grouping). A non-string is
|
|
250
|
-
// ignored here (rendered ungrouped) rather than coerced; the normalize layer
|
|
251
|
-
// (tools/core/module.js) reports it as a shape error.
|
|
252
|
-
group: typeof group === "string" ? group : "",
|
|
253
|
-
items: items.map((v) => Example.fromData(v))
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
},
|
|
257
|
-
receive: {
|
|
258
|
-
onApplyFilter(draft, value, ctx) {
|
|
259
|
-
ctx.intent("exampleFilterChanged", [value], { route: ["dyn"] });
|
|
260
|
-
draft.filter = value;
|
|
261
|
-
},
|
|
262
|
-
onClearFilter(draft, ctx) {
|
|
263
|
-
ctx.intent("exampleFilterChanged", [""], { route: ["dyn"] });
|
|
264
|
-
draft.filter = this.constructor.getMetaClass().fields.filter.defaultValue;
|
|
265
|
-
},
|
|
266
|
-
// First display of this section: run each example's `on.init`, mark shown.
|
|
267
|
-
init(draft, ctx) {
|
|
268
|
-
fanoutLifecycle(ctx, this.items, "init");
|
|
269
|
-
draft.initialized = true;
|
|
270
|
-
},
|
|
271
|
-
resume(draft, ctx) {
|
|
272
|
-
fanoutLifecycle(ctx, this.items, "resume");
|
|
273
|
-
return this;
|
|
274
|
-
},
|
|
275
|
-
suspend(draft, ctx) {
|
|
276
|
-
fanoutLifecycle(ctx, this.items, "suspend");
|
|
277
|
-
return this;
|
|
278
|
-
}
|
|
279
|
-
},
|
|
280
|
-
alter: {
|
|
281
|
-
filterItem(_key, item) {
|
|
282
|
-
return this.filter === "" || fuzzyMatch(this.filter, `${item.title} ${item.description}`);
|
|
283
|
-
}
|
|
284
|
-
},
|
|
285
|
-
view: html`<section class="flex flex-col gap-3">
|
|
286
|
-
<div class="sticky top-0 z-10 bg-base-100 pt-1 pb-2 shadow-sm">
|
|
287
|
-
<h2 class="text-lg font-bold" @text=".title"></h2>
|
|
288
|
-
<p class="text-md italic opacity-60" @text=".description"></p>
|
|
289
|
-
</div>
|
|
290
|
-
<input
|
|
291
|
-
class="input w-full outline-0 focus:bg-base-200"
|
|
292
|
-
type="search"
|
|
293
|
-
placeholder="Filter examples"
|
|
294
|
-
:value=".filter"
|
|
295
|
-
@on.input="onApplyFilter e.value"
|
|
296
|
-
@on.keydown.cancel="onClearFilter"
|
|
297
|
-
/>
|
|
298
|
-
<div class="flex flex-col gap-3">
|
|
299
|
-
<x render-each=".items" @when="filterItem"></x>
|
|
300
|
-
</div>
|
|
301
|
-
</section>`
|
|
302
|
-
});
|
|
303
|
-
var SidebarEntry = component({
|
|
304
|
-
name: "SidebarEntry",
|
|
305
|
-
fields: { sectionId: "?", title: "", description: "", selected: false, visible: true },
|
|
306
|
-
receive: {
|
|
307
|
-
onClick(draft, ctx) {
|
|
308
|
-
ctx.intent("sectionSelected", [this.sectionId], { route: ["dyn"] });
|
|
309
|
-
return this;
|
|
310
|
-
}
|
|
311
|
-
},
|
|
312
|
-
view: html`<div
|
|
313
|
-
@if.class=".selected"
|
|
314
|
-
@then="'list-row cursor-pointer text-blue-400 hover:text-blue-500 font-semibold'"
|
|
315
|
-
@else="'list-row cursor-pointer hover:bg-base-200'"
|
|
316
|
-
:title=".description"
|
|
317
|
-
@on.click="onClick"
|
|
318
|
-
>
|
|
319
|
-
<div @text=".title" class="list-col-grow"></div>
|
|
320
|
-
<p class="text-xs opacity-60 list-col-wrap truncate" @text=".description"></p>
|
|
321
|
-
</div>`
|
|
322
|
-
});
|
|
323
|
-
var SidebarGroup = component({
|
|
324
|
-
name: "SidebarGroup",
|
|
325
|
-
fields: { name: "", collapsed: false, visible: true, rows: [] },
|
|
326
|
-
receive: {
|
|
327
|
-
onToggle(draft, ctx) {
|
|
328
|
-
ctx.intent("groupToggled", [this.name], { route: ["dyn"] });
|
|
329
|
-
return this;
|
|
330
|
-
}
|
|
331
|
-
},
|
|
332
|
-
// Visibility drives the render-each `when` predicates (not `@show` on the items):
|
|
333
|
-
// a `when` filter renders only visible rows while keeping each row's positional key,
|
|
334
|
-
// so an event's path resolves to the right row. `@show` on a render-each item would
|
|
335
|
-
// shift the rendered set and mis-map the click.
|
|
336
|
-
alter: {
|
|
337
|
-
rowVisible(_key, row) {
|
|
338
|
-
return row.visible;
|
|
339
|
-
}
|
|
340
|
-
},
|
|
341
|
-
view: html`<div class="flex flex-col">
|
|
342
|
-
<div
|
|
343
|
-
class="list-row cursor-pointer hover:bg-base-200 font-semibold flex items-center gap-1"
|
|
344
|
-
@show="truthy? .name"
|
|
345
|
-
@on.click="onToggle"
|
|
346
|
-
>
|
|
347
|
-
<span @show=".collapsed">▸</span>
|
|
348
|
-
<span @hide=".collapsed">▾</span>
|
|
349
|
-
<span @text=".name" class="list-col-grow"></span>
|
|
350
|
-
</div>
|
|
351
|
-
<div
|
|
352
|
-
@if.class="truthy? .name"
|
|
353
|
-
@then="'flex flex-col pl-3'"
|
|
354
|
-
@else="'flex flex-col'"
|
|
355
|
-
>
|
|
356
|
-
<x render-each=".rows" @when="rowVisible"></x>
|
|
357
|
-
</div>
|
|
358
|
-
</div>`
|
|
359
|
-
});
|
|
360
|
-
var Example = component({
|
|
361
|
-
name: "Example",
|
|
362
|
-
fields: {
|
|
363
|
-
id: "?",
|
|
364
|
-
title: "?",
|
|
365
|
-
description: "",
|
|
366
|
-
value: null,
|
|
367
|
-
view: "main",
|
|
368
|
-
intentHandlers: null,
|
|
369
|
-
on: null,
|
|
370
|
-
// Inspector tabs. activeTab selects which body renders; the *View fields hold
|
|
371
|
-
// prebuilt inspector instances (attached by mountStorybook before start, via
|
|
372
|
-
// src/storybook/inspect.js); the has* flags gate which tabs appear.
|
|
373
|
-
activeTab: "preview",
|
|
374
|
-
hasInspect: false,
|
|
375
|
-
hasComponent: false,
|
|
376
|
-
hasLint: false,
|
|
377
|
-
hasTest: false,
|
|
378
|
-
componentView: null,
|
|
379
|
-
instanceView: null,
|
|
380
|
-
lintView: null,
|
|
381
|
-
testView: null,
|
|
382
|
-
// Live dispatch activity for this example's component (`.value`): an ActivityLog
|
|
383
|
-
// (baked empty by src/storybook/inspect.js) the core observer appends to via
|
|
384
|
-
// logActivity. `hasActivity` gates the Activity tab so it only appears once
|
|
385
|
-
// something has actually happened.
|
|
386
|
-
activityLog: null,
|
|
387
|
-
hasActivity: false
|
|
388
|
-
},
|
|
389
|
-
// Storybook-only convention (read in mountStorybook, never by core): names the
|
|
390
|
-
// field on an example instance that holds its per-example intent-handler mocks.
|
|
391
|
-
intentOverridesField: "intentHandlers",
|
|
392
|
-
statics: {
|
|
393
|
-
fromData({
|
|
394
|
-
id,
|
|
395
|
-
title = "No Title Example",
|
|
396
|
-
description = "",
|
|
397
|
-
value = null,
|
|
398
|
-
view = "main",
|
|
399
|
-
intentHandlers = null,
|
|
400
|
-
on = null
|
|
401
|
-
}) {
|
|
402
|
-
id ??= slugify(title);
|
|
403
|
-
return this.make({
|
|
404
|
-
id,
|
|
405
|
-
title,
|
|
406
|
-
description,
|
|
407
|
-
value,
|
|
408
|
-
view,
|
|
409
|
-
intentHandlers,
|
|
410
|
-
on
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
},
|
|
414
|
-
methods: {
|
|
415
|
-
// An `intent` on the `dyn` leg leaves the example and walks into the storybook
|
|
416
|
-
// engine's own components. Nothing to warn about: if nothing answers, the walk
|
|
417
|
-
// runs out and the example hears `<name>Unhandled`.
|
|
418
|
-
runPhase(ctx, phase) {
|
|
419
|
-
dispatchPhase(ctx, ctx.at.field("value").buildPath(), phase, this.value);
|
|
420
|
-
}
|
|
421
|
-
},
|
|
422
|
-
// Lifecycle hooks: the section forwards init/resume/suspend here; each runs the
|
|
423
|
-
// matching `on` phase's actions against this example's component (`.value`).
|
|
424
|
-
receive: {
|
|
425
|
-
init(draft, ctx) {
|
|
426
|
-
this.runPhase(ctx, this.on?.init);
|
|
427
|
-
return this;
|
|
428
|
-
},
|
|
429
|
-
resume(draft, ctx) {
|
|
430
|
-
this.runPhase(ctx, this.on?.resume);
|
|
431
|
-
return this;
|
|
432
|
-
},
|
|
433
|
-
suspend(draft, ctx) {
|
|
434
|
-
this.runPhase(ctx, this.on?.suspend);
|
|
435
|
-
return this;
|
|
436
|
-
},
|
|
437
|
-
// Append one activity row to the example's ActivityLog. Dispatched by the storybook
|
|
438
|
-
// observer at this example's node — a path OUTSIDE `.value`, so the observer's own
|
|
439
|
-
// filter ignores it and this append is never itself logged (no loop).
|
|
440
|
-
logActivity(draft, entry) {
|
|
441
|
-
draft.activityLog.events.unshift(entry);
|
|
442
|
-
if (draft.activityLog.events.length > 50) draft.activityLog.events.length = 50;
|
|
443
|
-
draft.activityLog.hasEvents = true;
|
|
444
|
-
draft.hasActivity = true;
|
|
445
|
-
},
|
|
446
|
-
onLogSelected(draft) {
|
|
447
|
-
console.log(this.value);
|
|
448
|
-
return this;
|
|
449
|
-
},
|
|
450
|
-
onFocusSelected(draft, ctx) {
|
|
451
|
-
ctx.intent("exampleFocusRequested", [this], { route: ["dyn"] });
|
|
452
|
-
},
|
|
453
|
-
setActiveTab(draft, value) {
|
|
454
|
-
draft.activeTab = value;
|
|
455
|
-
}
|
|
456
|
-
},
|
|
457
|
-
view: html`<div class="card card-border bg-base-100 shadow-md">
|
|
458
|
-
<div class="card-body">
|
|
459
|
-
<h2 class="card-title flex justify-between">
|
|
460
|
-
<a :href="$'#example-{.id}'" :id="$'example-{.id}'" @text=".title"></a>
|
|
461
|
-
<div class="flex gap-2">
|
|
462
|
-
<button class="btn btn-ghost btn-sm" @on.click="onFocusSelected">
|
|
463
|
-
focus
|
|
464
|
-
</button>
|
|
465
|
-
<button class="btn btn-ghost btn-sm" @on.click="onLogSelected">
|
|
466
|
-
log
|
|
467
|
-
</button>
|
|
468
|
-
</div>
|
|
469
|
-
</h2>
|
|
470
|
-
<p class="text-md italic opacity-60" @text=".description"></p>
|
|
471
|
-
<div role="tablist" class="tabs tabs-border" @show=".hasInspect">
|
|
472
|
-
<a
|
|
473
|
-
role="tab"
|
|
474
|
-
@if.class="equals? .activeTab 'preview'"
|
|
475
|
-
@then="'tab tab-active'"
|
|
476
|
-
@else="'tab'"
|
|
477
|
-
@on.click="setActiveTab 'preview'"
|
|
478
|
-
>
|
|
479
|
-
Preview
|
|
480
|
-
</a>
|
|
481
|
-
<a
|
|
482
|
-
role="tab"
|
|
483
|
-
@show=".hasComponent"
|
|
484
|
-
@if.class="equals? .activeTab 'component'"
|
|
485
|
-
@then="'tab tab-active'"
|
|
486
|
-
@else="'tab'"
|
|
487
|
-
@on.click="setActiveTab 'component'"
|
|
488
|
-
>
|
|
489
|
-
Component
|
|
490
|
-
</a>
|
|
491
|
-
<a
|
|
492
|
-
role="tab"
|
|
493
|
-
@if.class="equals? .activeTab 'instance'"
|
|
494
|
-
@then="'tab tab-active'"
|
|
495
|
-
@else="'tab'"
|
|
496
|
-
@on.click="setActiveTab 'instance'"
|
|
497
|
-
>
|
|
498
|
-
Instance
|
|
499
|
-
</a>
|
|
500
|
-
<a
|
|
501
|
-
role="tab"
|
|
502
|
-
@show=".hasLint"
|
|
503
|
-
@if.class="equals? .activeTab 'lint'"
|
|
504
|
-
@then="'tab tab-active'"
|
|
505
|
-
@else="'tab'"
|
|
506
|
-
@on.click="setActiveTab 'lint'"
|
|
507
|
-
>
|
|
508
|
-
Lint
|
|
509
|
-
</a>
|
|
510
|
-
<a
|
|
511
|
-
role="tab"
|
|
512
|
-
@show=".hasTest"
|
|
513
|
-
@if.class="equals? .activeTab 'test'"
|
|
514
|
-
@then="'tab tab-active'"
|
|
515
|
-
@else="'tab'"
|
|
516
|
-
@on.click="setActiveTab 'test'"
|
|
517
|
-
>
|
|
518
|
-
Test
|
|
519
|
-
</a>
|
|
520
|
-
<a
|
|
521
|
-
role="tab"
|
|
522
|
-
@show=".hasActivity"
|
|
523
|
-
@if.class="equals? .activeTab 'activity'"
|
|
524
|
-
@then="'tab tab-active'"
|
|
525
|
-
@else="'tab'"
|
|
526
|
-
@on.click="setActiveTab 'activity'"
|
|
527
|
-
>
|
|
528
|
-
Activity
|
|
529
|
-
</a>
|
|
530
|
-
</div>
|
|
531
|
-
<div @show="equals? .activeTab 'preview'">
|
|
532
|
-
<div class="bg-base-100 p-3" @push-view=".view">
|
|
533
|
-
<x render=".value"></x>
|
|
534
|
-
</div>
|
|
535
|
-
</div>
|
|
536
|
-
<div class="p-3" @show="equals? .activeTab 'component'">
|
|
537
|
-
<x render=".componentView"></x>
|
|
538
|
-
</div>
|
|
539
|
-
<div class="p-3" @show="equals? .activeTab 'instance'">
|
|
540
|
-
<x render=".instanceView"></x>
|
|
541
|
-
</div>
|
|
542
|
-
<div class="p-3" @show="equals? .activeTab 'lint'">
|
|
543
|
-
<x render=".lintView"></x>
|
|
544
|
-
</div>
|
|
545
|
-
<div class="p-3" @show="equals? .activeTab 'test'">
|
|
546
|
-
<x render=".testView"></x>
|
|
547
|
-
</div>
|
|
548
|
-
<div class="p-3" @show="equals? .activeTab 'activity'">
|
|
549
|
-
<x render=".activityLog"></x>
|
|
550
|
-
</div>
|
|
551
|
-
</div>
|
|
552
|
-
</div>`
|
|
553
|
-
});
|
|
554
|
-
function markSidebarSelected(storybook, id) {
|
|
555
|
-
for (const group of storybook.sidebar) {
|
|
556
|
-
for (const entry of group.rows) entry.selected = entry.sectionId === id;
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
function selectSectionAtIndex(storybook, index) {
|
|
560
|
-
if (storybook.sections.length === 0) return;
|
|
561
|
-
const safeIndex = index >= 0 && index < storybook.sections.length ? index : 0;
|
|
562
|
-
storybook.selectedSectionIndex = safeIndex;
|
|
563
|
-
markSidebarSelected(storybook, storybook.sections[safeIndex]?.id);
|
|
564
|
-
}
|
|
565
|
-
function applyFilterToSidebar(storybook, filter) {
|
|
566
|
-
const active = (filter ?? "") !== "";
|
|
567
|
-
for (const group of storybook.sidebar) {
|
|
568
|
-
let anyMatch = false;
|
|
569
|
-
for (const entry of group.rows) {
|
|
570
|
-
const match = !active || fuzzyMatch(filter, `${entry.title} ${entry.description}`);
|
|
571
|
-
if (match) anyMatch = true;
|
|
572
|
-
entry.visible = match && !group.collapsed;
|
|
573
|
-
}
|
|
574
|
-
group.visible = active ? anyMatch : true;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
function toggleSidebarGroup(storybook, name) {
|
|
578
|
-
const active = storybook.filter !== "";
|
|
579
|
-
const group = storybook.sidebar.find((candidate) => candidate.name === name);
|
|
580
|
-
if (!group) return;
|
|
581
|
-
group.collapsed = !group.collapsed;
|
|
582
|
-
for (const entry of group.rows) {
|
|
583
|
-
const match = !active || fuzzyMatch(storybook.filter, `${entry.title} ${entry.description}`);
|
|
584
|
-
entry.visible = match && !group.collapsed;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
function selectSectionWithId(storybook, id) {
|
|
588
|
-
const index = id ? storybook.sections.findIndex((section) => section.id === id) : storybook.selectedSectionIndex;
|
|
589
|
-
selectSectionAtIndex(storybook, index);
|
|
590
|
-
}
|
|
591
|
-
function focusExampleByIds(storybook, sectionId, exampleId) {
|
|
592
|
-
if (!sectionId || !exampleId) return;
|
|
593
|
-
const section = storybook.sections.find((candidate) => candidate.id === sectionId);
|
|
594
|
-
const example = section?.items.find((candidate) => candidate.id === exampleId);
|
|
595
|
-
if (!example) return;
|
|
596
|
-
storybook.sectionId = sectionId;
|
|
597
|
-
storybook.exampleId = exampleId;
|
|
598
|
-
storybook.focusExample = example.value;
|
|
599
|
-
}
|
|
600
|
-
function setSelectedSectionFilter(storybook, value) {
|
|
601
|
-
const section = storybook.sections[storybook.selectedSectionIndex];
|
|
602
|
-
if (section) section.filter = value ?? "";
|
|
603
|
-
}
|
|
604
|
-
function transitionSections(ctx, sb, oldIndex, newIndex) {
|
|
605
|
-
const changed = oldIndex !== newIndex;
|
|
606
|
-
if (changed && sb.sections[oldIndex]?.initialized)
|
|
607
|
-
ctx.at.index("sections", oldIndex).send("suspend", []);
|
|
608
|
-
const target = sb.sections[newIndex];
|
|
609
|
-
if (!target) return;
|
|
610
|
-
if (!target.initialized) ctx.at.index("sections", newIndex).send("init", []);
|
|
611
|
-
else if (changed) ctx.at.index("sections", newIndex).send("resume", []);
|
|
612
|
-
}
|
|
613
|
-
function fanoutLifecycle(ctx, items, name) {
|
|
614
|
-
items.forEach((_item, j) => {
|
|
615
|
-
ctx.at.index("items", j).send(name, []);
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
function fuzzyMatch(query, target) {
|
|
619
|
-
const q = query.toLowerCase().trim();
|
|
620
|
-
if (q === "") return true;
|
|
621
|
-
const t = target.toLowerCase();
|
|
622
|
-
if (t.includes(q)) return true;
|
|
623
|
-
const budget = q.length + Math.max(1, Math.floor(q.length / 2));
|
|
624
|
-
for (let start = 0; start < t.length; start++) {
|
|
625
|
-
if (t[start] !== q[0]) continue;
|
|
626
|
-
let qi = 1;
|
|
627
|
-
let ti = start + 1;
|
|
628
|
-
for (; ti < t.length && qi < q.length; ti++) {
|
|
629
|
-
if (t[ti] === q[qi]) qi++;
|
|
630
|
-
}
|
|
631
|
-
if (qi === q.length && ti - start <= budget) return true;
|
|
632
|
-
}
|
|
633
|
-
return false;
|
|
634
|
-
}
|
|
635
|
-
function slugify(str) {
|
|
636
|
-
return String(str).normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
637
|
-
}
|
|
638
|
-
function buildSidebar(sections) {
|
|
639
|
-
const groups = /* @__PURE__ */ new Map();
|
|
640
|
-
const singles = [];
|
|
641
|
-
sections.forEach((s) => {
|
|
642
|
-
const name = s.group ?? "";
|
|
643
|
-
if (name === "") singles.push(s);
|
|
644
|
-
else {
|
|
645
|
-
if (!groups.has(name)) groups.set(name, []);
|
|
646
|
-
groups.get(name).push(s);
|
|
647
|
-
}
|
|
648
|
-
});
|
|
649
|
-
const buckets = [];
|
|
650
|
-
for (const [name, secs] of groups)
|
|
651
|
-
buckets.push({ key: name.toLowerCase(), name, sections: secs });
|
|
652
|
-
for (const s of singles) buckets.push({ key: s.title.toLowerCase(), name: "", sections: [s] });
|
|
653
|
-
buckets.sort((a, b) => a.key.localeCompare(b.key));
|
|
654
|
-
return buckets.map(
|
|
655
|
-
(b) => SidebarGroup.make({
|
|
656
|
-
name: b.name,
|
|
657
|
-
collapsed: false,
|
|
658
|
-
visible: true,
|
|
659
|
-
rows: b.sections.map(
|
|
660
|
-
(s) => SidebarEntry.make({
|
|
661
|
-
sectionId: s.id,
|
|
662
|
-
title: s.title,
|
|
663
|
-
description: s.description,
|
|
664
|
-
selected: false,
|
|
665
|
-
visible: true
|
|
666
|
-
})
|
|
667
|
-
)
|
|
668
|
-
})
|
|
669
|
-
);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
// src/storybook/build.js
|
|
673
|
-
import { PASS } from "tutuca";
|
|
674
|
-
import { getComponents as getInspectorComponents } from "tutuca/components";
|
|
675
|
-
function buildStorybook(modules) {
|
|
676
|
-
const rawSections = modules.flatMap((m) => {
|
|
677
|
-
const raw = m.getExamples?.();
|
|
678
|
-
if (raw == null) return [];
|
|
679
|
-
return Array.isArray(raw) ? raw : [raw];
|
|
680
|
-
});
|
|
681
|
-
const sections = rawSections.map((s) => Section.fromData(s)).sort((a, b) => a.title.localeCompare(b.title));
|
|
682
|
-
const engineComponents = [
|
|
683
|
-
Storybook,
|
|
684
|
-
Section,
|
|
685
|
-
Example,
|
|
686
|
-
SidebarGroup,
|
|
687
|
-
SidebarEntry,
|
|
688
|
-
// ActivityLog + ActivityEntry arrive via getInspectorComponents() (tutuca/components).
|
|
689
|
-
...getInspectorComponents()
|
|
690
|
-
];
|
|
691
|
-
const engineSet = new Set(engineComponents);
|
|
692
|
-
const moduleComponents = modules.map((m) => {
|
|
693
|
-
const seen = /* @__PURE__ */ new Set();
|
|
694
|
-
const out = [];
|
|
695
|
-
for (const c of m.getComponents?.() ?? []) {
|
|
696
|
-
if (!c || engineSet.has(c) || seen.has(c)) continue;
|
|
697
|
-
seen.add(c);
|
|
698
|
-
out.push(c);
|
|
699
|
-
}
|
|
700
|
-
return out;
|
|
701
|
-
});
|
|
702
|
-
const macros = {};
|
|
703
|
-
const intentHandlers = {};
|
|
704
|
-
const overrideNames = /* @__PURE__ */ new Set();
|
|
705
|
-
for (const s of rawSections) {
|
|
706
|
-
for (const it of s?.items ?? []) {
|
|
707
|
-
for (const name in it?.intentHandlers ?? {}) overrideNames.add(name);
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
for (const m of modules) {
|
|
711
|
-
if (m.getMacros) Object.assign(macros, m.getMacros());
|
|
712
|
-
if (m.getIntentHandlers) Object.assign(intentHandlers, m.getIntentHandlers());
|
|
713
|
-
}
|
|
714
|
-
const components = [.../* @__PURE__ */ new Set([...engineComponents, ...moduleComponents.flat()])];
|
|
715
|
-
return {
|
|
716
|
-
root: Storybook.withSections(sections),
|
|
717
|
-
components,
|
|
718
|
-
engineComponents,
|
|
719
|
-
moduleComponents,
|
|
720
|
-
macros,
|
|
721
|
-
intentHandlers,
|
|
722
|
-
overrideNames
|
|
723
|
-
};
|
|
724
|
-
}
|
|
725
|
-
function buildExampleIntentHandlers({ intentHandlers: reals, overrideNames }) {
|
|
726
|
-
const names = /* @__PURE__ */ new Set([...Object.keys(reals), ...overrideNames]);
|
|
727
|
-
const makeMeta = (name) => async (...rest) => {
|
|
728
|
-
const ctx = rest.at(-1);
|
|
729
|
-
const args = rest.slice(0, -1);
|
|
730
|
-
let override = null;
|
|
731
|
-
ctx.walkPath((Comp, inst) => {
|
|
732
|
-
const field = Comp.extra?.intentOverridesField;
|
|
733
|
-
if (!field) return;
|
|
734
|
-
const map = inst[field] ?? null;
|
|
735
|
-
if (map && name in map) {
|
|
736
|
-
override = map[name];
|
|
737
|
-
return false;
|
|
738
|
-
}
|
|
739
|
-
});
|
|
740
|
-
const fn = override ?? reals[name];
|
|
741
|
-
if (!fn) return PASS;
|
|
742
|
-
return await fn(...args, ctx);
|
|
743
|
-
};
|
|
744
|
-
const handlers = {};
|
|
745
|
-
for (const name of names) handlers[name] = makeMeta(name);
|
|
746
|
-
return handlers;
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
// src/storybook/mount.js
|
|
750
|
-
import { injectCss, tutuca } from "tutuca";
|
|
751
|
-
import { produce as produce2 } from "tutuca/immer";
|
|
752
|
-
|
|
753
|
-
// src/storybook/activity.js
|
|
754
|
-
import { rootDispatcher } from "tutuca";
|
|
755
|
-
import { makeInspect, pathKeysLabel, recordToEntry } from "tutuca/components";
|
|
756
|
-
function storybookPathLabel(pathKeys) {
|
|
757
|
-
return pathKeysLabel(pathKeys.slice(2));
|
|
758
|
-
}
|
|
759
|
-
function subscribeExampleActivity(app) {
|
|
760
|
-
const inspect = makeInspect(app);
|
|
761
|
-
return app.observe((record) => {
|
|
762
|
-
const keys = record.pathKeys;
|
|
763
|
-
if (keys.length < 3 || keys[0].field !== "sections" || keys[1].field !== "items" || keys[2].field !== "value")
|
|
764
|
-
return;
|
|
765
|
-
const si = keys[0].key;
|
|
766
|
-
const ii = keys[1].key;
|
|
767
|
-
if (si === void 0 || ii === void 0) return;
|
|
768
|
-
const entry = recordToEntry(record, { inspect, pathLabel: storybookPathLabel });
|
|
769
|
-
rootDispatcher(app.transactor).at.index("sections", si).index("items", ii).send("logActivity", [entry]);
|
|
770
|
-
});
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
// src/storybook/inspect.js
|
|
774
|
-
import { ActivityLog, buildInspectorViews, isComponentInstance } from "tutuca/components";
|
|
775
|
-
import { produce } from "tutuca/immer";
|
|
776
|
-
function buildTestIndex(modules) {
|
|
777
|
-
const index = /* @__PURE__ */ new Map();
|
|
778
|
-
for (const m of modules) {
|
|
779
|
-
if (typeof m.getTests !== "function") continue;
|
|
780
|
-
const components = m.getComponents?.() ?? [];
|
|
781
|
-
for (const c of components) {
|
|
782
|
-
if (!index.has(c.name)) index.set(c.name, { getTests: m.getTests, components });
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
return index;
|
|
786
|
-
}
|
|
787
|
-
async function buildExampleInspectors(example, scope, testIndex, dev) {
|
|
788
|
-
const value = example.value;
|
|
789
|
-
const comp = isComponentInstance(value) ? scope.getCompFor(value) : null;
|
|
790
|
-
const entry = comp ? testIndex.get(comp.name) : null;
|
|
791
|
-
const views = await buildInspectorViews(value, scope, {
|
|
792
|
-
getTests: entry?.getTests ?? null,
|
|
793
|
-
components: entry?.components ?? [],
|
|
794
|
-
dev
|
|
795
|
-
});
|
|
796
|
-
return {
|
|
797
|
-
...views,
|
|
798
|
-
activityLog: ActivityLog.make({})
|
|
799
|
-
};
|
|
800
|
-
}
|
|
801
|
-
async function attachInspectorViews(root, scope, modules, dev = null) {
|
|
802
|
-
const testIndex = buildTestIndex(modules);
|
|
803
|
-
const updates = [];
|
|
804
|
-
for (let si = 0; si < root.sections.length; si++) {
|
|
805
|
-
for (let ii = 0; ii < root.sections[si].items.length; ii++) {
|
|
806
|
-
updates.push({
|
|
807
|
-
si,
|
|
808
|
-
ii,
|
|
809
|
-
values: await buildExampleInspectors(root.sections[si].items[ii], scope, testIndex, dev)
|
|
810
|
-
});
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
return produce(root, (draft) => {
|
|
814
|
-
for (const { si, ii, values } of updates) Object.assign(draft.sections[si].items[ii], values);
|
|
815
|
-
});
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
// src/storybook/themes.js
|
|
819
|
-
var MARGAUI_THEMES = [
|
|
820
|
-
"light",
|
|
821
|
-
"dark",
|
|
822
|
-
"abyss",
|
|
823
|
-
"acid",
|
|
824
|
-
"aqua",
|
|
825
|
-
"autumn",
|
|
826
|
-
"black",
|
|
827
|
-
"bumblebee",
|
|
828
|
-
"business",
|
|
829
|
-
"caramellatte",
|
|
830
|
-
"cmyk",
|
|
831
|
-
"coffee",
|
|
832
|
-
"corporate",
|
|
833
|
-
"cupcake",
|
|
834
|
-
"cyberpunk",
|
|
835
|
-
"dim",
|
|
836
|
-
"dracula",
|
|
837
|
-
"emerald",
|
|
838
|
-
"fantasy",
|
|
839
|
-
"forest",
|
|
840
|
-
"garden",
|
|
841
|
-
"halloween",
|
|
842
|
-
"lemonade",
|
|
843
|
-
"lofi",
|
|
844
|
-
"luxury",
|
|
845
|
-
"night",
|
|
846
|
-
"nord",
|
|
847
|
-
"pastel",
|
|
848
|
-
"retro",
|
|
849
|
-
"silk",
|
|
850
|
-
"sunset",
|
|
851
|
-
"synthwave",
|
|
852
|
-
"valentine",
|
|
853
|
-
"winter",
|
|
854
|
-
"wireframe"
|
|
855
|
-
];
|
|
856
|
-
var BUNDLED_THEMES = /* @__PURE__ */ new Set(["light", "dark"]);
|
|
857
|
-
|
|
858
|
-
// src/storybook/mount.js
|
|
859
|
-
async function mountStorybook(selector, modules, { compileCss, root, persistUrl = true, dev = null, noCache = false, themes = null } = {}) {
|
|
860
|
-
const app = tutuca(selector);
|
|
861
|
-
const built = buildStorybook(modules);
|
|
862
|
-
const themeBaseUrl = themes?.baseUrl ?? null;
|
|
863
|
-
const base = root ?? built.root;
|
|
864
|
-
app.state.set(
|
|
865
|
-
themeBaseUrl ? produce2(base, (draft) => {
|
|
866
|
-
draft.themes = MARGAUI_THEMES;
|
|
867
|
-
}) : base
|
|
868
|
-
);
|
|
869
|
-
const rootScope = app.registerComponents(built.engineComponents);
|
|
870
|
-
rootScope.registerMacros(built.macros);
|
|
871
|
-
rootScope.registerIntentHandlers(buildExampleIntentHandlers(built));
|
|
872
|
-
rootScope.registerIntentHandlers({ loadState: persistUrl ? loadState : loadStateBlank });
|
|
873
|
-
if (persistUrl) {
|
|
874
|
-
rootScope.registerIntentHandlers({ persistState });
|
|
875
|
-
}
|
|
876
|
-
if (themeBaseUrl) {
|
|
877
|
-
rootScope.registerIntentHandlers({ applyTheme });
|
|
878
|
-
}
|
|
879
|
-
const moduleScopes = built.moduleComponents.map((comps) => {
|
|
880
|
-
const scope = rootScope.enter();
|
|
881
|
-
scope.registerComponents(comps);
|
|
882
|
-
return scope;
|
|
883
|
-
});
|
|
884
|
-
if (dev && app.state.val?.sections) {
|
|
885
|
-
app.state.set(await attachInspectorViews(app.state.val, rootScope, modules, dev));
|
|
886
|
-
}
|
|
887
|
-
if (compileCss) {
|
|
888
|
-
injectCss("tutuca-storybook", await compileCss(app));
|
|
889
|
-
}
|
|
890
|
-
app.start({ noCache });
|
|
891
|
-
if (dev) subscribeExampleActivity(app);
|
|
892
|
-
app.sendAtRoot("init", []);
|
|
893
|
-
if (persistUrl) {
|
|
894
|
-
window.addEventListener("popstate", () => app.sendAtRoot("init", []));
|
|
895
|
-
}
|
|
896
|
-
app.scopes = { root: rootScope, modules: moduleScopes };
|
|
897
|
-
return app;
|
|
898
|
-
function persistState(state, instance, push) {
|
|
899
|
-
if (instance !== app.state.val) return;
|
|
900
|
-
const url = new URL(window.location.href);
|
|
901
|
-
for (const [k, v] of Object.entries(state)) {
|
|
902
|
-
if (v === "" || v == null) url.searchParams.delete(k);
|
|
903
|
-
else url.searchParams.set(k, String(v));
|
|
904
|
-
}
|
|
905
|
-
window.history[push ? "pushState" : "replaceState"](null, "", url);
|
|
906
|
-
}
|
|
907
|
-
function applyTheme(name, instance) {
|
|
908
|
-
if (instance !== app.state.val) return;
|
|
909
|
-
ensureThemeLink(name);
|
|
910
|
-
document.documentElement.dataset.theme = name;
|
|
911
|
-
}
|
|
912
|
-
function loadState() {
|
|
913
|
-
const p = new URLSearchParams(window.location.search);
|
|
914
|
-
return {
|
|
915
|
-
section: p.get("section"),
|
|
916
|
-
example: p.get("example"),
|
|
917
|
-
sectionFilter: p.get("sectionFilter") ?? "",
|
|
918
|
-
exampleFilter: p.get("exampleFilter") ?? "",
|
|
919
|
-
theme: resolveTheme(p.get("theme"))
|
|
920
|
-
};
|
|
921
|
-
}
|
|
922
|
-
function loadStateBlank() {
|
|
923
|
-
return {
|
|
924
|
-
section: null,
|
|
925
|
-
example: null,
|
|
926
|
-
sectionFilter: "",
|
|
927
|
-
exampleFilter: "",
|
|
928
|
-
theme: resolveTheme(null)
|
|
929
|
-
};
|
|
930
|
-
}
|
|
931
|
-
function resolveTheme(fromUrl) {
|
|
932
|
-
if (!themeBaseUrl) return "";
|
|
933
|
-
const name = MARGAUI_THEMES.includes(fromUrl) ? fromUrl : osTheme();
|
|
934
|
-
ensureThemeLink(name);
|
|
935
|
-
document.documentElement.dataset.theme = name;
|
|
936
|
-
return name;
|
|
937
|
-
}
|
|
938
|
-
function osTheme() {
|
|
939
|
-
return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
940
|
-
}
|
|
941
|
-
function ensureThemeLink(name) {
|
|
942
|
-
if (BUNDLED_THEMES.has(name) || document.getElementById(themeLinkId(name))) return;
|
|
943
|
-
const link = document.createElement("link");
|
|
944
|
-
link.rel = "stylesheet";
|
|
945
|
-
link.id = themeLinkId(name);
|
|
946
|
-
link.href = `${themeBaseUrl}${name}.css`;
|
|
947
|
-
document.head.append(link);
|
|
948
|
-
}
|
|
949
|
-
function themeLinkId(name) {
|
|
950
|
-
return `tutuca-theme-${name}`;
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
// src/storybook/index.js
|
|
955
|
-
function getComponents() {
|
|
956
|
-
return [Storybook, Section, Example, SidebarGroup, SidebarEntry];
|
|
957
|
-
}
|
|
958
|
-
export {
|
|
959
|
-
BUNDLED_THEMES,
|
|
960
|
-
Example,
|
|
961
|
-
MARGAUI_THEMES,
|
|
962
|
-
Section,
|
|
963
|
-
Storybook,
|
|
964
|
-
buildExampleIntentHandlers,
|
|
965
|
-
buildStorybook,
|
|
966
|
-
fuzzyMatch,
|
|
967
|
-
getComponents,
|
|
968
|
-
mountStorybook,
|
|
969
|
-
slugify
|
|
970
|
-
};
|