@storystick/react 0.2.0-rc.0 → 0.2.0-rc.1

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/PUBLIC-API.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @storystick/react API contract
2
2
 
3
- Install this candidate explicitly with `@storystick/react@0.2.0-rc.0` after registry bootstrap and publication. This repository alone does not make the candidate or beta channel available on npm.
3
+ Install this candidate explicitly with `@storystick/react@0.2.0-rc.1` after registry bootstrap and publication. This repository alone does not make the candidate or beta channel available on npm.
4
4
 
5
- Version 0.2.0-rc.0, cohort `sdk-0.2.0-rc.0`. ESM distribution; no CommonJS or universal Edge compatibility is promised.
5
+ Version 0.2.0-rc.1, cohort `sdk-0.2.0-rc.1`. ESM distribution; no CommonJS or universal Edge compatibility is promised.
6
6
 
7
7
  This package is a product integration facade. Reserved contracts are required between SDK packages and must be upgraded with the same cohort. Their public export paths do not promise independent stability.
8
8
 
@@ -26,6 +26,8 @@ This package is a product integration facade. Reserved contracts are required be
26
26
  | `./internal/use-momentary-preview-navigate-mode` | Reserved SDK contract |
27
27
  | `./internal/resize-edge` | Reserved SDK contract |
28
28
  | `./internal/preview-toolbar-shell` | Reserved SDK contract |
29
+ | `./internal/preview-page-state` | Reserved SDK contract |
30
+ | `./internal/authoring-skeleton` | Reserved SDK contract |
29
31
  | `./internal/preview-panel-state` | Reserved SDK contract |
30
32
  | `./internal/preview-host` | Reserved SDK contract |
31
33
 
