@wire-dsl/engine 0.9.0 → 0.10.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/index.d.ts CHANGED
@@ -31,10 +31,21 @@ interface PropertySourceMap {
31
31
  nameRange: CodeRange;
32
32
  valueRange: CodeRange;
33
33
  }
34
+ /**
35
+ * SourceMap for a single event handler
36
+ * Captures ranges for event name and the full action expression
37
+ */
38
+ interface EventSourceMap {
39
+ name: string;
40
+ value: string;
41
+ range: CodeRange;
42
+ nameRange: CodeRange;
43
+ valueRange: CodeRange;
44
+ }
34
45
  /**
35
46
  * Types of nodes in Wire DSL
36
47
  */
37
- type SourceMapNodeType = 'project' | 'screen' | 'layout' | 'component' | 'component-definition' | 'layout-definition' | 'cell' | 'style' | 'mocks' | 'colors';
48
+ type SourceMapNodeType = 'project' | 'screen' | 'layout' | 'component' | 'component-definition' | 'layout-definition' | 'cell' | 'tab' | 'modal-body' | 'modal-footer' | 'style' | 'mocks' | 'colors';
38
49
  /**
39
50
  * Main SourceMap entry - represents one node in the AST
40
51
  */
@@ -53,6 +64,7 @@ interface SourceMapEntry {
53
64
  nameRange?: CodeRange;
54
65
  bodyRange?: CodeRange;
55
66
  properties?: Record<string, PropertySourceMap>;
67
+ events?: Record<string, EventSourceMap>;
56
68
  insertionPoint?: InsertionPoint;
57
69
  }
58
70
  /**
@@ -138,24 +150,58 @@ interface ASTDefinedComponent {
138
150
  interface ASTScreen {
139
151
  type: 'screen';
140
152
  name: string;
141
- params: Record<string, string | number>;
153
+ params: Record<string, string | number | string[]>;
142
154
  layout: ASTLayout;
143
155
  _meta?: {
144
156
  nodeId: string;
145
157
  };
146
158
  }
159
+ type ASTEventActionType = 'navigate' | 'show' | 'hide' | 'toggle' | 'enable' | 'disable' | 'setTab';
160
+ interface ASTEventAction {
161
+ type: ASTEventActionType;
162
+ screen?: string;
163
+ targetId?: string;
164
+ tabsId?: string;
165
+ index?: number;
166
+ }
167
+ interface ASTEventHandler {
168
+ event: string;
169
+ actions: ASTEventAction[];
170
+ }
171
+ interface ASTTab {
172
+ type: 'tab';
173
+ children: (ASTComponent | ASTLayout)[];
174
+ _meta?: {
175
+ nodeId: string;
176
+ };
177
+ }
178
+ interface ASTModalBody {
179
+ type: 'modal-body';
180
+ children: (ASTComponent | ASTLayout)[];
181
+ _meta?: {
182
+ nodeId: string;
183
+ };
184
+ }
185
+ interface ASTModalFooter {
186
+ type: 'modal-footer';
187
+ children: (ASTComponent | ASTLayout)[];
188
+ _meta?: {
189
+ nodeId: string;
190
+ };
191
+ }
147
192
  interface ASTLayout {
148
193
  type: 'layout';
149
194
  layoutType: string;
150
- params: Record<string, string | number>;
151
- children: (ASTComponent | ASTLayout | ASTCell)[];
195
+ params: Record<string, string | number | string[]>;
196
+ events: ASTEventHandler[];
197
+ children: (ASTComponent | ASTLayout | ASTCell | ASTTab | ASTModalBody | ASTModalFooter)[];
152
198
  _meta?: {
153
199
  nodeId: string;
154
200
  };
155
201
  }
156
202
  interface ASTCell {
157
203
  type: 'cell';
158
- props: Record<string, string | number>;
204
+ props: Record<string, string | number | string[]>;
159
205
  children: (ASTComponent | ASTLayout)[];
160
206
  _meta?: {
161
207
  nodeId: string;
@@ -164,7 +210,8 @@ interface ASTCell {
164
210
  interface ASTComponent {
165
211
  type: 'component';
166
212
  componentType: string;
167
- props: Record<string, string | number>;
213
+ props: Record<string, string | number | string[]>;
214
+ events: ASTEventHandler[];
168
215
  _meta?: {
169
216
  nodeId: string;
170
217
  };
@@ -267,15 +314,49 @@ interface IRScreen {
267
314
  ref: string;
268
315
  };
269
316
  }
317
+ type IREventAction = {
318
+ type: 'navigate';
319
+ screen: string;
320
+ } | {
321
+ type: 'show';
322
+ targetId: string;
323
+ } | {
324
+ type: 'hide';
325
+ targetId: string;
326
+ } | {
327
+ type: 'toggle';
328
+ targetId: string;
329
+ } | {
330
+ type: 'enable';
331
+ targetId: string;
332
+ } | {
333
+ type: 'disable';
334
+ targetId: string;
335
+ } | {
336
+ type: 'setTab';
337
+ tabsId: string;
338
+ index: number;
339
+ } | {
340
+ type: 'navigateItems';
341
+ screens: string[];
342
+ };
343
+ type IREventName = 'onClick' | 'onChange' | 'onActive' | 'onInactive' | 'onItemsClick' | 'onItemClick' | 'onRowClick' | 'onClose';
344
+ interface IREventHandler {
345
+ event: IREventName;
346
+ actions: IREventAction[];
347
+ }
348
+ /** Special target id used when event target is self (hide(self), show(self), toggle(self)) */
349
+ declare const SELF_TARGET = "_self";
270
350
  type IRNode = IRContainerNode | IRComponentNode | IRInstanceNode;
