@pygmalionjs/pygmalion 0.5.33 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist-lib/{FrozenRoutePreview-hofdx4eP.js → FrozenRoutePreview-DStbZQD4.js} +2525 -2237
- package/dist-lib/pygmalion.js +4558 -4500
- package/dist-lib/testing.js +1 -1
- package/dist-lib/types/editor/designImport.d.ts +7 -1
- package/dist-lib/types/editor/editModePolicy.d.ts +13 -0
- package/dist-lib/types/editor/heldPseudoStates.d.ts +18 -0
- package/dist-lib/types/editor/interactiveStates.d.ts +26 -1
- package/dist-lib/types/editor/shadowPreview.d.ts +6 -0
- package/dist-lib/types/lib.d.ts +12 -7
- package/dist-lib/types/shell/ComponentStateControls.d.ts +1 -5
- package/docs/screen-state-contract.md +127 -0
- package/node/storyboard-capture-runtime.mjs +7 -0
- package/package.json +2 -1
package/dist-lib/testing.js
CHANGED
|
@@ -21,7 +21,13 @@ export interface DesignImportAsset {
|
|
|
21
21
|
scenarioId?: string;
|
|
22
22
|
description?: string;
|
|
23
23
|
}
|
|
24
|
-
export type DesignScreenInteractionAction = 'click' | 'focus'
|
|
24
|
+
export type DesignScreenInteractionAction = 'click' | 'focus'
|
|
25
|
+
/** Holds the pointer over the target through capture. */
|
|
26
|
+
| 'hover'
|
|
27
|
+
/** Holds keyboard focus, including the `:focus-visible` presentation. */
|
|
28
|
+
| 'focus-visible'
|
|
29
|
+
/** Holds the pointer-down `:active` presentation through capture. */
|
|
30
|
+
| 'active' | 'fill' | 'check' | 'press' | 'wait' | 'storage';
|
|
25
31
|
/**
|
|
26
32
|
* A critical step to capture the actual app screen after putting it in a specific user flow state.
|
|
27
33
|
* A CSS selector is used first, and if text is present, the selector candidates are narrowed down to the display string.
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
/** Explains a control that is inert because the editor is in view-only. */
|
|
2
2
|
export declare const VIEW_ONLY_REASON = "View only \u2014 switch to Editing to change this.";
|
|
3
|
+
export type ImportedRoutePresentation = 'editable-layers' | 'source-preview';
|
|
4
|
+
/**
|
|
5
|
+
* Chooses the visible surface for a route whose DOM has already been imported.
|
|
6
|
+
*
|
|
7
|
+
* Imported layers are an editing model, not the source screen itself. View-only
|
|
8
|
+
* therefore keeps the captured source preview on screen whenever one exists;
|
|
9
|
+
* switching to Editing is the explicit hand-off to the reconstructed layers.
|
|
10
|
+
* A missing preview falls back to layers instead of turning the frame blank.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveImportedRoutePresentation({ editMode, sourcePreviewAvailable, }: {
|
|
13
|
+
editMode: boolean;
|
|
14
|
+
sourcePreviewAvailable: boolean;
|
|
15
|
+
}): ImportedRoutePresentation;
|
|
3
16
|
interface ModeKeyEvent {
|
|
4
17
|
key: string;
|
|
5
18
|
code?: string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DesignScreenInteraction } from './designImport';
|
|
2
|
+
export type HeldPseudoState = 'hover' | 'focus' | 'focus-visible' | 'active';
|
|
3
|
+
/** The pseudo gestures still held after the complete recipe has run. */
|
|
4
|
+
export declare function heldPseudoInteractionsFromRecipe(interactions: readonly DesignScreenInteraction[]): DesignScreenInteraction[];
|
|
5
|
+
export declare function heldPseudoStatesFromInteractions(interactions: readonly DesignScreenInteraction[]): HeldPseudoState[];
|
|
6
|
+
/**
|
|
7
|
+
* Converts pseudo-class selectors into stable attribute selectors while
|
|
8
|
+
* leaving comments and quoted CSS values untouched.
|
|
9
|
+
*/
|
|
10
|
+
export declare function materializeHeldPseudoCss(cssText: string, states: readonly HeldPseudoState[]): string;
|
|
11
|
+
export interface HeldPseudoMaterialization {
|
|
12
|
+
cleanup(): void;
|
|
13
|
+
issues: string[];
|
|
14
|
+
}
|
|
15
|
+
/** Applies the stable selector attributes that correspond to held gestures. */
|
|
16
|
+
export declare function applyHeldPseudoAttributes(root: ParentNode, interactions: readonly DesignScreenInteraction[]): HeldPseudoMaterialization;
|
|
17
|
+
/** Materializes held pseudo classes in a live same-origin preview document. */
|
|
18
|
+
export declare function materializeHeldPseudoDocument(document: Document, interactions: readonly DesignScreenInteraction[]): HeldPseudoMaterialization;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { DesignScreenInteraction, StoryboardEnvironment } from './designImport';
|
|
2
2
|
import type { PageModel } from './store';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Screen-state axes — same-frame variations reproduced by a gesture or a boot
|
|
5
|
+
* condition.
|
|
5
6
|
*
|
|
6
7
|
* A collapsed sidebar or a hovered row is not a separate screen: it is the
|
|
7
8
|
* same screen after one gesture, reachable on every frame that has the
|
|
@@ -61,9 +62,20 @@ export interface InteractiveStateOption {
|
|
|
61
62
|
};
|
|
62
63
|
};
|
|
63
64
|
}
|
|
65
|
+
export type ScreenStateAxisKind = 'interaction' | 'condition';
|
|
64
66
|
export interface InteractiveStateDef {
|
|
65
67
|
id: string;
|
|
66
68
|
label: string;
|
|
69
|
+
/**
|
|
70
|
+
* Why this variation belongs on the frame instead of becoming another frame.
|
|
71
|
+
*
|
|
72
|
+
* `interaction` is a deterministic local state reached by a gesture on the
|
|
73
|
+
* already booted screen. `condition` is an external state that must exist
|
|
74
|
+
* before the screen boots, such as an empty response, denied permission, or
|
|
75
|
+
* stalled request. The validator refuses mixed axes so a sample value cannot
|
|
76
|
+
* quietly masquerade as a review state.
|
|
77
|
+
*/
|
|
78
|
+
kind: ScreenStateAxisKind;
|
|
67
79
|
/**
|
|
68
80
|
* Frames the axis applies to. A frame qualifies when its route matches
|
|
69
81
|
* (prefix) and its imported tree carries the test id, so an axis declared
|
|
@@ -98,6 +110,12 @@ export interface InteractiveStateDiagnostic {
|
|
|
98
110
|
interactionId: string;
|
|
99
111
|
message: string;
|
|
100
112
|
}
|
|
113
|
+
/** Preferred public name: the collection contains interaction and condition axes. */
|
|
114
|
+
export type ScreenStateAxisDef = InteractiveStateDef;
|
|
115
|
+
/** Preferred public name: an option on either kind of screen-state axis. */
|
|
116
|
+
export type ScreenStateOption = InteractiveStateOption;
|
|
117
|
+
/** Preferred public name for validation output. */
|
|
118
|
+
export type ScreenStateAxisDiagnostic = InteractiveStateDiagnostic;
|
|
101
119
|
/**
|
|
102
120
|
* Whether selecting this option asks supply for a different screen.
|
|
103
121
|
*
|
|
@@ -108,12 +126,16 @@ export interface InteractiveStateDiagnostic {
|
|
|
108
126
|
*/
|
|
109
127
|
export declare function optionChangesSupply(option: InteractiveStateOption): boolean;
|
|
110
128
|
export declare function validateInteractiveStates(defs: readonly InteractiveStateDef[]): InteractiveStateDiagnostic[];
|
|
129
|
+
/** Validates both interaction and condition axes. */
|
|
130
|
+
export declare const validateScreenStateAxes: typeof validateInteractiveStates;
|
|
111
131
|
/**
|
|
112
132
|
* Axes available on one frame. The test-id requirement is checked against
|
|
113
133
|
* the imported tree, so an axis stays hidden until the frame actually shows
|
|
114
134
|
* the control it drives — a frame without a sidebar never offers to fold it.
|
|
115
135
|
*/
|
|
116
136
|
export declare function interactiveStatesForPage(page: PageModel, defs: readonly InteractiveStateDef[]): InteractiveStateDef[];
|
|
137
|
+
/** Preferred public name for axes applicable to one frame. */
|
|
138
|
+
export declare const screenStateAxesForPage: typeof interactiveStatesForPage;
|
|
117
139
|
/**
|
|
118
140
|
* The option this frame's capture already shows, or undefined when the axis
|
|
119
141
|
* declares a single base instead of witnessing each option.
|
|
@@ -126,5 +148,8 @@ export declare function capturedInteractiveOption(def: InteractiveStateDef, page
|
|
|
126
148
|
* one option declared without steps.
|
|
127
149
|
*/
|
|
128
150
|
export declare function baseInteractiveOption(def: InteractiveStateDef, page?: PageModel): InteractiveStateOption | undefined;
|
|
151
|
+
export declare function setScreenStateAxes(defs: readonly InteractiveStateDef[] | undefined): void;
|
|
152
|
+
/** @deprecated Use setScreenStateAxes. */
|
|
129
153
|
export declare function setInteractiveStates(defs: readonly InteractiveStateDef[] | undefined): void;
|
|
130
154
|
export declare function getInteractiveStates(): readonly InteractiveStateDef[];
|
|
155
|
+
export declare const getScreenStateAxes: typeof getInteractiveStates;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DesignScreenInteraction } from './designImport';
|
|
1
2
|
export interface PreparedShadowPreview {
|
|
2
3
|
bodyHtml: string;
|
|
3
4
|
sharedCssText: string;
|
|
@@ -28,12 +29,17 @@ export interface ShadowPreviewMountInput {
|
|
|
28
29
|
overrideCssText?: string;
|
|
29
30
|
/** Resolves relative element and CSS resource URLs. */
|
|
30
31
|
baseUrl?: string;
|
|
32
|
+
/** Recipe whose terminal pseudo gestures must remain visible in frozen DOM. */
|
|
33
|
+
interactions?: readonly DesignScreenInteraction[];
|
|
31
34
|
}
|
|
32
35
|
export interface ShadowPreviewHandle {
|
|
33
36
|
readonly root: ShadowRoot;
|
|
34
37
|
readonly contentElement: HTMLElement;
|
|
35
38
|
readonly sharedCssHash: string;
|
|
36
39
|
readonly overrideKey: string;
|
|
40
|
+
readonly heldPseudoIssues: readonly string[];
|
|
41
|
+
/** Rebuilds held pseudo rules from external stylesheets after their links load. */
|
|
42
|
+
refreshLinkedHeldPseudoCss(): void;
|
|
37
43
|
updateBodyHtml(bodyHtml: string, baseUrl?: string): void;
|
|
38
44
|
updateOverrideCss(cssText: string): void;
|
|
39
45
|
updateSharedCss(cssText: string): void;
|
package/dist-lib/types/lib.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { type ApplyPayload, type DesignChangePayload, type DesignChangeResult, t
|
|
|
5
5
|
import { type InspectApplyPayload } from './editor/inspect';
|
|
6
6
|
import { type ScreenFlowPath } from './editor/screenFlows';
|
|
7
7
|
import { type ScenarioCoverageClaim } from './editor/scenarioCoverage';
|
|
8
|
-
import { type InteractiveStateDef } from './editor/interactiveStates';
|
|
8
|
+
import { type InteractiveStateDef, type ScreenStateAxisDef } from './editor/interactiveStates';
|
|
9
9
|
import { type SurfaceClassification } from './editor/surfaceDemands';
|
|
10
10
|
import { type ScreenDimensionDef } from './editor/screenDimensions';
|
|
11
11
|
import { type FrameBranchKindDef, type FrameLabelLabels } from './editor/frameLabels';
|
|
@@ -46,8 +46,8 @@ export { applyScenarioCoverageClaim, validateScenarioCoverage, } from './editor/
|
|
|
46
46
|
export type { ApplyCoverageResult, ScenarioCoverageClaim, ScenarioCoverageContext, ScenarioCoverageDiagnostic, } from './editor/scenarioCoverage';
|
|
47
47
|
export { enumerateControlCandidates, enumerateRequestCandidates, enumerateRouteCandidates, frameSurfaceDemands, getObservedRequests, getSurfaceClassifications, MIN_SURFACE_REASON_LENGTH, requestEndpointShape, setObservedRequests, setSurfaceClassifications, unclassifiedSurfaces, validateSurfaceDemands, } from './editor/surfaceDemands';
|
|
48
48
|
export type { ControlNodeLike, FrameSurfaceDemands, ObservedRequest, RouteEnumerationInput, SurfaceCandidate, SurfaceClassification, SurfaceDemandDiagnostic, SurfaceDemandKind, SurfaceVerdict, } from './editor/surfaceDemands';
|
|
49
|
-
export { validateInteractiveStates } from './editor/interactiveStates';
|
|
50
|
-
export type { InteractiveStateDef, InteractiveStateDiagnostic, InteractiveStateOption, } from './editor/interactiveStates';
|
|
49
|
+
export { validateInteractiveStates, validateScreenStateAxes, } from './editor/interactiveStates';
|
|
50
|
+
export type { InteractiveStateDef, InteractiveStateDiagnostic, InteractiveStateOption, ScreenStateAxisDef, ScreenStateAxisDiagnostic, ScreenStateAxisKind, ScreenStateOption, } from './editor/interactiveStates';
|
|
51
51
|
export { validateScreenCards } from './editor/screenCards';
|
|
52
52
|
export type { CardBadgeOption, CardSlotDef, ScreenCardDef, ScreenCardDiagnostic, } from './editor/screenCards';
|
|
53
53
|
export { validateFrameBranchKinds, framesMissingScenario } from './editor/frameLabels';
|
|
@@ -210,7 +210,7 @@ export declare function pygmalionOpenPreview(): void;
|
|
|
210
210
|
export declare function pygmalionResetCurrentEdits(): boolean;
|
|
211
211
|
export interface InitialPageDef extends DesignImportInitialPage {
|
|
212
212
|
}
|
|
213
|
-
export declare function PygmalionEditor({ registry, tokens, initialPages, initialCanvas, initialEditMode, componentConnections, viewportPresets, sessionPresets, previewEnvironmentControls, designImport, onTokensChange, appOrigin, previewRevision, previewCacheNamespace, previewArtifacts, previewArtifactEndpoint, previewConcurrency, previewOpen, onPreviewOpenChange, flowCanvas, frameSurface, captureSupply, screenFlows, screenFlowConcurrency, scenarioCoverage, screenDimensions, screenLists, sectionHeaders, sectionHeaderLabels, screenLanes, frameBranchKinds, frameLabelLabels, screenCards, liveScreenBudget, interactiveStates, surfaceClassifications, onApply, onDesignChange, onInspectApply, onInspectPreview, onInspectImpact, storyboard, storyboardDiscovery, storyboardDiscoveryEndpoint, }: {
|
|
213
|
+
export declare function PygmalionEditor({ registry, tokens, initialPages, initialCanvas, initialEditMode, componentConnections, viewportPresets, sessionPresets, previewEnvironmentControls, designImport, onTokensChange, appOrigin, previewRevision, previewCacheNamespace, previewArtifacts, previewArtifactEndpoint, previewConcurrency, previewOpen, onPreviewOpenChange, flowCanvas, frameSurface, captureSupply, screenFlows, screenFlowConcurrency, scenarioCoverage, screenDimensions, screenLists, sectionHeaders, sectionHeaderLabels, screenLanes, frameBranchKinds, frameLabelLabels, screenCards, liveScreenBudget, screenStateAxes, interactiveStates, surfaceClassifications, onApply, onDesignChange, onInspectApply, onInspectPreview, onInspectImpact, storyboard, storyboardDiscovery, storyboardDiscoveryEndpoint, }: {
|
|
214
214
|
registry?: ComponentRegistry;
|
|
215
215
|
tokens?: TokenDef[];
|
|
216
216
|
initialPages?: InitialPageDef[];
|
|
@@ -362,9 +362,14 @@ export declare function PygmalionEditor({ registry, tokens, initialPages, initia
|
|
|
362
362
|
*/
|
|
363
363
|
liveScreenBudget?: number;
|
|
364
364
|
/**
|
|
365
|
-
* Host-declared
|
|
366
|
-
*
|
|
367
|
-
*
|
|
365
|
+
* Host-declared screen-state axes. Interaction axes replay local gestures;
|
|
366
|
+
* condition axes recreate external boot conditions. Both stay attached to
|
|
367
|
+
* the same frame instead of multiplying the canvas. Validate with
|
|
368
|
+
* validateScreenStateAxes.
|
|
369
|
+
*/
|
|
370
|
+
screenStateAxes?: readonly ScreenStateAxisDef[];
|
|
371
|
+
/**
|
|
372
|
+
* @deprecated Use screenStateAxes. Kept as a migration bridge.
|
|
368
373
|
*/
|
|
369
374
|
interactiveStates?: readonly InteractiveStateDef[];
|
|
370
375
|
/**
|
|
@@ -81,11 +81,7 @@ export declare const FrameScreenLists: import("react").FunctionComponent<{
|
|
|
81
81
|
export declare const FrameCardBadges: import("react").FunctionComponent<{
|
|
82
82
|
page: PageModel;
|
|
83
83
|
}>;
|
|
84
|
-
/**
|
|
85
|
-
* Interactive states — axes reachable by acting on the screen (folding the
|
|
86
|
-
* sidebar, hovering a row). They are not sibling frames: the same frame is
|
|
87
|
-
* re-supplied with the option's steps appended to its recipe.
|
|
88
|
-
*/
|
|
84
|
+
/** Same-frame states, separated by the mechanism that reproduces them. */
|
|
89
85
|
export declare const FrameInteractiveStates: import("react").FunctionComponent<{
|
|
90
86
|
page: PageModel;
|
|
91
87
|
}>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Screen state contract
|
|
2
|
+
|
|
3
|
+
Pygmalion uses five review placements. A host must choose the placement from
|
|
4
|
+
how the variation is reproduced, not from how different its pixels look.
|
|
5
|
+
|
|
6
|
+
| Placement | Use when | Pygmalion declaration |
|
|
7
|
+
| --- | --- | --- |
|
|
8
|
+
| Frame | The variation is an independent task, journey checkpoint, structural composition, overlay context, or supported viewport that reviewers must navigate to directly. | `DesignScreenCase` |
|
|
9
|
+
| Interaction state | The same booted screen reaches a deterministic, reversible visual endpoint through a local gesture. | `ScreenStateAxisDef` with `kind: 'interaction'` |
|
|
10
|
+
| Condition state | The same screen needs external data, network, storage, permission, media, or timing conditions before it boots. | `ScreenStateAxisDef` with `kind: 'condition'` |
|
|
11
|
+
| Editable parameter | The variation only samples text length, row count, content, or another freely editable value and has no distinct product endpoint. | Edit mode, component props, list controls, viewport controls, or QA data |
|
|
12
|
+
| Behavior | The action has no stable visual endpoint to preserve. | `DesignBehaviorScenario` or an automated interaction test |
|
|
13
|
+
|
|
14
|
+
The practical test is: after reproducing the variation, is there a stable state
|
|
15
|
+
that another reviewer should be able to select again? If yes, use a frame or a
|
|
16
|
+
screen-state axis. If the answer is only “this value is longer” or “the request
|
|
17
|
+
was sent,” keep it in editing or QA instead.
|
|
18
|
+
|
|
19
|
+
## Frame versus same-frame state
|
|
20
|
+
|
|
21
|
+
Preserve a separate frame when at least one of these is true:
|
|
22
|
+
|
|
23
|
+
- it is a named step in a user journey;
|
|
24
|
+
- it changes the screen's primary task or structural composition;
|
|
25
|
+
- it provides an independent review anchor that must remain visible beside
|
|
26
|
+
another screen;
|
|
27
|
+
- it is a supported viewport whose responsive composition must be compared.
|
|
28
|
+
|
|
29
|
+
Use a same-frame axis when all of these are true:
|
|
30
|
+
|
|
31
|
+
- the route, task, and main structure stay the same;
|
|
32
|
+
- the variation is deterministic and can be recreated from the captured base;
|
|
33
|
+
- choosing it does not depend on an undocumented sequence;
|
|
34
|
+
- the final visual state remains visible long enough to capture.
|
|
35
|
+
|
|
36
|
+
Pseudo states such as hover, keyboard focus, and pointer active are always
|
|
37
|
+
interaction states. They must not become duplicate frames.
|
|
38
|
+
|
|
39
|
+
## Required axis classification
|
|
40
|
+
|
|
41
|
+
`kind` is mandatory and is enforced when the editor installs the axes.
|
|
42
|
+
`validateScreenStateAxes` is also exported for host validation gates.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import type { ScreenStateAxisDef } from '@pygmalionjs/pygmalion';
|
|
46
|
+
|
|
47
|
+
export const screenStateAxes: readonly ScreenStateAxisDef[] = [
|
|
48
|
+
{
|
|
49
|
+
id: 'editable-heading',
|
|
50
|
+
label: 'Heading',
|
|
51
|
+
kind: 'interaction',
|
|
52
|
+
requires: { testId: 'editable-heading' },
|
|
53
|
+
options: [
|
|
54
|
+
{ id: 'rest', label: 'Rest' },
|
|
55
|
+
{
|
|
56
|
+
id: 'hover',
|
|
57
|
+
label: 'Hover',
|
|
58
|
+
steps: [
|
|
59
|
+
{
|
|
60
|
+
action: 'hover',
|
|
61
|
+
label: 'Hover heading',
|
|
62
|
+
selector: '[data-testid="editable-heading"]',
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'editing',
|
|
68
|
+
label: 'Editing',
|
|
69
|
+
steps: [
|
|
70
|
+
{
|
|
71
|
+
action: 'click',
|
|
72
|
+
label: 'Open heading editor',
|
|
73
|
+
selector: '[data-testid="editable-heading"]',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
action: 'wait',
|
|
77
|
+
label: 'Heading input is visible',
|
|
78
|
+
selector: '[data-testid="heading-input"]',
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'results',
|
|
86
|
+
label: 'Results',
|
|
87
|
+
kind: 'condition',
|
|
88
|
+
options: [
|
|
89
|
+
{ id: 'loaded', label: 'Loaded' },
|
|
90
|
+
{
|
|
91
|
+
id: 'failed',
|
|
92
|
+
label: 'Failed',
|
|
93
|
+
environment: {
|
|
94
|
+
network: {
|
|
95
|
+
conditions: [
|
|
96
|
+
{ match: '/api/results', outcome: { kind: 'fail', status: 500 } },
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
<PygmalionEditor screenStateAxes={screenStateAxes} />;
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
An interaction option cannot declare `environment`. Every non-base condition
|
|
109
|
+
option must declare one. An interaction option ending in `fill` is rejected
|
|
110
|
+
unless a later targeted `wait` proves that the input reached a distinct visual
|
|
111
|
+
result. This keeps empty strings and maximum-length samples out of the state
|
|
112
|
+
panel while still allowing a search input to reproduce a real empty-result
|
|
113
|
+
state.
|
|
114
|
+
|
|
115
|
+
## Held pseudo states
|
|
116
|
+
|
|
117
|
+
Recipes support `hover`, `focus-visible`, and `active` in addition to `focus`.
|
|
118
|
+
These actions remain held through capture. The browser runner dispatches the
|
|
119
|
+
corresponding events, the Playwright worker performs the real pointer or focus
|
|
120
|
+
gesture, and frozen DOM previews materialize `:hover`, `:focus`,
|
|
121
|
+
`:focus-visible`, `:focus-within`, and `:active` selectors as scoped data
|
|
122
|
+
attributes. A DOM preview therefore preserves the same pseudo-state styling as
|
|
123
|
+
the captured application rather than relying on the editor's current pointer.
|
|
124
|
+
|
|
125
|
+
A held pseudo action must be terminal; only `wait` steps may follow it. This
|
|
126
|
+
prevents a declaration from claiming a transient hover that a later click has
|
|
127
|
+
already replaced.
|
|
@@ -381,6 +381,13 @@ export async function runStoryboardInteraction(page, interaction) {
|
|
|
381
381
|
await target.click({ timeout: timeoutMs });
|
|
382
382
|
} else if (interaction.action === 'focus') {
|
|
383
383
|
await target.focus({ timeout: timeoutMs });
|
|
384
|
+
} else if (interaction.action === 'hover') {
|
|
385
|
+
await target.hover({ timeout: timeoutMs });
|
|
386
|
+
} else if (interaction.action === 'focus-visible') {
|
|
387
|
+
await target.focus({ timeout: timeoutMs });
|
|
388
|
+
} else if (interaction.action === 'active') {
|
|
389
|
+
await target.hover({ timeout: timeoutMs });
|
|
390
|
+
await page.mouse.down();
|
|
384
391
|
} else if (interaction.action === 'fill') {
|
|
385
392
|
await target.fill(interaction.value ?? '', { timeout: timeoutMs });
|
|
386
393
|
} else if (interaction.action === 'check') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pygmalionjs/pygmalion",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Code-backed DOM design sandbox and visual QA editor",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"publishConfig": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"files": [
|
|
34
34
|
"dist-lib",
|
|
35
|
+
"docs/screen-state-contract.md",
|
|
35
36
|
"node/component-branches.mjs",
|
|
36
37
|
"node/design-session.mjs",
|
|
37
38
|
"node/dev-mirror.mjs",
|