@poveste/app 0.13.1 → 0.15.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/dist/bundled/App.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/bundled/app.css +31 -31
- package/dist/bundled/components/HomeView.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/bundled/components/app/AppLogo.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/bundled/components/app/Breadcrumb.js +1 -1
- package/dist/bundled/components/app/Breadcrumb.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/bundled/components/app/TopBar.vue_vue_type_script_setup_true_lang.js +4 -3
- package/dist/bundled/components/app/TopBarChipItem.js +1 -1
- package/dist/bundled/components/base/BaseCheckbox.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/bundled/components/base/BaseEmpty.js +2 -2
- package/dist/bundled/components/base/BaseOverflowMenu.vue_vue_type_script_setup_true_lang.js +18 -15
- package/dist/bundled/components/base/BaseSelect.vue_vue_type_script_setup_true_lang.js +158 -26
- package/dist/bundled/components/base/BaseSplitPane.js +1 -1
- package/dist/bundled/components/base/BaseSplitPane.vue_vue_type_script_setup_true_lang.js +6 -4
- package/dist/bundled/components/command/CommandPrompts.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/bundled/components/command/CommandPromptsModal.vue_vue_type_script_setup_true_lang.js +4 -3
- package/dist/bundled/components/command/PromptSelect.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/bundled/components/command/PromptText.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/bundled/components/panel/ControlsComponentPropItem.vue_vue_type_script_setup_true_lang.js +20 -14
- package/dist/bundled/components/panel/PaneTabs.vue_vue_type_script_setup_true_lang.js +6 -6
- package/dist/bundled/components/panel/StatePresets.vue_vue_type_script_setup_true_lang.js +94 -55
- package/dist/bundled/components/panel/StoryControls.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/bundled/components/panel/StoryDocs.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/bundled/components/panel/StoryEvent.vue_vue_type_script_setup_true_lang.js +5 -1
- package/dist/bundled/components/panel/StoryEvents.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/bundled/components/panel/StorySidePanel.vue_vue_type_script_setup_true_lang.js +8 -6
- package/dist/bundled/components/panel/StorySourceCode.js +1 -1
- package/dist/bundled/components/panel/StorySourceCode.vue_vue_type_script_setup_true_lang.js +8 -6
- package/dist/bundled/components/search/SearchItemContent.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/bundled/components/search/SearchPane.vue_vue_type_script_setup_true_lang.js +16 -9
- package/dist/bundled/components/story/GenericMountStory.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/bundled/components/story/GenericRenderStory.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/bundled/components/story/StoryResponsivePreview.js +1 -1
- package/dist/bundled/components/story/StoryResponsivePreview.vue_vue_type_script_setup_true_lang.js +23 -8
- package/dist/bundled/components/story/StoryVariantGrid.js +1 -1
- package/dist/bundled/components/story/StoryVariantGrid.vue_vue_type_script_setup_true_lang.js +15 -10
- package/dist/bundled/components/story/StoryVariantGridItem.js +1 -1
- package/dist/bundled/components/story/StoryVariantGridItem.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/bundled/components/story/StoryVariantSinglePreviewRemote.vue_vue_type_script_setup_true_lang.js +9 -6
- package/dist/bundled/components/story/StoryView.vue_vue_type_script_setup_true_lang.js +7 -3
- package/dist/bundled/components/story/StoryViewer.js +1 -1
- package/dist/bundled/components/story/StoryViewer.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/bundled/components/toolbar/ToolbarBackground.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/bundled/components/toolbar/ToolbarResponsiveSize.vue_vue_type_script_setup_true_lang.js +17 -16
- package/dist/bundled/components/toolbar/ToolbarTitle.js +1 -1
- package/dist/bundled/components/toolbar/ToolbarTitle.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/bundled/components/tree/StoryGroup.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/bundled/components/tree/StoryList.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/bundled/components/tree/StoryListFolder.js +1 -1
- package/dist/bundled/components/tree/StoryListFolder.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/bundled/components/tree/StoryListItem.js +1 -1
- package/dist/bundled/components/tree/StoryListItem.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/bundled/plugin.js +4 -3
- package/dist/bundled/sandbox.js +5 -4
- package/dist/bundled/stores/folder.js +1 -1
- package/dist/bundled/stores/story.js +2 -2
- package/dist/bundled/util/commands.js +21 -10
- package/dist/bundled/util/dark.js +1 -1
- package/dist/bundled/util/docs.js +2 -1
- package/dist/bundled/util/events.js +1 -1
- package/dist/bundled/util/grid-slots.js +2 -2
- package/dist/bundled/util/icons.generated.js +0 -1
- package/dist/bundled/util/keyboard.js +9 -14
- package/dist/bundled/util/mapping.js +3 -3
- package/dist/bundled/util/preview-settings.js +1 -1
- package/dist/bundled/util/render-tracker.js +33 -0
- package/dist/bundled/util/variant.js +12 -2
- package/dist/style.css +23 -1
- package/package.json +14 -18
- package/src/__tests__/render-tracker.spec.ts +38 -0
- package/src/app/App.vue +7 -4
- package/src/app/components/HomeView.vue +1 -1
- package/src/app/components/app/AppLogo.vue +6 -2
- package/src/app/components/app/Breadcrumb.vue +2 -1
- package/src/app/components/app/TopBar.vue +7 -2
- package/src/app/components/app/TopBarChipItem.vue +1 -1
- package/src/app/components/base/BaseCheckbox.vue +1 -1
- package/src/app/components/base/BaseEmpty.vue +1 -1
- package/src/app/components/base/BaseListItemLink.vue +4 -2
- package/src/app/components/base/BaseOverflowMenu.vue +25 -18
- package/src/app/components/base/BaseSelect.vue +171 -15
- package/src/app/components/base/BaseSplitPane.vue +11 -6
- package/src/app/components/command/CommandPrompts.vue +1 -1
- package/src/app/components/command/CommandPromptsModal.vue +1 -0
- package/src/app/components/command/PromptSelect.vue +3 -2
- package/src/app/components/command/PromptText.vue +7 -3
- package/src/app/components/panel/ControlsComponentPropItem.vue +26 -15
- package/src/app/components/panel/PaneTabs.vue +6 -6
- package/src/app/components/panel/StatePresets.vue +125 -68
- package/src/app/components/panel/StoryControls.vue +1 -1
- package/src/app/components/panel/StoryDocs.vue +3 -1
- package/src/app/components/panel/StoryEvent.vue +7 -2
- package/src/app/components/panel/StoryEvents.vue +4 -1
- package/src/app/components/panel/StorySidePanel.vue +3 -1
- package/src/app/components/panel/StorySourceCode.vue +12 -8
- package/src/app/components/search/SearchItemContent.vue +2 -1
- package/src/app/components/search/SearchPane.vue +16 -8
- package/src/app/components/story/GenericMountStory.vue +2 -1
- package/src/app/components/story/GenericRenderStory.vue +2 -1
- package/src/app/components/story/StoryResponsivePreview.vue +36 -7
- package/src/app/components/story/StoryVariantGrid.vue +33 -16
- package/src/app/components/story/StoryVariantGridItem.vue +1 -1
- package/src/app/components/story/StoryVariantSinglePreviewRemote.vue +14 -8
- package/src/app/components/story/StoryView.vue +10 -4
- package/src/app/components/story/StoryViewer.vue +3 -3
- package/src/app/components/toolbar/ToolbarBackground.vue +1 -1
- package/src/app/components/toolbar/ToolbarResponsiveSize.vue +23 -3
- package/src/app/components/toolbar/ToolbarTitle.vue +2 -2
- package/src/app/components/tree/StoryGroup.vue +2 -2
- package/src/app/components/tree/StoryList.vue +2 -2
- package/src/app/components/tree/StoryListFolder.vue +2 -2
- package/src/app/components/tree/StoryListItem.vue +1 -1
- package/src/app/plugin.ts +7 -3
- package/src/app/sandbox.ts +9 -3
- package/src/app/stores/folder.ts +1 -1
- package/src/app/stores/story.ts +2 -2
- package/src/app/types.ts +6 -6
- package/src/app/util/commands.ts +27 -17
- package/src/app/util/dark.ts +1 -1
- package/src/app/util/docs.ts +2 -1
- package/src/app/util/env.ts +0 -1
- package/src/app/util/events.ts +6 -5
- package/src/app/util/grid-slots.ts +2 -2
- package/src/app/util/icons.generated.ts +1 -1
- package/src/app/util/keyboard.ts +10 -11
- package/src/app/util/mapping.ts +3 -3
- package/src/app/util/preview-settings.ts +1 -1
- package/src/app/util/render-tracker.ts +31 -0
- package/src/app/util/scroll.ts +5 -1
- package/src/app/util/state.ts +4 -4
- package/src/app/util/variant.ts +16 -3
- package/src/app/components/base/BaseCopyIcon.vue +0 -34
|
@@ -2,27 +2,38 @@ import { router } from "../router.js";
|
|
|
2
2
|
import { useStoryStore } from "../stores/story.js";
|
|
3
3
|
import { openInEditor } from "./open-in-editor.js";
|
|
4
4
|
//#region src/app/util/commands.ts
|
|
5
|
+
/**
|
|
6
|
+
* The path "Open file in editor" should open, or undefined when there is none.
|
|
7
|
+
*
|
|
8
|
+
* `Story.file` is optional, and `let file: string` used to be assigned
|
|
9
|
+
* `story.file?.filePath` in both branches — so a story without a file produced
|
|
10
|
+
* `file: undefined`, and `openInEditor` sent
|
|
11
|
+
* `__open-in-editor?file=undefined` to the dev server: a request to open a file
|
|
12
|
+
* literally named "undefined", with nothing said in the UI (#667).
|
|
13
|
+
*
|
|
14
|
+
* A docs-only story prefers its markdown, falling back to the component file
|
|
15
|
+
* when the story has both.
|
|
16
|
+
*/
|
|
17
|
+
function editableFilePath(story) {
|
|
18
|
+
const file = story?.file;
|
|
19
|
+
if (!file) return void 0;
|
|
20
|
+
return story?.docsOnly ? file.docsFilePath ?? file.filePath : file.filePath;
|
|
21
|
+
}
|
|
5
22
|
var builtinCommands = [{
|
|
6
23
|
id: "builtin:open-in-editor",
|
|
7
24
|
label: "Open file in editor",
|
|
8
25
|
icon: "carbon:script-reference",
|
|
9
|
-
showIf: ({ route }) => route.name === "story" && !!useStoryStore().currentStory,
|
|
10
|
-
getParams: () => {
|
|
11
|
-
const story = useStoryStore().currentStory;
|
|
12
|
-
let file;
|
|
13
|
-
if (story.docsOnly) file = story.file?.docsFilePath ?? story.file?.filePath;
|
|
14
|
-
else file = story.file?.filePath;
|
|
15
|
-
return { file };
|
|
16
|
-
},
|
|
26
|
+
showIf: ({ route }) => route.name === "story" && !!editableFilePath(useStoryStore().currentStory),
|
|
27
|
+
getParams: () => ({ file: editableFilePath(useStoryStore().currentStory) }),
|
|
17
28
|
clientAction: ({ file }) => {
|
|
18
|
-
openInEditor(file);
|
|
29
|
+
if (file) openInEditor(file);
|
|
19
30
|
}
|
|
20
31
|
}, {
|
|
21
32
|
id: "builtin:poveste-docs",
|
|
22
33
|
label: "Open Poveste Documentation",
|
|
23
34
|
icon: "carbon:help",
|
|
24
35
|
clientAction: () => {
|
|
25
|
-
window.open("https://poveste.dev/guide/getting-started
|
|
36
|
+
window.open("https://poveste.dev/guide/getting-started", "_blank");
|
|
26
37
|
}
|
|
27
38
|
}];
|
|
28
39
|
function executeCommand(command, params) {
|
|
@@ -4,7 +4,7 @@ import { watch } from "@poveste/vendors/vue";
|
|
|
4
4
|
//#region src/app/util/dark.ts
|
|
5
5
|
var isDark = useDark({
|
|
6
6
|
valueDark: "ptw-dark",
|
|
7
|
-
initialValue: povesteConfig.theme.defaultColorScheme,
|
|
7
|
+
...povesteConfig.theme.defaultColorScheme ? { initialValue: povesteConfig.theme.defaultColorScheme } : {},
|
|
8
8
|
storageKey: "poveste-color-scheme",
|
|
9
9
|
storage: povesteConfig.theme.storeColorScheme ? localStorage : sessionStorage
|
|
10
10
|
});
|
|
@@ -7,7 +7,8 @@ async function getSourceCode(story, variant) {
|
|
|
7
7
|
const source = variant.slots?.().source()[0].children;
|
|
8
8
|
if (source) return unindent(source);
|
|
9
9
|
} else {
|
|
10
|
-
const
|
|
10
|
+
const supportPluginId = story.file?.supportPluginId;
|
|
11
|
+
const clientPlugin = supportPluginId ? clientSupportPlugins[supportPluginId] : void 0;
|
|
11
12
|
if (clientPlugin) return (await clientPlugin()).generateSourceCode(variant);
|
|
12
13
|
}
|
|
13
14
|
const sourceLoader = story.file?.source;
|
|
@@ -19,7 +19,7 @@ async function logEvent(name, argument) {
|
|
|
19
19
|
}
|
|
20
20
|
function stringifyEvent(e) {
|
|
21
21
|
const obj = {};
|
|
22
|
-
for (const k in e) obj[k] = e
|
|
22
|
+
for (const k in e) obj[k] = Reflect.get(e, k);
|
|
23
23
|
return JSON.stringify(obj, (k, v) => {
|
|
24
24
|
if (v instanceof Node) return "Node";
|
|
25
25
|
if (v instanceof Window) return "Window";
|
|
@@ -11,7 +11,7 @@ function assignSlots(slots, storyId, visible) {
|
|
|
11
11
|
const next = slots.map((slot) => slot.storyId === storyId && position.has(slot.variant.id) ? {
|
|
12
12
|
...slot,
|
|
13
13
|
visible: true,
|
|
14
|
-
order: position.get(slot.variant.id)
|
|
14
|
+
order: position.get(slot.variant.id) ?? 0
|
|
15
15
|
} : {
|
|
16
16
|
...slot,
|
|
17
17
|
visible: false,
|
|
@@ -57,7 +57,7 @@ function trimSlots(slots) {
|
|
|
57
57
|
next = kept;
|
|
58
58
|
}
|
|
59
59
|
let end = next.length;
|
|
60
|
-
while (end > 0 && !next[end - 1]
|
|
60
|
+
while (end > 0 && !next[end - 1]?.visible) end--;
|
|
61
61
|
return end === next.length ? next : next.slice(0, end);
|
|
62
62
|
}
|
|
63
63
|
//#endregion
|
|
@@ -6,7 +6,6 @@ var iconCollections = [
|
|
|
6
6
|
"add-alt": { "body": "<path fill=\"currentColor\" d=\"M16 4c6.6 0 12 5.4 12 12s-5.4 12-12 12S4 22.6 4 16S9.4 4 16 4m0-2C8.3 2 2 8.3 2 16s6.3 14 14 14s14-6.3 14-14S23.7 2 16 2\"/><path fill=\"currentColor\" d=\"M24 15h-7V8h-2v7H8v2h7v7h2v-7h7z\"/>" },
|
|
7
7
|
"area": { "body": "<path fill=\"currentColor\" d=\"M27 22.142V9.858A3.992 3.992 0 1 0 22.142 5H9.858A3.992 3.992 0 1 0 5 9.858v12.284A3.992 3.992 0 1 0 9.858 27h12.284A3.992 3.992 0 1 0 27 22.142M26 4a2 2 0 1 1-2 2a2 2 0 0 1 2-2M4 6a2 2 0 1 1 2 2a2 2 0 0 1-2-2m2 22a2 2 0 1 1 2-2a2 2 0 0 1-2 2m16.142-3H9.858A4 4 0 0 0 7 22.142V9.858A4 4 0 0 0 9.858 7h12.284A4 4 0 0 0 25 9.858v12.284A3.99 3.99 0 0 0 22.142 25M26 28a2 2 0 1 1 2-2a2.003 2.003 0 0 1-2 2\"/>" },
|
|
8
8
|
"audio-console": { "body": "<path fill=\"currentColor\" d=\"M28 14.005h-1v-12h-2v12h-1a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h1v10h2v-10h1a2.003 2.003 0 0 0 2-2v-2a2 2 0 0 0-2-2m0 4h-4v-2h4Zm-10-12h-1v-4h-2v4h-1a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h1v18h2v-18h1a2 2 0 0 0 2-2v-2a2 2 0 0 0-2-2m0 4h-4v-2h4Zm-10 10H7v-18H5v18H4a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h1v4h2v-4h1a2 2 0 0 0 2-2v-2a2 2 0 0 0-2-2m0 4H4v-2h4Z\"/>" },
|
|
9
|
-
"bring-forward": { "body": "<path fill=\"currentColor\" d=\"M12 15h-2v-3a2 2 0 0 1 2-2h3v2h-3Zm3 15h-3a2 2 0 0 1-2-2v-3h2v3h3Zm3-2h4v2h-4zm10 2h-3v-2h3v-3h2v3a2 2 0 0 1-2 2M10 18h2v4h-2zm18 0h2v4h-2zm2-3h-2v-3h-3v-2h3a2 2 0 0 1 2 2Zm-12-5h4v2h-4z\"/><path fill=\"currentColor\" d=\"M8 22H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v4h-2V4H4v16h4Z\"/>" },
|
|
10
9
|
"caret-down": { "body": "<path fill=\"currentColor\" d=\"m24 12l-8 10l-8-10z\"/>" },
|
|
11
10
|
"caret-right": { "body": "<path fill=\"currentColor\" d=\"m12 8l10 8l-10 8z\"/>" },
|
|
12
11
|
"checkbox": { "body": "<path fill=\"currentColor\" d=\"M26 4H6a2 2 0 0 0-2 2v20a2 2 0 0 0 2 2h20a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2M6 26V6h20v20Z\"/>" },
|
|
@@ -24,30 +24,24 @@ var pressedKeys = /* @__PURE__ */ new Set();
|
|
|
24
24
|
var trackedWindows = shallowRef([]);
|
|
25
25
|
function bindTracking(target) {
|
|
26
26
|
const onKeydown = (event) => {
|
|
27
|
-
for (const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
mod.pressed = true;
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
27
|
+
for (const mod of Object.values(modifiers)) if (mod.key === event.key) {
|
|
28
|
+
mod.pressed = true;
|
|
29
|
+
return;
|
|
33
30
|
}
|
|
34
31
|
pressedKeys.add(event.key.toLocaleLowerCase());
|
|
35
32
|
};
|
|
36
33
|
const onKeyup = (event) => {
|
|
37
34
|
requestAnimationFrame(() => {
|
|
38
35
|
pressedKeys.clear();
|
|
39
|
-
for (const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
mod.pressed = false;
|
|
43
|
-
break;
|
|
44
|
-
}
|
|
36
|
+
for (const mod of Object.values(modifiers)) if (mod.key === event.key) {
|
|
37
|
+
mod.pressed = false;
|
|
38
|
+
break;
|
|
45
39
|
}
|
|
46
40
|
});
|
|
47
41
|
};
|
|
48
42
|
const onBlur = () => {
|
|
49
43
|
pressedKeys.clear();
|
|
50
|
-
for (const
|
|
44
|
+
for (const mod of Object.values(modifiers)) mod.pressed = false;
|
|
51
45
|
};
|
|
52
46
|
target.addEventListener("keydown", onKeydown);
|
|
53
47
|
target.addEventListener("keyup", onKeyup);
|
|
@@ -88,7 +82,8 @@ function isMatchingShortcut(shortcut) {
|
|
|
88
82
|
function isMatchingCombination(combination) {
|
|
89
83
|
const splitted = combination.split("+").map((key) => key.trim());
|
|
90
84
|
const targetKey = splitted.pop();
|
|
91
|
-
|
|
85
|
+
if (!targetKey) return false;
|
|
86
|
+
for (const [name, mod] of Object.entries(modifiers)) if (splitted.includes(name) !== mod.pressed) return false;
|
|
92
87
|
return pressedKeys.has(targetKey);
|
|
93
88
|
}
|
|
94
89
|
function formatKey(key) {
|
|
@@ -20,7 +20,7 @@ function mapFile(file, existingFile) {
|
|
|
20
20
|
file: markRaw(result),
|
|
21
21
|
variants: file.story.variants.map((v) => mapVariant(v, existingFile.story.variants.find((item) => item.id === v.id)))
|
|
22
22
|
};
|
|
23
|
-
else if (key !== "component") result
|
|
23
|
+
else if (key !== "component") Reflect.set(result, key, Reflect.get(file, key));
|
|
24
24
|
} else result = {
|
|
25
25
|
...file,
|
|
26
26
|
component: markRaw(file.component),
|
|
@@ -38,14 +38,14 @@ function mapVariant(variant, existingVariant) {
|
|
|
38
38
|
let result;
|
|
39
39
|
if (existingVariant) {
|
|
40
40
|
result = existingVariant;
|
|
41
|
-
for (const key in variant) if (!copiedFromExistingVariant.includes(key)) result
|
|
41
|
+
for (const key in variant) if (!copiedFromExistingVariant.includes(key)) Reflect.set(result, key, Reflect.get(variant, key));
|
|
42
42
|
} else result = {
|
|
43
43
|
...variant,
|
|
44
44
|
state: reactive({
|
|
45
45
|
_hPropState: {},
|
|
46
46
|
_hPropDefs: {}
|
|
47
47
|
}),
|
|
48
|
-
setupApp:
|
|
48
|
+
setupApp: void 0,
|
|
49
49
|
slots: () => ({
|
|
50
50
|
default: void 0,
|
|
51
51
|
controls: void 0,
|
|
@@ -24,7 +24,7 @@ function applyPreviewSettings(settings) {
|
|
|
24
24
|
if (povesteConfig.autoApplyContrastColor) document.documentElement.style.color = contrastColor;
|
|
25
25
|
}
|
|
26
26
|
function getContrastColor(setting) {
|
|
27
|
-
return povesteConfig.backgroundPresets
|
|
27
|
+
return povesteConfig.backgroundPresets?.find((preset) => preset.color === setting.backgroundColor)?.contrastColor ?? "unset";
|
|
28
28
|
}
|
|
29
29
|
//#endregion
|
|
30
30
|
export { PREVIEW_SETTINGS_STORAGE_KEY, applyPreviewSettings, getContrastColor, loadStoredPreviewSettings, receivedSettings };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//#region src/app/util/render-tracker.ts
|
|
2
|
+
/**
|
|
3
|
+
* Whether the render now under way inside a realm reported a throw.
|
|
4
|
+
*
|
|
5
|
+
* The host cannot see inside the realm, so "did this render succeed" has to be
|
|
6
|
+
* read off the two named messages it does see. A throw reaches
|
|
7
|
+
* `app.config.errorHandler` during mount and `SANDBOX_READY` is emitted after
|
|
8
|
+
* `await host.mount()` returns, so the order is always error-then-ready — which
|
|
9
|
+
* makes a ready with nothing recorded since the render began a clean render.
|
|
10
|
+
*
|
|
11
|
+
* Clearing on the story and variant ids changing instead cleared the pair being
|
|
12
|
+
* navigated *to*, so arriving at a broken story wiped its marker, and the fixed
|
|
13
|
+
* story the comment was about was never reached at all: a file edited in place
|
|
14
|
+
* keeps its ids, so that watcher did not fire on the one path it named (#597).
|
|
15
|
+
*/
|
|
16
|
+
function createRenderTracker() {
|
|
17
|
+
let threw = false;
|
|
18
|
+
return {
|
|
19
|
+
/** A new render is starting: what the previous one reported no longer holds. */
|
|
20
|
+
begin() {
|
|
21
|
+
threw = false;
|
|
22
|
+
},
|
|
23
|
+
threw() {
|
|
24
|
+
threw = true;
|
|
25
|
+
},
|
|
26
|
+
/** True when the render that has just reported ready said nothing before it. */
|
|
27
|
+
wasClean() {
|
|
28
|
+
return !threw;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { createRenderTracker };
|
|
@@ -8,14 +8,24 @@ import { useRoute } from "@poveste/vendors/vue-router";
|
|
|
8
8
|
* has to tell "a variant is on its way" from "this story is waiting for the
|
|
9
9
|
* reader to pick one" needs the same answer it will give — hence one predicate
|
|
10
10
|
* rather than two copies of the condition (#328).
|
|
11
|
+
*
|
|
12
|
+
* Any story with variants, deliberately. It used to require a remembered choice
|
|
13
|
+
* or exactly one variant, which made `StoryView`'s `variants[0]` fallback
|
|
14
|
+
* unreachable: a story with several variants and no history satisfied neither
|
|
15
|
+
* clause, so the first visit of a session showed an empty panel and every visit
|
|
16
|
+
* after it worked, because the first one recorded the history (#724).
|
|
17
|
+
*
|
|
18
|
+
* No layout clause. Grid stories already auto-selected on revisit through the
|
|
19
|
+
* remembered choice, so excluding them here would auto-open them on the second
|
|
20
|
+
* visit and never the first — this defect, made permanent for one layout.
|
|
11
21
|
*/
|
|
12
22
|
function autoSelectsVariant(story) {
|
|
13
|
-
return !!story &&
|
|
23
|
+
return !!story && story.variants.length > 0;
|
|
14
24
|
}
|
|
15
25
|
function useCurrentVariantRoute(variant) {
|
|
16
26
|
const route = useRoute();
|
|
17
27
|
return {
|
|
18
|
-
isActive: computed(() => route.query
|
|
28
|
+
isActive: computed(() => route.query["variantId"] === variant.value.id),
|
|
19
29
|
targetRoute: computed(() => ({
|
|
20
30
|
...route,
|
|
21
31
|
query: {
|
package/dist/style.css
CHANGED
|
@@ -274,7 +274,7 @@
|
|
|
274
274
|
inherits: false;
|
|
275
275
|
initial-value: solid;
|
|
276
276
|
}
|
|
277
|
-
.__poveste-json-code[data-v-
|
|
277
|
+
.__poveste-json-code[data-v-68225145] .cm-editor {
|
|
278
278
|
min-width: 280px;
|
|
279
279
|
height: 100%;
|
|
280
280
|
}
|
|
@@ -1012,6 +1012,9 @@
|
|
|
1012
1012
|
.mb-8 {
|
|
1013
1013
|
margin-bottom: calc(var(--spacing) * 8);
|
|
1014
1014
|
}
|
|
1015
|
+
.mb-\[10px\] {
|
|
1016
|
+
margin-bottom: 10px;
|
|
1017
|
+
}
|
|
1015
1018
|
.ml-4 {
|
|
1016
1019
|
margin-left: calc(var(--spacing) * 4);
|
|
1017
1020
|
}
|
|
@@ -1670,6 +1673,9 @@
|
|
|
1670
1673
|
.stroke-2 {
|
|
1671
1674
|
stroke-width: 2;
|
|
1672
1675
|
}
|
|
1676
|
+
.p-0 {
|
|
1677
|
+
padding: 0px;
|
|
1678
|
+
}
|
|
1673
1679
|
.p-1 {
|
|
1674
1680
|
padding: var(--spacing);
|
|
1675
1681
|
}
|
|
@@ -1927,6 +1933,16 @@
|
|
|
1927
1933
|
outline-style: var(--tw-outline-style);
|
|
1928
1934
|
outline-width: 1px;
|
|
1929
1935
|
}
|
|
1936
|
+
.outline-2 {
|
|
1937
|
+
outline-style: var(--tw-outline-style);
|
|
1938
|
+
outline-width: 2px;
|
|
1939
|
+
}
|
|
1940
|
+
.-outline-offset-2 {
|
|
1941
|
+
outline-offset: calc(2px * -1);
|
|
1942
|
+
}
|
|
1943
|
+
.outline-primary-500 {
|
|
1944
|
+
outline-color: var(--color-primary-500);
|
|
1945
|
+
}
|
|
1930
1946
|
.blur {
|
|
1931
1947
|
--tw-blur: blur(8px);
|
|
1932
1948
|
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
|
@@ -2022,6 +2038,9 @@
|
|
|
2022
2038
|
.group-focus-within\:visible:is(:where(.group):focus-within *) {
|
|
2023
2039
|
visibility: visible;
|
|
2024
2040
|
}
|
|
2041
|
+
.group-focus-within\:flex:is(:where(.group):focus-within *) {
|
|
2042
|
+
display: flex;
|
|
2043
|
+
}
|
|
2025
2044
|
@media (hover: hover) {
|
|
2026
2045
|
.group-hover\:flex:is(:where(.group):hover *) {
|
|
2027
2046
|
display: flex;
|
|
@@ -2166,6 +2185,9 @@
|
|
|
2166
2185
|
.focus-visible\:border-primary-500:focus-visible {
|
|
2167
2186
|
border-color: var(--color-primary-500);
|
|
2168
2187
|
}
|
|
2188
|
+
.focus-visible\:opacity-100:focus-visible {
|
|
2189
|
+
opacity: 100%;
|
|
2190
|
+
}
|
|
2169
2191
|
.active\:bg-gray-600\/50:active {
|
|
2170
2192
|
background-color: color-mix(in srgb, rgb(var(--_poveste-color-gray-600)) 50%, transparent);
|
|
2171
2193
|
@supports (color: color-mix(in lab, red, red)) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poveste/app",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.15.0",
|
|
5
5
|
"description": "Pre-bundled UI for Poveste",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Sorin Gitlan",
|
|
@@ -23,13 +23,9 @@
|
|
|
23
23
|
"access": "public"
|
|
24
24
|
},
|
|
25
25
|
"exports": {
|
|
26
|
-
".":
|
|
27
|
-
"types": "./dist/index.d.ts",
|
|
28
|
-
"default": "./dist/bundle-index.js"
|
|
29
|
-
},
|
|
26
|
+
".": "./dist/bundle-index.js",
|
|
30
27
|
"./*": "./*"
|
|
31
28
|
},
|
|
32
|
-
"types": "./src/index.ts",
|
|
33
29
|
"files": [
|
|
34
30
|
"dist",
|
|
35
31
|
"src"
|
|
@@ -38,11 +34,11 @@
|
|
|
38
34
|
"node": "^22.22.2 || ^24.15.0 || >=26.0.0"
|
|
39
35
|
},
|
|
40
36
|
"dependencies": {
|
|
41
|
-
"@poveste/controls": "^0.
|
|
42
|
-
"@poveste/shared": "^0.
|
|
43
|
-
"@poveste/vendors": "^0.
|
|
37
|
+
"@poveste/controls": "^0.15.0",
|
|
38
|
+
"@poveste/shared": "^0.15.0",
|
|
39
|
+
"@poveste/vendors": "^0.15.0",
|
|
44
40
|
"fuse.js": "^7.0.0",
|
|
45
|
-
"shiki": "^
|
|
41
|
+
"shiki": "^4.4.3"
|
|
46
42
|
},
|
|
47
43
|
"devDependencies": {
|
|
48
44
|
"@fontsource/noto-sans-display": "^5.3.0",
|
|
@@ -56,19 +52,19 @@
|
|
|
56
52
|
"@types/fs-extra": "^11.0.4",
|
|
57
53
|
"@types/node": "^22.10.1",
|
|
58
54
|
"@vitejs/plugin-vue": "^6.0.0",
|
|
59
|
-
"chokidar": "^
|
|
60
|
-
"concurrently": "^
|
|
55
|
+
"chokidar": "^5.0.0",
|
|
56
|
+
"concurrently": "^10.0.5",
|
|
61
57
|
"fs-extra": "^11.2.0",
|
|
62
|
-
"globby": "^
|
|
63
|
-
"pathe": "^
|
|
58
|
+
"globby": "^16.2.4",
|
|
59
|
+
"pathe": "^2.0.3",
|
|
64
60
|
"postcss": "^8.5.23",
|
|
65
|
-
"postcss-cli": "^
|
|
61
|
+
"postcss-cli": "^12.0.0",
|
|
66
62
|
"tailwindcss": "^4.3.3",
|
|
67
|
-
"typescript": "
|
|
63
|
+
"typescript": "6.0.3",
|
|
68
64
|
"vite": "^8.2.0",
|
|
69
|
-
"vitest": "^
|
|
65
|
+
"vitest": "^5.0.0",
|
|
70
66
|
"vue": "^3.5.26",
|
|
71
|
-
"vue-tsc": "^
|
|
67
|
+
"vue-tsc": "^3.3.11"
|
|
72
68
|
},
|
|
73
69
|
"scripts": {
|
|
74
70
|
"build": "pnpm run icons && rimraf dist && vite build && pnpm run build:css && node ./scripts/copy.mjs",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { createRenderTracker } from '../app/util/render-tracker.js'
|
|
3
|
+
|
|
4
|
+
describe('createRenderTracker', () => {
|
|
5
|
+
it('calls a render clean when nothing was reported during it', () => {
|
|
6
|
+
const render = createRenderTracker()
|
|
7
|
+
|
|
8
|
+
render.begin()
|
|
9
|
+
|
|
10
|
+
expect(render.wasClean()).toBe(true)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('does not call a render clean when a throw was reported during it', () => {
|
|
14
|
+
const render = createRenderTracker()
|
|
15
|
+
render.begin()
|
|
16
|
+
|
|
17
|
+
render.threw()
|
|
18
|
+
|
|
19
|
+
expect(render.wasClean()).toBe(false)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// The half #597 is about. Without it a story that threw once stays marked for
|
|
23
|
+
// the life of the tab: every later render inherits the first one's verdict,
|
|
24
|
+
// so the marker the old comment promised to lift on a fix never lifts.
|
|
25
|
+
it('forgets the previous render when a new one begins', () => {
|
|
26
|
+
const render = createRenderTracker()
|
|
27
|
+
render.begin()
|
|
28
|
+
render.threw()
|
|
29
|
+
|
|
30
|
+
render.begin()
|
|
31
|
+
|
|
32
|
+
expect(render.wasClean()).toBe(true)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('treats a render that has reported nothing yet as clean', () => {
|
|
36
|
+
expect(createRenderTracker().wasClean()).toBe(true)
|
|
37
|
+
})
|
|
38
|
+
})
|
package/src/app/App.vue
CHANGED
|
@@ -5,7 +5,7 @@ export default {
|
|
|
5
5
|
</script>
|
|
6
6
|
|
|
7
7
|
<script lang="ts" setup>
|
|
8
|
-
import type { StoryFile, Tree } from './types'
|
|
8
|
+
import type { Story, StoryFile, Tree } from './types'
|
|
9
9
|
import { useTitle } from '@vueuse/core'
|
|
10
10
|
import { onUpdate, files as rawFiles, tree as rawTree } from 'virtual:$poveste-stories'
|
|
11
11
|
import { computed, onMounted, ref, watch } from 'vue'
|
|
@@ -29,7 +29,7 @@ import { onKeyboardShortcut } from './util/keyboard'
|
|
|
29
29
|
import { mapFile } from './util/mapping'
|
|
30
30
|
import { isMobile } from './util/responsive'
|
|
31
31
|
|
|
32
|
-
const files = ref<StoryFile[]>(rawFiles.map(file => mapFile(file)))
|
|
32
|
+
const files = ref<StoryFile[]>(rawFiles.map((file: StoryFile) => mapFile(file)))
|
|
33
33
|
const tree = ref<Tree>(rawTree)
|
|
34
34
|
|
|
35
35
|
onUpdate((newFiles: StoryFile[], newTree: Tree) => {
|
|
@@ -41,8 +41,11 @@ onUpdate((newFiles: StoryFile[], newTree: Tree) => {
|
|
|
41
41
|
tree.value = newTree
|
|
42
42
|
})
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
// The accumulator needs a type: `[]` alone infers `never[]`, so the push was an
|
|
45
|
+
// error about the seed rather than about the story. A file whose story has not
|
|
46
|
+
// been collected yet contributes nothing.
|
|
47
|
+
const stories = computed(() => files.value.reduce<Story[]>((acc, file) => {
|
|
48
|
+
if (file.story) acc.push(file.story)
|
|
46
49
|
return acc
|
|
47
50
|
}, []))
|
|
48
51
|
|
|
@@ -5,7 +5,7 @@ import { useStoryStore } from '../stores/story'
|
|
|
5
5
|
import { customLogos, povesteConfig } from '../util/config'
|
|
6
6
|
import HomeCounter from './app/HomeCounter.vue'
|
|
7
7
|
|
|
8
|
-
const logoUrl = computed(() => povesteConfig.theme?.logo?.square ? customLogos
|
|
8
|
+
const logoUrl = computed(() => povesteConfig.theme?.logo?.square ? customLogos?.square ?? PovesteLogo : PovesteLogo)
|
|
9
9
|
const storyStore = useStoryStore()
|
|
10
10
|
|
|
11
11
|
const stats = computed(() => {
|
|
@@ -6,11 +6,15 @@ import PovesteLogoLight from '../../assets/poveste-text.svg'
|
|
|
6
6
|
import { customLogos, povesteConfig } from '../../util/config'
|
|
7
7
|
import { isDark } from '../../util/dark'
|
|
8
8
|
|
|
9
|
+
// `theme.logo` is what the config asked for; `customLogos` is the resolved URL
|
|
10
|
+
// map from the virtual module. Two objects, so a check on one narrows nothing
|
|
11
|
+
// about the other — and falling back to the bundled logo beats rendering an
|
|
12
|
+
// `<img>` with no `src` if they ever disagree.
|
|
9
13
|
const logoUrl = computed(() => {
|
|
10
14
|
if (isDark.value) {
|
|
11
|
-
return povesteConfig.theme.logo?.dark ? customLogos
|
|
15
|
+
return povesteConfig.theme.logo?.dark ? customLogos?.dark ?? PovesteLogoDark : PovesteLogoDark
|
|
12
16
|
}
|
|
13
|
-
return povesteConfig.theme.logo?.light ? customLogos
|
|
17
|
+
return povesteConfig.theme.logo?.light ? customLogos?.light ?? PovesteLogoLight : PovesteLogoLight
|
|
14
18
|
})
|
|
15
19
|
|
|
16
20
|
const altText = computed(() => `${povesteConfig.theme.title} logo`)
|
|
@@ -16,7 +16,8 @@ const storyStore = useStoryStore()
|
|
|
16
16
|
const story = computed(() => storyStore.currentStory)
|
|
17
17
|
|
|
18
18
|
const folders = computed(() => {
|
|
19
|
-
|
|
19
|
+
// No story, or a story with no file behind it, means no trail to draw.
|
|
20
|
+
return story.value?.file?.path.slice(0, -1) ?? []
|
|
20
21
|
})
|
|
21
22
|
|
|
22
23
|
const isMenuOpened = ref(false)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
+
import { computed } from 'vue'
|
|
2
3
|
import { useStoryStore } from '../../stores/story'
|
|
3
4
|
import DevOnlyToolbarOpenInEditor from '../toolbar/DevOnlyToolbarOpenInEditor.vue'
|
|
4
5
|
import ToolbarBackground from '../toolbar/ToolbarBackground.vue'
|
|
@@ -15,6 +16,10 @@ defineEmits({
|
|
|
15
16
|
})
|
|
16
17
|
|
|
17
18
|
const storyStore = useStoryStore()
|
|
19
|
+
|
|
20
|
+
// Named once so the `v-if` and the binding below read the same value; a story
|
|
21
|
+
// without a file has nothing to open.
|
|
22
|
+
const editableFile = computed(() => storyStore.currentStory?.file?.filePath)
|
|
18
23
|
</script>
|
|
19
24
|
|
|
20
25
|
<template>
|
|
@@ -51,8 +56,8 @@ const storyStore = useStoryStore()
|
|
|
51
56
|
:story="storyStore.currentStory"
|
|
52
57
|
/>
|
|
53
58
|
<DevOnlyToolbarOpenInEditor
|
|
54
|
-
v-if="__POVESTE_DEV__"
|
|
55
|
-
:file="
|
|
59
|
+
v-if="__POVESTE_DEV__ && editableFile"
|
|
60
|
+
:file="editableFile"
|
|
56
61
|
tooltip="Edit story in editor"
|
|
57
62
|
/>
|
|
58
63
|
</TopBarChip>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<!-- Without an href this is not a link, so `aria-label` on it is prohibited and
|
|
3
3
|
it has no accessible role. Chip items are used both ways (#310). -->
|
|
4
4
|
<a
|
|
5
|
-
:role="$attrs
|
|
5
|
+
:role="$attrs['href'] ? undefined : 'button'"
|
|
6
6
|
class="poveste-top-bar-chip-item flex items-center gap-1 px-2.5 py-1.5 hover:bg-white/50 active:bg-white/70 dark:hover:bg-white/10 dark:active:bg-white/15 hover:text-primary-500 dark:hover:text-primary-400 cursor-pointer text-gray-900 dark:text-gray-100 transition-colors"
|
|
7
7
|
>
|
|
8
8
|
<slot />
|
|
@@ -28,7 +28,7 @@ const progress = computed(() => props.modelValue ? 1 : 0)
|
|
|
28
28
|
const dashoffset = computed(() => (1 - progress.value) * dasharray.value)
|
|
29
29
|
|
|
30
30
|
watch(path, () => {
|
|
31
|
-
dasharray.value = path.value
|
|
31
|
+
dasharray.value = path.value?.getTotalLength?.() ?? 21.21
|
|
32
32
|
})
|
|
33
33
|
</script>
|
|
34
34
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="poveste-base-empty base-empty flex flex-col items-center justify-center space-y-4 py-12 h-full text-center text-gray-400
|
|
2
|
+
<div class="poveste-base-empty base-empty flex flex-col items-center justify-center space-y-4 py-12 h-full text-center text-gray-500 dark:text-gray-400 text-lg">
|
|
3
3
|
<slot />
|
|
4
4
|
</div>
|
|
5
5
|
</template>
|
|
@@ -26,9 +26,11 @@ export default defineComponent({
|
|
|
26
26
|
},
|
|
27
27
|
|
|
28
28
|
setup(props, { emit }) {
|
|
29
|
-
function handleNavigate(event, navigate: (event) => unknown) {
|
|
29
|
+
function handleNavigate(event: MouseEvent | KeyboardEvent, navigate: (event?: MouseEvent) => unknown) {
|
|
30
30
|
emit('navigate')
|
|
31
|
-
|
|
31
|
+
// vue-router types this as a click, but its guard reads only the modifier
|
|
32
|
+
// keys and `defaultPrevented`, which a key press carries too.
|
|
33
|
+
navigate(event as MouseEvent)
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
return {
|