271
351
  interface IRContainerNode {
272
352
  id: string;
273
353
  kind: 'container';
274
- containerType: 'stack' | 'grid' | 'split' | 'panel' | 'card';
275
- params: Record<string, string | number>;
354
+ containerType: 'stack' | 'grid' | 'split' | 'panel' | 'card' | 'tabs' | 'tab' | 'modal' | 'modal-body' | 'modal-footer';
355
+ params: Record<string, string | number | string[]>;
276
356
  children: Array<{
277
357
  ref: string;
278
358
  }>;
359
+ events?: IREventHandler[];
279
360
  style: IRNodeStyle;
280
361
  meta: IRMeta;
281
362
  }
@@ -283,7 +364,9 @@ interface IRComponentNode {
283
364
  id: string;
284
365
  kind: 'component';
285
366
  componentType: string;
286
- props: Record<string, string | number>;
367
+ props: Record<string, string | number | string[]>;
368
+ userDefinedId?: string;
369
+ events?: IREventHandler[];
287
370
  style: IRNodeStyle;
288
371
  meta: IRMeta;
289
372
  }
@@ -311,7 +394,7 @@ interface IRInstanceNode {
311
394
  /** Whether it originated from a `define Component` or `define Layout` */
312
395
  definitionKind: 'component' | 'layout';
313
396
  /** Props/params passed at the call site (e.g. { text: "Hello" }) */
314
- invocationProps: Record<string, string | number>;
397
+ invocationProps: Record<string, string | number | string[]>;
315
398
  /** Reference to the root IR node produced by expanding the definition */
316
399
  expandedRoot: {
317
400
  ref: string;
@@ -350,8 +433,14 @@ declare class IRGenerator {
350
433
  type: string;
351
434
  }>;
352
435
  private convertLayout;
436
+ private convertTab;
437
+ private convertModalBody;
438
+ private convertModalFooter;
353
439
  private convertCell;
354
440
  private convertComponent;
441
+ private convertASTEventAction;
442
+ private convertASTEvents;
443
+ private extractOnItemsClickEvent;
355
444
  private expandDefinedComponent;
356
445
  private expandDefinedLayout;
357
446
  private resolveChildrenSlot;
@@ -431,6 +520,86 @@ declare function resolveDevicePreset(device: string): {
431
520
  */
432
521
  declare function isValidDevice(device: string): boolean;
433
522
 
523
+ /**
524
+ * Wire DSL Play Test State Management
525
+ *
526
+ * Provides the `applyStateChange` function for mutating a copy of the IR
527
+ * during play test sessions. The engine stays 100% static — this module
528
+ * produces a NEW IR (immutable pattern) from a change descriptor.
529
+ *
530
+ * The Wire source is never touched during play test. The canvas keeps a
531
+ * mutable copy of the IR per session and discards it when play test ends.
532
+ *
533
+ * Usage (Wire Studio / Canvas):
534
+ * const newIR = applyStateChange(currentIR, { type: 'setVisible', targetId: 'confirmModal', visible: true });
535
+ * const newSVG = render(newIR, screenName);
536
+ */
537
+
538
+ type IRStateChange =
539
+ /** Show or hide a node by its userDefinedId (or '_self' resolved from originNodeId) */
540
+ {
541
+ type: 'setVisible';
542
+ targetId: string;
543
+ visible: boolean;
544
+ }
545
+ /** Toggle visibility of a node by its userDefinedId (or '_self') */
546
+ | {
547
+ type: 'toggleVisible';
548
+ targetId: string;
549
+ }
550
+ /** Change the active tab index in a layout tabs container */
551
+ | {
552
+ type: 'setActiveTab';
553
+ tabsId: string;
554
+ index: number;
555
+ }
556
+ /** Set checked state on Checkbox / Radio controls */
557
+ | {
558
+ type: 'setChecked';
559
+ targetId: string;
560
+ checked: boolean;
561
+ }
562
+ /** Toggle checked state on Checkbox / Radio controls */
563
+ | {
564
+ type: 'toggleChecked';
565
+ targetId: string;
566
+ }
567
+ /** Set enabled state on Toggle controls */
568
+ | {
569
+ type: 'setEnabled';
570
+ targetId: string;
571
+ enabled: boolean;
572
+ }
573
+ /** Toggle enabled state on Toggle controls */
574
+ | {
575
+ type: 'toggleEnabled';
576
+ targetId: string;
577
+ }
578
+ /** Set disabled state on any component (enable = disabled false, disable = disabled true) */
579
+ | {
580
+ type: 'setDisabled';
581
+ targetId: string;
582
+ disabled: boolean;
583
+ }
584
+ /** Navigate to a different screen (changes the active screen id in the IR) */
585
+ | {
586
+ type: 'navigateTo';
587
+ screen: string;
588
+ };
589
+ /**
590
+ * Apply a state change to a copy of the IR, returning a new IR object.
591
+ *
592
+ * - Does NOT mutate the input IR
593
+ * - Returns a new IR with the minimal change applied
594
+ * - `originNodeId` is required when `targetId === '_self'` to resolve the self reference
595
+ *
596
+ * @param ir The current IR (from the canvas's mutable session copy)
597
+ * @param change The state change to apply
598
+ * @param originNodeId The nodeId of the element that triggered the event (required for self-target)
599
+ * @returns A new IR with the change applied
600
+ */
601
+ declare function applyStateChange(ir: IRContract, change: IRStateChange, originNodeId?: string): IRContract;
602
+
434
603
  /**
435
604
  * Layout Engine
436
605
  *
@@ -463,6 +632,8 @@ declare class LayoutEngine {
463
632
  private calculateSplit;
464
633
  private calculatePanel;
465
634
  private calculateCard;
635
+ private calculateTabs;
636
+ private calculateModal;
466
637
  /**
467
638
  * Calculate layout for an instance node.
468
639
  * The instance is a transparent wrapper — its bounding box equals the
@@ -497,6 +668,11 @@ declare class LayoutEngine {
497
668
  private calculateChildHeight;
498
669
  private calculateChildWidth;
499
670
  private estimateTextWidth;
671
+ /**
672
+ * Returns false only when the node has an explicit `visible: 'false'` param/prop.
673
+ * Missing or any other value is treated as visible.
674
+ */
675
+ private isNodeVisible;
500
676
  private parseBooleanProp;
501
677
  private adjustNodeYPositions;
502
678
  }
@@ -717,7 +893,8 @@ declare class SVGRenderer {
717
893
  protected renderSeparate(node: IRComponentNode, _pos: any): string;
718
894
  protected renderAlert(node: IRComponentNode, pos: any): string;
719
895
  protected renderBadge(node: IRComponentNode, pos: any): string;
720
- protected renderModal(node: IRComponentNode, pos: any): string;
896
+ protected renderModalDecoration(node: IRNode, pos: any, output: string[]): void;
897
+ protected renderModalFooterDecoration(pos: any, output: string[]): void;
721
898
  protected renderList(node: IRComponentNode, pos: any): string;
722
899
  protected renderGenericComponent(node: IRComponentNode, pos: any): string;
723
900
  protected renderStat(node: IRComponentNode, pos: any): string;
@@ -806,10 +983,17 @@ declare class SVGRenderer {
806
983
  private buildParentContainerIndex;
807
984
  protected escapeXml(text: string): string;
808
985
  /**
809
- * Get data-node-id attribute string for SVG elements
810
- * Enables bidirectional selection between code and canvas
986
+ * Get data-node-id and event data attributes for SVG elements.
987
+ * Enables bidirectional selection between code and canvas (data-node-id)
988
+ * and play test interactivity (data-event-*, data-user-id, data-tabs-id).
811
989
  */
812
990
  protected getDataNodeId(node: IRComponentNode | IRContainerNode | IRInstanceNode): string;
991
+ private isScopedEvent;
992
+ private getScopedEventAttrs;
993
+ private getTabsTriggerAttrs;
994
+ private serializeEventHandler;
995
+ private eventNameToDataAttr;
996
+ private serializeEventAction;
813
997
  }
814
998
  declare function renderToSVG(ir: IRContract, layout: LayoutResult, options?: SVGRenderOptions): string;
815
999
  declare function createSVGElement(tag: string, attrs: Record<string, string | number>, children?: string[]): string;
@@ -1218,6 +1402,19 @@ declare class SourceMapBuilder {
1218
1402
  separator?: any;
1219
1403
  full?: any;
1220
1404
  }): PropertySourceMap;
1405
+ /**
1406
+ * Add an event handler to an existing node in the SourceMap
1407
+ * Events have action expressions as values (e.g., "show(modal) & navigate(Home)")
1408
+ *
1409
+ * @param nodeId - ID of the node that owns this event
1410
+ * @param eventName - Name of the event (e.g., "onClick", "onClose")
1411
+ * @param tokens - Captured tokens: name token for event key, CST node for actionChain
1412
+ * @returns The EventSourceMap entry created
1413
+ */
1414
+ addEvent(nodeId: string, eventName: string, tokens: {
1415
+ name: any;
1416
+ value: any;
1417
+ }): EventSourceMap;
1221
1418
  /**
1222
1419
  * Push a parent onto the stack (when entering a container node)
1223
1420
  */
@@ -1404,4 +1601,4 @@ declare class SourceMapResolver {
1404
1601
 
1405
1602
  declare const version = "0.0.1";
1406
1603
 
1407
- export { type AST, type ASTCell, type ASTComponent, type ASTDefinedComponent, type ASTDefinedLayout, type ASTLayout, type ASTScreen, type CapturedTokens, type CodeRange, DENSITY_TOKENS, DEVICE_PRESETS, type DesignTokens, type DevicePreset, type IRComponent, type IRComponentNode, type IRContainerNode, type IRContract, IRGenerator, type IRInstanceNode, type IRLayout, type IRMeta, type IRMetadata, type IRNode, type IRNodeStyle, type IRProject, type IRScreen, type IRStyle, type IRWireframe, type InsertionPoint, LayoutEngine, type LayoutPosition, type LayoutResult, type ParseDiagnosticsResult, type ParseError, type ParseResult, type ParseWireDSLWithSourceMapOptions, type ParsedComponent, type ParsedWireframe, type Position, type PositionQueryResult, type PropertySourceMap, type SVGComponent, type SVGRenderOptions, SVGRenderer, SkeletonSVGRenderer, SketchSVGRenderer, SourceMapBuilder, type SourceMapEntry, type SourceMapNodeType, SourceMapResolver, buildSVG, calculateLayout, createSVGElement, generateIR, generateStableNodeId, getTypeFromNodeId, isValidDevice, isValidNodeId, parseWireDSL, parseWireDSLWithSourceMap, renderToSVG, resolveDevicePreset, resolveGridPosition, resolveTokens, version };
1604
+ export { type AST, type ASTCell, type ASTComponent, type ASTDefinedComponent, type ASTDefinedLayout, type ASTEventAction, type ASTEventActionType, type ASTEventHandler, type ASTLayout, type ASTModalBody, type ASTModalFooter, type ASTScreen, type ASTTab, type CapturedTokens, type CodeRange, DENSITY_TOKENS, DEVICE_PRESETS, type DesignTokens, type DevicePreset, type EventSourceMap, type IRComponent, type IRComponentNode, type IRContainerNode, type IRContract, type IREventAction, type IREventHandler, type IREventName, IRGenerator, type IRInstanceNode, type IRLayout, type IRMeta, type IRMetadata, type IRNode, type IRNodeStyle, type IRProject, type IRScreen, type IRStateChange, type IRStyle, type IRWireframe, type InsertionPoint, LayoutEngine, type LayoutPosition, type LayoutResult, type ParseDiagnosticsResult, type ParseError, type ParseResult, type ParseWireDSLWithSourceMapOptions, type ParsedComponent, type ParsedWireframe, type Position, type PositionQueryResult, type PropertySourceMap, SELF_TARGET, type SVGComponent, type SVGRenderOptions, SVGRenderer, SkeletonSVGRenderer, SketchSVGRenderer, SourceMapBuilder, type SourceMapEntry, type SourceMapNodeType, SourceMapResolver, applyStateChange, buildSVG, calculateLayout, createSVGElement, generateIR, generateStableNodeId, getTypeFromNodeId, isValidDevice, isValidNodeId, parseWireDSL, parseWireDSLWithSourceMap, renderToSVG, resolveDevicePreset, resolveGridPosition, resolveTokens, version };