package/dist/.source-hash CHANGED
@@ -1 +1 @@
1
- 937895c9b9d8b1e9dd734b85c5339eee7d93c3afe545ac5d65f65f5330fb3acb
1
+ 4cb09a292c5e314adfbbfa1a1bdf1cad0ae376072cfd5a37269e4be8e69570da
@@ -0,0 +1,5 @@
1
+ /** The same outline covers admission and module loading, before either app's
2
+ * styles/providers exist. Only the owned panel receives these styles. */
3
+ export declare function AuthoringPanelSkeleton({ surface }: {
4
+ surface: 'editor' | 'assistant';
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /** The same outline covers admission and module loading, before either app's
3
+ * styles/providers exist. Only the owned panel receives these styles. */
4
+ export function AuthoringPanelSkeleton({ surface }) {
5
+ return _jsxs("div", { "data-ss-panel-skeleton": surface, role: "status", "aria-label": surface === 'assistant' ? 'Loading assistant' : 'Loading document content', children: [_jsx("style", { children: STYLES }), _jsx("div", { "data-ss-panel-skeleton-body": "", "aria-hidden": "true", children: surface === 'assistant' ? _jsxs(_Fragment, { children: [_jsxs("div", { "data-ss-panel-skeleton-greeting": "", children: [_jsx("i", {}), _jsx("i", {})] }), _jsxs("div", { "data-ss-panel-skeleton-composer": "", children: [_jsx("i", {}), _jsx("i", {})] })] }) : _jsxs(_Fragment, { children: [_jsxs("div", { "data-ss-panel-skeleton-title": "", children: [_jsx("i", {}), _jsx("i", {})] }), _jsxs("div", { "data-ss-panel-skeleton-tabs": "", children: [_jsx("i", {}), _jsx("i", {}), _jsx("i", {})] }), _jsxs("div", { "data-ss-panel-skeleton-fields": "", children: [_jsx("i", {}), _jsx("i", {}), _jsx("i", {})] })] }) })] });
6
+ }
7
+ const STYLES = `
8
+ [data-ss-panel-skeleton] { box-sizing: border-box; width: 100%; height: 100%; min-height: 240px; background: var(--ss-surface-page, #fafafa); }
9
+ [data-ss-panel-skeleton-body] { box-sizing: border-box; display: flex; flex-direction: column; height: 100%; min-height: inherit; gap: 32px; padding: 24px 16px 16px; }
10
+ [data-ss-panel-skeleton] i { display: block; flex-shrink: 0; background: var(--ss-surface-widget, #f0f0f0); border-radius: var(--ss-radius-field, 18px); }
11
+ [data-ss-panel-skeleton-title] { display: grid; gap: 12px; padding-top: 12px; }
12
+ [data-ss-panel-skeleton-title] i { height: 24px; width: 85%; }
13
+ [data-ss-panel-skeleton-title] i:last-child { width: 60%; }
14
+ [data-ss-panel-skeleton-tabs] { display: flex; gap: 12px; }
15
+ [data-ss-panel-skeleton-tabs] i { width: 64px; height: 32px; border-radius: 999px; }
16
+ [data-ss-panel-skeleton-fields] { display: grid; gap: 12px; }
17
+ [data-ss-panel-skeleton-fields] i { height: 88px; }
18
+ [data-ss-panel-skeleton-fields] i:last-child { height: 132px; }
19
+ [data-ss-panel-skeleton-greeting] { display: grid; gap: 12px; width: 75%; margin: auto; }
20
+ [data-ss-panel-skeleton-greeting] i { height: 16px; }
21
+ [data-ss-panel-skeleton-greeting] i:last-child { width: 70%; margin-inline: auto; }
22
+ [data-ss-panel-skeleton-composer] { display: flex; align-items: center; justify-content: space-between; height: 60px; flex-shrink: 0; padding: 0 16px; border-radius: 30px; background: var(--ss-surface-card, #fff); box-shadow: 0 0 0 1px var(--ss-border-card, #e9e9e9); }
23
+ [data-ss-panel-skeleton-composer] i { width: 55%; height: 12px; }
24
+ [data-ss-panel-skeleton-composer] i:last-child { width: 32px; height: 32px; border-radius: 50%; }
25
+ @media (prefers-reduced-motion: no-preference) {
26
+ [data-ss-panel-skeleton] i { animation: ss-panel-skeleton-pulse 1.8s ease-in-out infinite; }
27
+ }
28
+ @keyframes ss-panel-skeleton-pulse { 50% { opacity: .5; } }
29
+ `;
@@ -1,11 +1,11 @@
1
- import { type PreviewAuthoringAdmission, type PreviewAuthoringRuntimeModule } from '@storystick/preview/authoring/runtime-contract';
1
+ import { type PreviewAuthoringAdmission, type PreviewAuthoringRuntimeModule, type PreviewAuthoringSurface } from '@storystick/preview/authoring/runtime-contract';
2
2
  declare global {
3
3
  interface Window {
4
4
  StorystickAuthoringRuntime?: PreviewAuthoringRuntimeModule;
5
5
  }
6
6
  }
7
7
  export declare function authoringRuntimeUrl(studioUrl: string): string | null;
8
- export declare function loadPreviewAuthoringRuntime(source: string): Promise<PreviewAuthoringRuntimeModule>;
8
+ export declare function loadPreviewAuthoringRuntime(source: string, surface?: PreviewAuthoringSurface | null): Promise<PreviewAuthoringRuntimeModule>;
9
9
  export declare function requestPreviewAuthoringAdmission(args: {
10
10
  url: string;
11
11
  previous?: PreviewAuthoringAdmission | null;
@@ -13,7 +13,15 @@ export function authoringRuntimeUrl(studioUrl) {
13
13
  return null;
14
14
  }
15
15
  }
16
- export function loadPreviewAuthoringRuntime(source) {
16
+ export function loadPreviewAuthoringRuntime(source, surface) {
17
+ const pending = loadRuntimeModule(source);
18
+ // Code preparation never holds admission/authentication behind a panel's
19
+ // dependencies. Rendering uses the same imports and its own Suspense boundary.
20
+ if (surface)
21
+ void pending.then(runtime => runtime.preload(surface)).catch(() => undefined);
22
+ return pending;
23
+ }
24
+ function loadRuntimeModule(source) {
17
25
  const installed = readRuntime();
18
26
  if (installed)
19
27
  return Promise.resolve(installed);
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useMomentaryPreviewInteractionOverride } from '@storystick/react/internal/use-momentary-preview-navigate-mode';
4
4
  import { PreviewPanelAction, PreviewPanelState } from '@storystick/react/internal/preview-panel-state';
5
+ import { AuthoringPanelSkeleton } from '@storystick/react/internal/authoring-skeleton';
5
6
  import { useStandalonePresentation } from './use-standalone-presentation.js';
6
7
  import { PreviewInteractionControl } from '@storystick/react/internal/preview-interaction-control';
7
8
  import { PreviewToolbarShell, PreviewEditIcon as EditIcon, PreviewAssistantIcon as ChatIcon, PreviewStudioIcon as StudioIcon, } from '@storystick/react/internal/preview-toolbar-shell';
@@ -28,6 +29,7 @@ export function PreviewStandaloneControls({ state, projectId, recovery, basePath
28
29
  const host = useRef(null);
29
30
  const editorContainer = useRef(null);
30
31
  const [headerLeading, setHeaderLeading] = useState(null);
32
+ const [headerTitle, setHeaderTitle] = useState(null);
31
33
  const [headerTrailing, setHeaderTrailing] = useState(null);
32
34
  const [contextContainer, setContextContainer] = useState(null);
33
35
  const [interactionMode, setInteractionMode] = useState(() => readStandaloneInteractionMode(projectId, state.sessionId));
@@ -148,14 +150,18 @@ export function PreviewStandaloneControls({ state, projectId, recovery, basePath
148
150
  ? 'Element selection is unavailable because some elements do not match their editing context.'
149
151
  : 'Element selection is unavailable because this page has incomplete or conflicting editing information.'
150
152
  : null;
151
- const shouldPreloadEditor = canEdit && (interactionMode === 'edit' || menuOpen);
153
+ const shouldPreloadRuntime = canEdit && (interactionMode === 'edit' || menuOpen);
152
154
  useEffect(() => {
153
- if (!shouldPreloadEditor || !runtimeSource)
155
+ if (!shouldPreloadRuntime || !runtimeSource)
154
156
  return;
155
- // Warm code for either entry point (overlay or menu). Admission and mounting
156
- // still belong to explicit edit intent; the loader shares in-flight work.
157
+ // Warm the common runtime; panel dependencies follow a specific intent.
158
+ // Admission and mounting stay separate from this shared code preparation.
157
159
  void loadPreviewAuthoringRuntime(runtimeSource).catch(() => undefined);
158
- }, [shouldPreloadEditor, runtimeSource]);
160
+ }, [shouldPreloadRuntime, runtimeSource]);
161
+ const preloadPanel = useCallback((surface) => {
162
+ if (runtimeSource)
163
+ void loadPreviewAuthoringRuntime(runtimeSource, surface).catch(() => undefined);
164
+ }, [runtimeSource]);
159
165
  const currentPage = useRef(page);
160
166
  currentPage.current = page;
161
167
  const presentationRef = useRef(presentation);
@@ -271,7 +277,7 @@ export function PreviewStandaloneControls({ state, projectId, recovery, basePath
271
277
  (surface === 'editor' && !targetRequest) ||
272
278
  root === null ||
273
279
  editorContainer.current === null ||
274
- contextContainer === null || headerLeading === null || headerTrailing === null ||
280
+ contextContainer === null || headerLeading === null || headerTitle === null || headerTrailing === null ||
275
281
  runtimeSource === null ||
276
282
  runtimeHandle.current !== null)
277
283
  return;
@@ -285,7 +291,7 @@ export function PreviewStandaloneControls({ state, projectId, recovery, basePath
285
291
  setError(null);
286
292
  try {
287
293
  const [runtime, initialAdmission] = await Promise.all([
288
- loadPreviewAuthoringRuntime(runtimeSource),
294
+ loadPreviewAuthoringRuntime(runtimeSource, surface),
289
295
  requestPreviewAuthoringAdmission({ url: authoringUrl }),
290
296
  ]);
291
297
  if (cancelled)
@@ -306,9 +312,9 @@ export function PreviewStandaloneControls({ state, projectId, recovery, basePath
306
312
  const handle = runtime.mount({
307
313
  root,
308
314
  container,
309
- header: { leading: headerLeading, trailing: headerTrailing },
315
+ header: { leading: headerLeading, title: headerTitle, trailing: headerTrailing },
310
316
  target: targetRef.current ?? undefined,
311
- surface: surface === 'assistant' ? 'assistant' : 'editor',
317
+ surface,
312
318
  assistantThreadId: assistantThreadId.current,
313
319
  onSurface: next => { if (runtimeOwner.current === owner) {
314
320
  setMenuOpen(false);
@@ -423,7 +429,7 @@ export function PreviewStandaloneControls({ state, projectId, recovery, basePath
423
429
  return () => {
424
430
  cancelled = true;
425
431
  };
426
- }, [authoringUrl, connectAttempt, targetRequest, root, contextContainer, headerLeading, headerTrailing, runtimeSource, surface, wantsRuntime, connected]);
432
+ }, [authoringUrl, connectAttempt, targetRequest, root, contextContainer, headerLeading, headerTitle, headerTrailing, runtimeSource, surface, wantsRuntime, connected]);
427
433
  useEffect(() => {
428
434
  if (!targetRequest || !runtimeHandle.current)
429
435
  return;
@@ -466,12 +472,16 @@ export function PreviewStandaloneControls({ state, projectId, recovery, basePath
466
472
  dispose();
467
473
  };
468
474
  }, []);
469
- return _jsx(PreviewToolbarShell, { hostRef: host, onRoot: setRoot, menuOpen: menuOpen, onMenuOpenChange: setMenuOpen, panelOpen: surface !== null, onPanelClose: closePanel, panelLeading: _jsx("div", { ref: setHeaderLeading, "data-ss-surface": "" }), panelTrailing: _jsx("div", { ref: setHeaderTrailing, "data-ss-surface": "" }), panelLabel: surface === "assistant" ? "Assistant" : "Edit content", menu: _jsxs(_Fragment, { children: [_jsxs("div", { className: "menu-group", role: "group", "aria-label": "Site view", children: [_jsx("span", { className: "menu-group-label", children: "Site view" }), _jsxs("div", { className: "menu-group-content", children: [_jsx("div", { ref: setContextContainer, "data-ss-surface": "" }), editorStatus === 'connecting' ? _jsx("p", { className: "menu-status", role: "status", children: "Loading site context\u2026" }) : null] })] }), _jsx("div", { className: "menu-separator", "aria-hidden": "true" }), _jsxs("div", { className: "menu-group", role: "group", "aria-label": "Interaction", children: [_jsx("span", { className: "menu-group-label", children: "Interaction" }), _jsx("div", { className: "menu-group-content", children: _jsx(PreviewInteractionControl, { interactionMode: effectiveMode, onChange: chooseInteractionMode }) })] }), _jsx("div", { className: "menu-separator", "aria-hidden": "true" }), _jsxs("button", { type: "button", className: "menu-action", disabled: !canEdit, onClick: () => openEditor(currentPageTarget), children: [_jsx(EditIcon, {}), _jsx("span", { children: "Edit content" })] }), _jsxs("button", { type: "button", className: "menu-action", disabled: !connected || !runtimeSource, onClick: () => { setError(null); setMenuOpen(false); setSurface('assistant'); }, children: [_jsx(ChatIcon, {}), _jsx("span", { children: "Assistant" })] }), destination && (_jsxs("a", { className: "menu-action", href: destination.href, children: [_jsx(StudioIcon, {}), _jsx("span", { children: "Open in Studio" })] })), (connectionNotice || notice ||
475
+ return _jsx(PreviewToolbarShell, { hostRef: host, onRoot: setRoot, menuOpen: menuOpen, onMenuOpenChange: setMenuOpen, panelOpen: surface !== null, onPanelClose: closePanel, panelTitle: _jsx("span", { ref: setHeaderTitle, className: "panel-title", "data-placeholder": surface === "assistant" ? "Assistant" : "Edit content" }), panelLeading: _jsx("div", { ref: setHeaderLeading, "data-ss-surface": "" }), panelTrailing: _jsx("div", { ref: setHeaderTrailing, "data-ss-surface": "" }), panelLabel: surface === "assistant" ? "Assistant" : "Edit content", menu: _jsxs(_Fragment, { children: [_jsxs("div", { className: "menu-group", role: "group", "aria-label": "Site view", children: [_jsx("span", { className: "menu-group-label", children: "Site view" }), _jsxs("div", { className: "menu-group-content", children: [_jsx("div", { ref: setContextContainer, "data-ss-surface": "" }), editorStatus === 'connecting' ? _jsxs("div", { className: "menu-context-loading", role: "status", "aria-label": "Loading site view", children: [_jsx("span", {}), _jsx("span", {})] }) : null] })] }), _jsx("div", { className: "menu-separator", "aria-hidden": "true" }), _jsxs("div", { className: "menu-group", role: "group", "aria-label": "Interaction", children: [_jsx("span", { className: "menu-group-label", children: "Interaction" }), _jsx("div", { className: "menu-group-content", children: _jsx(PreviewInteractionControl, { interactionMode: effectiveMode, onChange: chooseInteractionMode }) })] }), _jsx("div", { className: "menu-separator", "aria-hidden": "true" }), _jsxs("button", { type: "button", className: "menu-action", disabled: !canEdit, onMouseEnter: () => { if (canEdit)
476
+ preloadPanel('editor'); }, onFocus: () => { if (canEdit)
477
+ preloadPanel('editor'); }, onClick: () => openEditor(currentPageTarget), children: [_jsx(EditIcon, {}), _jsx("span", { children: "Edit content" })] }), _jsxs("button", { type: "button", className: "menu-action", disabled: !connected || !runtimeSource, onMouseEnter: () => { if (connected)
478
+ preloadPanel('assistant'); }, onFocus: () => { if (connected)
479
+ preloadPanel('assistant'); }, onClick: () => { setError(null); setMenuOpen(false); setSurface('assistant'); }, children: [_jsx(ChatIcon, {}), _jsx("span", { children: "Assistant" })] }), destination && (_jsxs("a", { className: "menu-action", href: destination.href, children: [_jsx(StudioIcon, {}), _jsx("span", { children: "Open in Studio" })] })), (connectionNotice || notice ||
470
480
  overlayNotice ||
471
481
  (!rendered && connected) ||
472
482
  recovery?.error ||
473
483
  error) && (_jsxs("div", { className: "notices", children: [connectionNotice && _jsx("p", { role: "status", children: connectionNotice }), notice && _jsx("p", { children: notice }), overlayNotice && _jsx("p", { role: "status", children: overlayNotice }), connected && !rendered && (_jsxs("p", { children: ["This page has not confirmed its rendered perspective.", ' ', _jsx("button", { type: "button", onClick: () => window.location.reload(), children: "Reload" })] })), recovery?.error && (_jsxs("p", { role: "alert", children: [recovery.error, ' ', _jsx("button", { type: "button", onClick: recovery.retry, disabled: false, children: "Retry" })] })), error && _jsxs("p", { role: "alert", children: [error, ' ', editorStatus === 'error' && !runtimeHandle.current ? (_jsx("button", { type: "button", onClick: () => setConnectAttempt(value => value + 1), children: "Retry" })) : null] })] }))] }), panel: _jsxs(_Fragment, { children: [surface === 'editor' && target === null && committedTarget === null && !continuation && (_jsxs("div", { className: "editor-empty", children: [_jsx(EditIcon, {}), _jsx("strong", { children: "Select an element on the page" }), _jsx("p", { children: "The matching field will open here." })] })), (editorStatus === 'idle' || editorStatus === 'connecting') &&
474
- (surface === 'assistant' || target || continuation) && (_jsx("div", { className: "editor-loading", role: "status", "aria-label": surface === 'assistant' ? 'Loading assistant' : 'Loading document content', children: _jsxs("div", { "aria-hidden": "true", children: [_jsx("div", {}), _jsx("div", {}), _jsx("div", {})] }) })), editorStatus === 'error' && error && (_jsx("div", { className: "panel-state-overlay", children: _jsx(PreviewPanelState, { title: surface === 'assistant' ? 'Assistant unavailable' : 'Editor unavailable', description: error, actions: _jsx(PreviewPanelAction, { onClick: () => {
484
+ (surface === 'assistant' || target || continuation) && (_jsx("div", { className: "editor-loading", children: _jsx(AuthoringPanelSkeleton, { surface: surface === 'assistant' ? 'assistant' : 'editor' }) })), editorStatus === 'error' && error && (_jsx("div", { className: "panel-state-overlay", children: _jsx(PreviewPanelState, { title: surface === 'assistant' ? 'Assistant unavailable' : 'Editor unavailable', description: error, actions: _jsx(PreviewPanelAction, { onClick: () => {
475
485
  setError(null);
476
486
  if (runtimeHandle.current && (committedTarget ?? target))
477
487
  requestEditorTarget({ ...(committedTarget ?? target) });
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from 'react';
2
+ /** One page composition for launch, activation and terminal Preview feedback.
3
+ * Actions and progress belong to the caller's existing lifecycle. */
4
+ export declare function PreviewPageState({ title, description, role, indicator, actions, children }: {
5
+ title: string;
6
+ description: ReactNode;
7
+ role: 'status' | 'alert';
8
+ indicator?: ReactNode;
9
+ actions?: ReactNode;
10
+ children?: ReactNode;
11
+ }): import("react").JSX.Element;
12
+ export declare function PreviewPageProgress(): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { PREVIEW_PAGE_STYLES } from '@storystick/preview/page-presentation';
3
+ import { StorystickMark } from './storystick-mark.js';
4
+ /** One page composition for launch, activation and terminal Preview feedback.
5
+ * Actions and progress belong to the caller's existing lifecycle. */
6
+ export function PreviewPageState({ title, description, role, indicator, actions, children }) {
7
+ return _jsxs(_Fragment, { children: [_jsx("style", { children: PREVIEW_PAGE_STYLES }), _jsx("main", { "data-ss-preview-page": "", children: _jsxs("div", { "data-ss-preview-page-content": "", children: [_jsxs("div", { "data-ss-preview-page-brand": "", children: [_jsx(StorystickMark, {}), _jsx("span", { children: "Storystick" })] }), _jsxs("div", { role: role, children: [_jsxs("div", { "data-ss-preview-page-heading": "", children: [indicator, _jsx("h1", { "data-ss-preview-page-title": "", children: title })] }), _jsx("p", { "data-ss-preview-page-description": "", children: description })] }), actions ? _jsx("div", { "data-ss-preview-page-actions": "", children: actions }) : null, children] }) })] });
8
+ }
9
+ export function PreviewPageProgress() {
10
+ return _jsx("span", { "data-ss-preview-page-progress": "", "aria-hidden": "true" });
11
+ }
@@ -12,7 +12,7 @@ export function PreviewPanelAction(props) {
12
12
  return _jsx("button", { type: "button", "data-ss-panel-action": "", ...props });
13
13
  }
14
14
  const MESSAGE_STYLES = `
15
- [data-ss-panel-state] { box-sizing: border-box; display: flex; flex-direction: column; width: 100%; height: 100%; min-height: 0; min-width: 0; overflow: auto; padding: 24px; background: #fff; }
15
+ [data-ss-panel-state] { box-sizing: border-box; display: flex; flex-direction: column; width: 100%; height: 100%; min-height: 0; min-width: 0; overflow: auto; padding: 24px; background: var(--ss-surface-page, #fff); }
16
16
  [data-ss-panel-state] > [data-ss-panel-message] { margin: auto; padding: 0; }
17
17
  [data-ss-panel-message] { box-sizing: border-box; flex: none; width: 100%; max-width: 360px; margin-inline: auto; padding: 16px; color: #52525b; font: 400 13px/1.5 "Storystick Inter", "Inter Variable", ui-sans-serif, system-ui, sans-serif; text-align: center; overflow-wrap: anywhere; }
18
18
  [data-ss-panel-message-copy] h2 { margin: 0; color: #18181b; font-size: 15px; font-weight: 500; line-height: 1.4; text-wrap: balance; }
@@ -1,6 +1,6 @@
1
1
  import type { ComponentProps, ReactNode, RefObject } from 'react';
2
2
  /** The same movable frame hosts the retained editor and assistant. */
3
- export declare function PreviewToolbarPanel({ open, label, onClose, panelRef, handleProps, resizeHandles, leading, trailing, children, }: {
3
+ export declare function PreviewToolbarPanel({ open, label, onClose, panelRef, handleProps, resizeHandles, leading, trailing, title, children, }: {
4
4
  open: boolean;
5
5
  label: string;
6
6
  onClose(): void;
@@ -9,5 +9,6 @@ export declare function PreviewToolbarPanel({ open, label, onClose, panelRef, ha
9
9
  resizeHandles: ReactNode;
10
10
  leading?: ReactNode;
11
11
  trailing?: ReactNode;
12
+ title?: ReactNode;
12
13
  children: ReactNode;
13
14
  }): import("react").JSX.Element;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { X } from './preview-icons.js';
3
3
  /** The same movable frame hosts the retained editor and assistant. */
4
- export function PreviewToolbarPanel({ open, label, onClose, panelRef, handleProps, resizeHandles, leading, trailing, children, }) {
5
- return (_jsxs("section", { ref: panelRef, className: "editor-panel", "aria-label": label, hidden: !open, inert: !open, children: [_jsxs("div", { className: "panel-contents", children: [_jsxs("header", { className: "panel-header", children: [leading, _jsx("button", { type: "button", className: "panel-drag-handle", "aria-label": `Move ${label} panel`, title: "Drag to move. Arrow keys move; Alt + Up/Down fills the height; Home and End dock to a side.", ...handleProps, children: label }), trailing, _jsx("button", { type: "button", className: "panel-close", "aria-label": `Close ${label}`, onClick: onClose, children: _jsx(X, { size: 16, strokeWidth: 1.25, absoluteStrokeWidth: true, "aria-hidden": "true" }) })] }), _jsx("div", { className: "panel-body", children: children })] }), resizeHandles] }));
4
+ export function PreviewToolbarPanel({ open, label, onClose, panelRef, handleProps, resizeHandles, leading, trailing, title, children, }) {
5
+ return (_jsxs("section", { ref: panelRef, className: "editor-panel", "aria-label": label, hidden: !open, inert: !open, children: [_jsxs("div", { className: "panel-contents", children: [_jsxs("header", { className: "panel-header", children: [leading, _jsx("button", { type: "button", className: "panel-drag-handle", "aria-label": `Move ${label} panel`, title: "Drag to move. Arrow keys move; Alt + Up/Down fills the height; Home and End dock to a side.", ...handleProps, children: title ?? label }), trailing, _jsx("button", { type: "button", className: "panel-close", "aria-label": `Close ${label}`, onClick: onClose, children: _jsx(X, { size: 16, strokeWidth: 1.25, absoluteStrokeWidth: true, "aria-hidden": "true" }) })] }), _jsx("div", { className: "panel-body", children: children })] }), resizeHandles] }));
6
6
  }
@@ -6,6 +6,7 @@ export declare function PreviewToolbarShell(props: {
6
6
  menu: ReactNode;
7
7
  panel: ReactNode;
8
8
  panelLabel: string;
9
+ panelTitle?: ReactNode;
9
10
  panelLeading?: ReactNode;
10
11
  panelTrailing?: ReactNode;
11
12
  onMenuOpenChange(open: boolean): void;
@@ -23,7 +23,7 @@ export function PreviewToolbarShell(props) {
23
23
  }, [host, props.onRoot]);
24
24
  return _jsx("div", { ref: host, "data-storystick-preview-controls": "", style: { all: "initial", position: "fixed", zIndex: 2147483647, top: 0, left: 0, width: 0, height: 0 }, children: root ? createPortal(_jsx(ToolbarContents, { ...props, root: root }), root) : null });
25
25
  }
26
- function ToolbarContents({ root, menuOpen, panelOpen, menu, panel, panelLabel, panelLeading, panelTrailing, onMenuOpenChange, onPanelClose }) {
26
+ function ToolbarContents({ root, menuOpen, panelOpen, menu, panel, panelLabel, panelTitle, panelLeading, panelTrailing, onMenuOpenChange, onPanelClose }) {
27
27
  const layout = usePreviewToolbarLayout(() => onMenuOpenChange(false));
28
28
  const menuRef = useRef(null);
29
29
  const [shell, setShell] = useState(null);
@@ -44,7 +44,7 @@ function ToolbarContents({ root, menuOpen, panelOpen, menu, panel, panelLabel, p
44
44
  onPanelClose();
45
45
  layout.bubble.current?.focus({ preventScroll: true });
46
46
  }
47
- return _jsxs(_Fragment, { children: [_jsx("style", { children: CONTROL_STYLES }), _jsxs("div", { ref: setShell, className: "storystick-shell", "data-ss-surface": "", dir: "ltr", children: [_jsx(PreviewToolbarPanel, { open: panelOpen, label: panelLabel, onClose: closePanel, leading: panelLeading, trailing: panelTrailing, panelRef: layout.panel, handleProps: { ...layout.panelDrag, onKeyDown: layout.onPanelKeyDown }, resizeHandles: _jsx(PreviewToolbarResizeHandles, { label: panelLabel, edges: layout.resizeEdges, size: layout.panelBounds, handleProps: layout.resizeHandleProps }), children: panel }), _jsxs(Popover.Root, { open: menuOpen, modal: false, onOpenChange: (open, details) => {
47
+ return _jsxs(_Fragment, { children: [_jsx("style", { children: CONTROL_STYLES }), _jsxs("div", { ref: setShell, className: "storystick-shell", "data-ss-surface": "", dir: "ltr", children: [_jsx(PreviewToolbarPanel, { open: panelOpen, label: panelLabel, onClose: closePanel, leading: panelLeading, title: panelTitle, trailing: panelTrailing, panelRef: layout.panel, handleProps: { ...layout.panelDrag, onKeyDown: layout.onPanelKeyDown }, resizeHandles: _jsx(PreviewToolbarResizeHandles, { label: panelLabel, edges: layout.resizeEdges, size: layout.panelBounds, handleProps: layout.resizeHandleProps }), children: panel }), _jsxs(Popover.Root, { open: menuOpen, modal: false, onOpenChange: (open, details) => {
48
48
  // Runtime menus live in a second React root, but remain part of this
49
49
  // dropdown interaction. Their owned portals must not dismiss its parent.
50
50
  const portals = root.querySelector('[data-storystick-authoring-portals]');
@@ -82,33 +82,42 @@ const CONTROL_STYLES = `
82
82
  .storystick-brand { width: 14px; height: 13px; transform: rotate(90deg); }
83
83
  .menu-positioner { z-index: 3; pointer-events: auto; }
84
84
  .menu { width: min(240px, calc(100vw - ${TOOLBAR_GAP * 2}px)); max-height: min(var(--available-height), calc(100dvh - ${TOOLBAR_GAP * 2}px)); overflow: auto; overscroll-behavior: contain; padding: 5px; }
85
- .bubble { box-shadow: var(--ss-shadow-floating, 0 0 1px rgb(0 0 0 / 0.34), 0 8px 20px -10px rgb(0 0 0 / 0.15), 0 2px 6px -4px rgb(0 0 0 / 0.1)); }
86
- .menu, .editor-panel { box-shadow: var(--ss-shadow-hub, 0 0 0 1px rgb(0 0 0 / 0.05), 0 1px 2px -1px rgb(0 0 0 / 0.05), 0 6px 16px -10px rgb(0 0 0 / 0.1), 0 16px 34px -24px rgb(0 0 0 / 0.12)); }
87
- .menu, .editor-panel { background: var(--ss-surface-card, #fff); border: 0; border-radius: var(--ss-radius-2xl, 19px); }
85
+ /* Keep the bubble's relief stable before and after the authoring styles load. */
86
+ .bubble { box-shadow: 0 0 1px rgb(0 0 0 / 0.34), 0 2px 6px -1px rgb(0 0 0 / 0.12), 0 8px 20px -6px rgb(0 0 0 / 0.18); }
87
+ .menu, .editor-panel { box-shadow: 0 0 0 1px rgb(0 0 0 / 0.07), 0 4px 12px -2px rgb(0 0 0 / 0.1), 0 16px 40px -10px rgb(0 0 0 / 0.2); border: 0; }
88
+ .menu { background: var(--ss-surface-card, #fff); border-radius: var(--ss-radius-2xl, 19px); }
88
89
  .menu-group { display: flex; flex-direction: column; gap: 2px; }
89
90
  .menu-group-label { display: flex; align-items: center; min-height: 25px; padding: 4px 10px; color: var(--ss-text-tertiary, oklch(0.515 0.006 258.364)); font-size: 13px; line-height: 17px; font-weight: 500; }
90
91
  .menu-group-content { padding: 0 10px 5px; }
91
- .menu-status { margin: 4px 0 0; color: var(--ss-text-tertiary, oklch(0.515 0.006 258.364)); font-size: 12px; line-height: 16px; }
92
+ .menu-context-loading { display: flex; gap: 8px; height: 32px; }
93
+ .menu-context-loading span { width: 88px; border-radius: 999px; background: var(--ss-surface-widget, #f0f0f0); }
94
+ .menu-context-loading span:last-child { width: 56px; }
92
95
  .menu-separator { margin: 5px 10px; border-top: 1px solid var(--ss-border-separator, oklch(0.958 0.001 255)); }
93
96
  .menu-action { width: 100%; min-height: 28px; display: flex; align-items: center; gap: 8px; padding: 5px 10px; color: inherit; text-decoration: none; text-align: left; font-size: 14px; line-height: 18px; font-weight: 400; background: transparent; border: 0; border-radius: var(--ss-radius-md, 7px); }
97
+ .menu-action + .menu-action { margin-block-start: 2px; }
94
98
  .menu-action:hover { background: var(--ss-surface-field, oklch(0.958 0.001 255)); }
95
99
  .menu-action:disabled { opacity: .45; cursor: default; }
96
100
  .menu-action:disabled:hover { background: transparent; }
97
101
  .notices { margin-top: 5px; padding: 5px 10px; color: var(--ss-text-tertiary, oklch(0.515 0.006 258.364)); border-top: 1px solid var(--ss-border-separator, oklch(0.958 0.001 255)); font-size: 12px; line-height: 16px; }
98
102
  .notices p { margin: 4px 0; }
99
103
  .notices button { padding: 0; color: inherit; background: none; border: 0; text-decoration: underline; }
100
- .editor-panel { pointer-events: auto; position: fixed; top: 0; left: 0; z-index: 1; }
101
- .panel-contents { height: 100%; overflow: hidden; border-radius: inherit; display: grid; grid-template-rows: 44px minmax(0, 1fr); }
104
+ .editor-panel { --ss-surface-page: var(--ss-surface-workspace, #fafafa); background: var(--ss-surface-page); border-radius: var(--ss-radius-bubble, 26px); pointer-events: auto; position: fixed; top: 0; left: 0; z-index: 1; }
105
+ /* Runtime roots carry theme defaults; their page ground belongs to this host. */
106
+ .editor-panel [data-ss-surface] { --ss-surface-page: inherit; }
107
+ .panel-contents { height: 100%; overflow: hidden; border-radius: inherit; display: grid; grid-template-rows: calc(2 * var(--ss-radius-bubble, 26px)) minmax(0, 1fr); }
102
108
  .editor-panel[hidden] { display: none; }
103
- .panel-header { display: flex; align-items: center; gap: 4px; padding: 2px 6px 0 6px; }
109
+ /* The close control has an 18px radius; its inset keeps both curves concentric. */
110
+ .panel-header { display: flex; align-items: center; gap: 4px; padding: 0 calc(var(--ss-radius-bubble, 26px) - 18px); }
104
111
  .panel-header > [data-ss-surface] { display: flex; flex: none; }
105
112
  .panel-header > [data-ss-surface]:empty { display: none; }
106
113
  .panel-drag-handle { min-width: 0; align-self: stretch; flex: 1; text-align: left; font-weight: 500; color: var(--ss-text-secondary, #52525b); background: none; border: 0; padding: 0; border-radius: 7px; touch-action: none; user-select: none; cursor: grab; }
114
+ .panel-title { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
115
+ .panel-title:empty::before { content: attr(data-placeholder); }
107
116
  .panel-drag-handle:first-child, .panel-header > :empty + .panel-drag-handle { padding-inline-start: 10px; }
108
117
  .panel-close { flex: none; width: 36px; height: 36px; display: grid; place-items: center; border: 0; border-radius: 999px; color: var(--ss-text-secondary, #52525b); background: none; }
109
118
  .panel-close:hover { background: var(--ss-surface-field, #f4f4f5); }
110
119
  .panel-body { position: relative; min-height: 0; display: grid; grid-template-rows: minmax(0, 1fr); }
111
- .panel-resize-handle { --ss-resize-edge-inset: max(0px, calc(var(--ss-radius-2xl, 19px) - 12px)); position: absolute; z-index: 3; border: 0; padding: 0; background: transparent; touch-action: none; user-select: none; }
120
+ .panel-resize-handle { --ss-resize-edge-inset: max(0px, calc(var(--ss-radius-bubble, 26px) - 12px)); position: absolute; z-index: 3; border: 0; padding: 0; background: transparent; touch-action: none; user-select: none; }
112
121
  .panel-resize-handle:focus-visible { outline: 2px solid var(--ss-focus-color, oklch(0.614 0.194 257)); outline-offset: -2px; border-radius: 4px; }
113
122
  .panel-resize-handle:is([data-resize-edge="n"], [data-resize-edge="s"]) { left: 12px; right: 12px; height: 8px; cursor: ns-resize; }
114
123
  .panel-resize-handle:is([data-resize-edge="e"], [data-resize-edge="w"]) { top: 12px; bottom: 12px; width: 8px; cursor: ew-resize; }
@@ -123,16 +132,13 @@ const CONTROL_STYLES = `
123
132
  .panel-resize-handle[data-resize-edge="nw"] { top: -4px; left: -4px; cursor: nwse-resize; }
124
133
  .bubble[data-dragging], .editor-panel[data-dragging] .panel-drag-handle { cursor: grabbing; }
125
134
  .bubble[data-dragging] { transition: none; }
126
- .editor-mount { min-height: 0; overflow: hidden; background: #fff; }
135
+ .editor-mount { min-height: 0; overflow: hidden; background: var(--ss-surface-page); }
127
136
  .editor-mount > [data-storystick-authoring-root] { height: 100%; }
128
- .editor-empty, .editor-loading { position: absolute; inset: 0; z-index: 2; display: grid; place-content: center; justify-items: center; padding: 24px; text-align: center; color: #52525b; background: #fff; }
137
+ .editor-empty, .editor-loading { position: absolute; inset: 0; z-index: 2; display: grid; place-content: center; justify-items: center; padding: 24px; text-align: center; color: #52525b; background: var(--ss-surface-page); }
129
138
  .panel-state-overlay { position: absolute; inset: 0; z-index: 2; min-height: 0; }
130
139
  .editor-empty svg { width: 28px; height: 28px; margin-bottom: 12px; }
131
140
  .editor-empty strong { color: #18181b; }
132
141
  .editor-empty p { margin: 4px 0 0; }
133
- .editor-loading { display: block; padding: 16px; }
134
- .editor-loading > div { display: grid; gap: 16px; }
135
- .editor-loading > div > div { height: 80px; border-radius: 6px; background: #f4f4f5; }
136
- .editor-loading > div > div:last-child { height: 128px; }
142
+ .editor-loading { display: block; padding: 0; }
137
143
  @media (prefers-reduced-motion: reduce) { .bubble { transition: none; } }
138
144
  `;
@@ -1,5 +1,6 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { STORYSTICK_MARK_RECTS, STORYSTICK_MARK_VIEW_BOX } from '@storystick/preview/page-presentation';
2
3
  /** Canonical brand geometry, shared by Studio and the lightweight preview host. */
3
4
  export function StorystickMark(props) {
4
- return (_jsxs("svg", { ...props, "data-slot": "storystick-mark", viewBox: "0 0 12 11", fill: "currentColor", focusable: "false", "aria-hidden": "true", children: [_jsx("rect", { x: "0", y: "4", width: "3", height: "7", rx: "1" }), _jsx("rect", { x: "4.5", y: "0", width: "3", height: "11", rx: "1" }), _jsx("rect", { x: "9", y: "6", width: "3", height: "5", rx: "1" })] }));
5
+ return (_jsx("svg", { ...props, "data-slot": "storystick-mark", viewBox: STORYSTICK_MARK_VIEW_BOX, fill: "currentColor", focusable: "false", "aria-hidden": "true", children: STORYSTICK_MARK_RECTS.map(rect => _jsx("rect", { ...rect }, rect.x)) }));
5
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storystick/react",
3
- "version": "0.2.0-rc.0",
3
+ "version": "0.2.0-rc.1",
4
4
  "license": "FSL-1.1-MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -80,6 +80,14 @@
80
80
  "types": "./dist/preview-toolbar-shell.d.ts",
81
81
  "import": "./dist/preview-toolbar-shell.js"
82
82
  },
83
+ "./internal/preview-page-state": {
84
+ "types": "./dist/preview-page-state.d.ts",
85
+ "import": "./dist/preview-page-state.js"
86
+ },
87
+ "./internal/authoring-skeleton": {
88
+ "types": "./dist/authoring-skeleton.d.ts",
89
+ "import": "./dist/authoring-skeleton.js"
90
+ },
83
91
  "./internal/preview-panel-state": {
84
92
  "types": "./dist/preview-panel-state.d.ts",
85
93
  "import": "./dist/preview-panel-state.js"
@@ -104,10 +112,10 @@
104
112
  "dependencies": {
105
113
  "@tanstack/query-core": "5.101.4",
106
114
  "@base-ui/react": "1.3.0",
107
- "@storystick/preview": "0.2.0-rc.0",
108
- "@storystick/media": "0.2.0-rc.0",
109
- "@storystick/types": "0.1.0-rc.0",
110
- "@storystick/client": "0.2.0-rc.0"
115
+ "@storystick/client": "0.2.0-rc.1",
116
+ "@storystick/media": "0.2.0-rc.1",
117
+ "@storystick/types": "0.1.0-rc.1",
118
+ "@storystick/preview": "0.2.0-rc.1"
111
119
  },
112
120
  "peerDependencies": {
113
121
  "react": "^19.2.8",