foldkit 0.89.0 → 0.90.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.
Files changed (118) hide show
  1. package/README.md +19 -10
  2. package/dist/command/index.d.ts.map +1 -1
  3. package/dist/command/index.js +3 -11
  4. package/dist/devTools/overlay.d.ts +3 -3
  5. package/dist/devTools/overlay.d.ts.map +1 -1
  6. package/dist/devTools/overlay.js +209 -171
  7. package/dist/devTools/protocol.d.ts +77 -19
  8. package/dist/devTools/protocol.d.ts.map +1 -1
  9. package/dist/devTools/protocol.js +10 -5
  10. package/dist/devTools/serialize.d.ts +8 -2
  11. package/dist/devTools/serialize.d.ts.map +1 -1
  12. package/dist/devTools/serialize.js +11 -2
  13. package/dist/devTools/store.d.ts +9 -5
  14. package/dist/devTools/store.d.ts.map +1 -1
  15. package/dist/devTools/store.js +13 -13
  16. package/dist/devTools/webSocketBridge.d.ts.map +1 -1
  17. package/dist/devTools/webSocketBridge.js +2 -2
  18. package/dist/html/index.d.ts +12 -0
  19. package/dist/html/index.d.ts.map +1 -1
  20. package/dist/html/index.js +20 -4
  21. package/dist/mount/index.d.ts +64 -21
  22. package/dist/mount/index.d.ts.map +1 -1
  23. package/dist/mount/index.js +43 -32
  24. package/dist/mount/public.d.ts +1 -1
  25. package/dist/mount/public.d.ts.map +1 -1
  26. package/dist/runtime/crashUI.js +30 -30
  27. package/dist/runtime/runtime.d.ts.map +1 -1
  28. package/dist/runtime/runtime.js +4 -4
  29. package/dist/test/apps/bubbling.js +4 -4
  30. package/dist/test/apps/disabledButton.js +10 -10
  31. package/dist/test/apps/fileUpload.d.ts.map +1 -1
  32. package/dist/test/apps/fileUpload.js +13 -13
  33. package/dist/test/apps/interactions.d.ts.map +1 -1
  34. package/dist/test/apps/interactions.js +15 -15
  35. package/dist/test/apps/keypress.js +8 -8
  36. package/dist/test/apps/login.d.ts.map +1 -1
  37. package/dist/test/apps/login.js +32 -24
  38. package/dist/test/apps/logoutButton.d.ts.map +1 -1
  39. package/dist/test/apps/logoutButton.js +2 -2
  40. package/dist/test/apps/mountPanel.d.ts +19 -3
  41. package/dist/test/apps/mountPanel.d.ts.map +1 -1
  42. package/dist/test/apps/mountPanel.js +41 -20
  43. package/dist/test/apps/multiRole.js +3 -3
  44. package/dist/test/apps/pointer.d.ts.map +1 -1
  45. package/dist/test/apps/pointer.js +11 -11
  46. package/dist/test/apps/resumeUpload.d.ts.map +1 -1
  47. package/dist/test/apps/resumeUpload.js +8 -8
  48. package/dist/test/internal.d.ts +50 -20
  49. package/dist/test/internal.d.ts.map +1 -1
  50. package/dist/test/internal.js +65 -74
  51. package/dist/test/scene.d.ts +7 -7
  52. package/dist/test/scene.d.ts.map +1 -1
  53. package/dist/test/scene.js +42 -37
  54. package/dist/test/story.d.ts.map +1 -1
  55. package/dist/test/story.js +2 -2
  56. package/dist/ui/anchor.d.ts +12 -8
  57. package/dist/ui/anchor.d.ts.map +1 -1
  58. package/dist/ui/anchor.js +41 -16
  59. package/dist/ui/animation/index.js +24 -24
  60. package/dist/ui/button/index.d.ts.map +1 -1
  61. package/dist/ui/button/index.js +6 -6
  62. package/dist/ui/calendar/index.d.ts.map +1 -1
  63. package/dist/ui/calendar/index.js +101 -99
  64. package/dist/ui/checkbox/index.d.ts.map +1 -1
  65. package/dist/ui/checkbox/index.js +15 -15
  66. package/dist/ui/combobox/multi.d.ts +1 -7
  67. package/dist/ui/combobox/multi.d.ts.map +1 -1
  68. package/dist/ui/combobox/shared.d.ts +14 -5
  69. package/dist/ui/combobox/shared.d.ts.map +1 -1
  70. package/dist/ui/combobox/shared.js +137 -125
  71. package/dist/ui/combobox/single.d.ts +1 -7
  72. package/dist/ui/combobox/single.d.ts.map +1 -1
  73. package/dist/ui/datePicker/index.js +4 -4
  74. package/dist/ui/dialog/index.d.ts.map +1 -1
  75. package/dist/ui/dialog/index.js +27 -27
  76. package/dist/ui/disclosure/index.d.ts.map +1 -1
  77. package/dist/ui/disclosure/index.js +24 -22
  78. package/dist/ui/dragAndDrop/index.d.ts.map +1 -1
  79. package/dist/ui/dragAndDrop/index.js +15 -15
  80. package/dist/ui/fieldset/index.js +6 -6
  81. package/dist/ui/fileDrop/index.d.ts +2 -2
  82. package/dist/ui/fileDrop/index.d.ts.map +1 -1
  83. package/dist/ui/fileDrop/index.js +16 -16
  84. package/dist/ui/input/index.d.ts.map +1 -1
  85. package/dist/ui/input/index.js +15 -13
  86. package/dist/ui/listbox/multi.d.ts +1 -7
  87. package/dist/ui/listbox/multi.d.ts.map +1 -1
  88. package/dist/ui/listbox/shared.d.ts +12 -3
  89. package/dist/ui/listbox/shared.d.ts.map +1 -1
  90. package/dist/ui/listbox/shared.js +91 -89
  91. package/dist/ui/listbox/single.d.ts +1 -7
  92. package/dist/ui/listbox/single.d.ts.map +1 -1
  93. package/dist/ui/menu/index.d.ts +12 -3
  94. package/dist/ui/menu/index.d.ts.map +1 -1
  95. package/dist/ui/menu/index.js +75 -77
  96. package/dist/ui/popover/index.d.ts +13 -3
  97. package/dist/ui/popover/index.d.ts.map +1 -1
  98. package/dist/ui/popover/index.js +62 -53
  99. package/dist/ui/radioGroup/index.d.ts.map +1 -1
  100. package/dist/ui/radioGroup/index.js +20 -20
  101. package/dist/ui/select/index.d.ts.map +1 -1
  102. package/dist/ui/select/index.js +13 -11
  103. package/dist/ui/slider/index.d.ts.map +1 -1
  104. package/dist/ui/slider/index.js +26 -26
  105. package/dist/ui/switch/index.d.ts.map +1 -1
  106. package/dist/ui/switch/index.js +14 -14
  107. package/dist/ui/tabs/index.d.ts.map +1 -1
  108. package/dist/ui/tabs/index.js +40 -36
  109. package/dist/ui/textarea/index.d.ts.map +1 -1
  110. package/dist/ui/textarea/index.js +15 -13
  111. package/dist/ui/toast/index.d.ts.map +1 -1
  112. package/dist/ui/toast/index.js +27 -27
  113. package/dist/ui/tooltip/index.d.ts +11 -2
  114. package/dist/ui/tooltip/index.d.ts.map +1 -1
  115. package/dist/ui/tooltip/index.js +33 -33
  116. package/dist/ui/virtualList/index.d.ts.map +1 -1
  117. package/dist/ui/virtualList/index.js +18 -15
  118. package/package.json +1 -1
@@ -3,7 +3,7 @@ import { Array as Array_, Context, Effect, Equal, Function, HashSet, Match as M,
3
3
  import * as Command from '../command/index.js';
4
4
  import { lockScroll, unlockScroll } from '../dom/scrollLock.js';
5
5
  import { OptionExt } from '../effectExtensions/index.js';
6
- import { createKeyedLazy, createLazy, html, } from '../html/index.js';
6
+ import { DEVTOOLS_HOST_ID, createKeyedLazy, createLazy, html, } from '../html/index.js';
7
7
  import { m } from '../message/index.js';
8
8
  import { makeProgram } from '../runtime/runtime.js';
9
9
  import { makeSubscriptions } from '../runtime/subscription.js';
@@ -19,13 +19,17 @@ const DisplayCommand = S.Struct({
19
19
  name: S.String,
20
20
  args: S.Option(S.Record(S.String, S.Unknown)),
21
21
  });
22
+ const DisplayMount = S.Struct({
23
+ name: S.String,
24
+ args: S.Option(S.Record(S.String, S.Unknown)),
25
+ });
22
26
  const DisplayEntry = S.Struct({
23
27
  tag: S.String,
24
28
  submodelPath: S.Array(S.String),
25
29
  maybeLeafTag: S.Option(S.String),
26
30
  commands: S.Array(DisplayCommand),
27
- mountStartNames: S.Array(S.String),
28
- mountEndNames: S.Array(S.String),
31
+ mountStarts: S.Array(DisplayMount),
32
+ mountEnds: S.Array(DisplayMount),
29
33
  timestamp: S.Number,
30
34
  isModelChanged: S.Boolean,
31
35
  });
@@ -54,7 +58,7 @@ const Model = S.Struct({
54
58
  isMobile: S.Boolean,
55
59
  entries: S.Array(DisplayEntry),
56
60
  initCommands: S.Array(DisplayCommand),
57
- initMountStartNames: S.Array(S.String),
61
+ initMountStarts: S.Array(DisplayMount),
58
62
  startIndex: S.Number,
59
63
  isPaused: S.Boolean,
60
64
  pausedAtIndex: S.Number,
@@ -74,7 +78,7 @@ const Flags = S.Struct({
74
78
  isMobile: S.Boolean,
75
79
  entries: S.Array(DisplayEntry),
76
80
  initCommands: S.Array(DisplayCommand),
77
- initMountStartNames: S.Array(S.String),
81
+ initMountStarts: S.Array(DisplayMount),
78
82
  startIndex: S.Number,
79
83
  isPaused: S.Boolean,
80
84
  pausedAtIndex: S.Number,
@@ -107,7 +111,7 @@ const GotInspectorTabsMessage = m('GotInspectorTabsMessage', {
107
111
  const ReceivedStoreUpdate = m('ReceivedStoreUpdate', {
108
112
  entries: S.Array(DisplayEntry),
109
113
  initCommands: S.Array(DisplayCommand),
110
- initMountStartNames: S.Array(S.String),
114
+ initMountStarts: S.Array(DisplayMount),
111
115
  startIndex: S.Number,
112
116
  isPaused: S.Boolean,
113
117
  pausedAtIndex: S.Number,
@@ -154,6 +158,10 @@ const toDisplayCommand = (command) => ({
154
158
  name: command.name,
155
159
  args: Option.fromNullishOr(command.args),
156
160
  });
161
+ const toDisplayMount = (mount) => ({
162
+ name: mount.name,
163
+ args: Option.fromNullishOr(mount.args),
164
+ });
157
165
  const toDisplayEntries = ({ entries }) => Array_.map(entries, entry => {
158
166
  const { submodelPath, maybeLeafTag } = extractSubmodelInfo(entry.tag, entry.message);
159
167
  return {
@@ -161,8 +169,8 @@ const toDisplayEntries = ({ entries }) => Array_.map(entries, entry => {
161
169
  submodelPath,
162
170
  maybeLeafTag,
163
171
  commands: Array_.map(entry.commands, toDisplayCommand),
164
- mountStartNames: entry.mountStartNames,
165
- mountEndNames: entry.mountEndNames,
172
+ mountStarts: Array_.map(entry.mountStarts, toDisplayMount),
173
+ mountEnds: Array_.map(entry.mountEnds, toDisplayMount),
166
174
  timestamp: entry.timestamp,
167
175
  isModelChanged: entry.isModelChanged,
168
176
  };
@@ -170,7 +178,7 @@ const toDisplayEntries = ({ entries }) => Array_.map(entries, entry => {
170
178
  const toDisplayState = (state) => ({
171
179
  entries: toDisplayEntries(state),
172
180
  initCommands: Array_.map(state.initCommands, toDisplayCommand),
173
- initMountStartNames: state.initMountStartNames,
181
+ initMountStarts: Array_.map(state.initMountStarts, toDisplayMount),
174
182
  startIndex: state.startIndex,
175
183
  isPaused: state.isPaused,
176
184
  pausedAtIndex: state.pausedAtIndex,
@@ -326,7 +334,7 @@ const makeUpdate = (store, shadow, mode) => {
326
334
  }),
327
335
  [],
328
336
  ],
329
- ReceivedStoreUpdate: ({ entries, initCommands, initMountStartNames, startIndex, isPaused, pausedAtIndex, }) => {
337
+ ReceivedStoreUpdate: ({ entries, initCommands, initMountStarts, startIndex, isPaused, pausedAtIndex, }) => {
330
338
  const shouldFollowLatest = M.value(mode).pipe(M.when('TimeTravel', () => !isPaused), M.when('Inspect', () => model.isFollowingLatest), M.exhaustive);
331
339
  const latestIndex = Array_.match(entries, {
332
340
  onEmpty: () => INIT_INDEX,
@@ -338,7 +346,7 @@ const makeUpdate = (store, shadow, mode) => {
338
346
  evo(model, {
339
347
  entries: () => entries,
340
348
  initCommands: () => initCommands,
341
- initMountStartNames: () => initMountStartNames,
349
+ initMountStarts: () => initMountStarts,
342
350
  startIndex: () => startIndex,
343
351
  isPaused: () => isPaused,
344
352
  pausedAtIndex: () => pausedAtIndex,
@@ -414,34 +422,34 @@ const PANEL_POSITION_CLASS = {
414
422
  TopLeft: 'dt-panel-tl',
415
423
  };
416
424
  const makeView = (position, mode, maybeBanner) => {
417
- const { div, header, span, ul, button, svg, path, keyed, Key, Class, Style, OnClick, AriaHidden, Xmlns, Fill, ViewBox, StrokeWidth, Stroke, StrokeLinecap, StrokeLinejoin, D, } = html();
425
+ const h = html();
418
426
  const lazyTreeNode = createKeyedLazy();
419
427
  const lazyMessageRow = createKeyedLazy();
420
428
  const lazyTabContent = createKeyedLazy();
421
429
  const lazyMessageList = createLazy();
422
430
  // JSON TREE
423
- const leafValueView = (value) => M.value(value).pipe(M.when(Predicate.isNull, () => span([Class('json-null italic')], ['null'])), M.when(Predicate.isUndefined, () => span([Class('json-null italic')], ['undefined'])), M.when(Predicate.isString, stringValue => span([Class('json-string')], [`"${stringValue}"`])), M.when(Predicate.isNumber, numberValue => span([Class('json-number')], [String(numberValue)])), M.when(Predicate.isBoolean, booleanValue => span([Class('json-boolean')], [String(booleanValue)])), M.orElse(unknownValue => span([Class('json-null')], [String(unknownValue)])));
424
- const keyView = (key) => span([Class('json-key')], [`${key}:\u00a0`]);
431
+ const leafValueView = (value) => M.value(value).pipe(M.when(Predicate.isNull, () => h.span([h.Class('json-null italic')], ['null'])), M.when(Predicate.isUndefined, () => h.span([h.Class('json-null italic')], ['undefined'])), M.when(Predicate.isString, stringValue => h.span([h.Class('json-string')], [`"${stringValue}"`])), M.when(Predicate.isNumber, numberValue => h.span([h.Class('json-number')], [String(numberValue)])), M.when(Predicate.isBoolean, booleanValue => h.span([h.Class('json-boolean')], [String(booleanValue)])), M.orElse(unknownValue => h.span([h.Class('json-null')], [String(unknownValue)])));
432
+ const keyView = (key) => h.span([h.Class('json-key')], [`${key}:\u00a0`]);
425
433
  const CHEVRON_RIGHT = 'M8.25 4.5l7.5 7.5-7.5 7.5';
426
434
  const CHEVRON_DOWN = 'M19.5 8.25l-7.5 7.5-7.5-7.5';
427
- const arrowView = (isExpanded) => svg([
428
- AriaHidden(true),
429
- Class('json-arrow shrink-0'),
430
- Xmlns('http://www.w3.org/2000/svg'),
431
- Fill('none'),
432
- ViewBox('0 0 24 24'),
433
- StrokeWidth('2'),
434
- Stroke('currentColor'),
435
+ const arrowView = (isExpanded) => h.svg([
436
+ h.AriaHidden(true),
437
+ h.Class('json-arrow shrink-0'),
438
+ h.Xmlns('http://www.w3.org/2000/h.svg'),
439
+ h.Fill('none'),
440
+ h.ViewBox('0 0 24 24'),
441
+ h.StrokeWidth('2'),
442
+ h.Stroke('currentColor'),
435
443
  ], [
436
- path([
437
- StrokeLinecap('round'),
438
- StrokeLinejoin('round'),
439
- D(isExpanded ? CHEVRON_DOWN : CHEVRON_RIGHT),
444
+ h.path([
445
+ h.StrokeLinecap('round'),
446
+ h.StrokeLinejoin('round'),
447
+ h.D(isExpanded ? CHEVRON_DOWN : CHEVRON_RIGHT),
440
448
  ], []),
441
449
  ]);
442
- const tagLabelView = (tag) => span([Class('json-tag')], [tag]);
443
- const diffDotView = span([Class('diff-dot')], []);
444
- const inlineDiffDotView = span([Class('diff-dot-inline')], []);
450
+ const tagLabelView = (tag) => h.span([h.Class('json-tag')], [tag]);
451
+ const diffDotView = h.span([h.Class('diff-dot')], []);
452
+ const inlineDiffDotView = h.span([h.Class('diff-dot-inline')], []);
445
453
  const flattenTree = ({ value, treePath, depth, key, ...shared }) => {
446
454
  const { rootPath, expandedPaths, changedPaths, affectedPaths, accumulator, indentRootChildren, } = shared;
447
455
  const isRoot = treePath === rootPath;
@@ -484,12 +492,12 @@ const makeView = (position, mode, maybeBanner) => {
484
492
  }
485
493
  };
486
494
  const flatNodeView = (value, treePath, depth, key, nodeIsExpandable, isExpanded, isChanged, isAffected, isRoot, tag) => {
487
- const indent = Style({ paddingLeft: `${depth * TREE_INDENT_PX}px` });
495
+ const indent = h.Style({ paddingLeft: `${depth * TREE_INDENT_PX}px` });
488
496
  const hasDiffDot = isChanged || isAffected;
489
497
  if (!nodeIsExpandable) {
490
- return div([
491
- Key(treePath),
492
- Class(clsx('tree-row flex items-center gap-px font-mono text-2xs', {
498
+ return h.div([
499
+ h.Key(treePath),
500
+ h.Class(clsx('tree-row flex items-center gap-px font-mono text-2xs', {
493
501
  'diff-changed': isChanged,
494
502
  })),
495
503
  indent,
@@ -504,20 +512,20 @@ const makeView = (position, mode, maybeBanner) => {
504
512
  ? `(${value.length})`
505
513
  : ''
506
514
  : collapsedPreview(value);
507
- return div([
508
- Key(treePath),
509
- Class(clsx('tree-row flex items-center gap-px font-mono text-2xs', {
515
+ return h.div([
516
+ h.Key(treePath),
517
+ h.Class(clsx('tree-row flex items-center gap-px font-mono text-2xs', {
510
518
  'tree-row-expandable cursor-pointer': !isRoot,
511
519
  'diff-changed': isChanged,
512
520
  })),
513
521
  indent,
514
- ...(isRoot ? [] : [OnClick(ToggledTreeNode({ path: treePath }))]),
522
+ ...(isRoot ? [] : [h.OnClick(ToggledTreeNode({ path: treePath }))]),
515
523
  ], [
516
524
  ...(isRoot ? [] : [arrowView(isExpanded)]),
517
525
  ...(!isRoot && hasDiffDot ? [diffDotView] : []),
518
526
  ...(String_.isNonEmpty(key) ? [keyView(key)] : []),
519
527
  ...(String_.isNonEmpty(tag) ? [tagLabelView(tag)] : []),
520
- span([Class('json-preview')], [preview]),
528
+ h.span([h.Class('json-preview')], [preview]),
521
529
  ]);
522
530
  };
523
531
  const renderFlatNode = (node) => lazyTreeNode(node.treePath, flatNodeView, [
@@ -546,8 +554,8 @@ const makeView = (position, mode, maybeBanner) => {
546
554
  accumulator: nodes,
547
555
  indentRootChildren,
548
556
  });
549
- return div([
550
- Class('inspector-tree flex-1 overflow-auto min-h-0 min-w-0 overscroll-none'),
557
+ return h.div([
558
+ h.Class('inspector-tree flex-1 overflow-auto min-h-0 min-w-0 overscroll-none'),
551
559
  ], nodes.map(renderFlatNode));
552
560
  };
553
561
  const inspectedTimestamp = (model) => {
@@ -571,8 +579,8 @@ const makeView = (position, mode, maybeBanner) => {
571
579
  : `+${remainingMs.toFixed(1)}ms`;
572
580
  }), Option.getOrElse(() => ''));
573
581
  };
574
- const emptyInspectorView = div([
575
- Class('flex-1 flex items-center justify-center text-dt-muted text-2xs font-mono min-w-0'),
582
+ const emptyInspectorView = h.div([
583
+ h.Class('flex-1 flex items-center justify-center text-dt-muted text-2xs font-mono min-w-0'),
576
584
  ], ['Click a message to inspect']);
577
585
  const INSPECTOR_TABS = [
578
586
  'Model',
@@ -580,8 +588,8 @@ const makeView = (position, mode, maybeBanner) => {
580
588
  'Commands',
581
589
  'Mounts',
582
590
  ];
583
- const noMessageView = div([
584
- Class('flex-1 flex items-center justify-center text-dt-muted text-2xs font-mono min-w-0'),
591
+ const noMessageView = h.div([
592
+ h.Class('flex-1 flex items-center justify-center text-dt-muted text-2xs font-mono min-w-0'),
585
593
  ], ['init — no Message']);
586
594
  const modelTabContent = (inspectedModel, expandedPaths, changedPaths, affectedPaths) => treeView(inspectedModel, 'root', expandedPaths, changedPaths, affectedPaths, Option.none(), true);
587
595
  const unwrapIfFiltered = (message, maybeSubmodelFilter) => {
@@ -611,11 +619,11 @@ const makeView = (position, mode, maybeBanner) => {
611
619
  onNone: () => noMessageView,
612
620
  onSome: rawMessage => {
613
621
  const message = unwrapIfFiltered(rawMessage, maybeSubmodelFilter);
614
- return div([Class('flex flex-col flex-1 min-h-0 min-w-0')], [
615
- div([
616
- Class('px-2 py-1 border-b text-2xs text-dt-muted font-mono shrink-0'),
622
+ return h.div([h.Class('flex flex-col flex-1 min-h-0 min-w-0')], [
623
+ h.div([
624
+ h.Class('px-2 py-1 border-b text-2xs text-dt-muted font-mono shrink-0'),
617
625
  ], [timestamp]),
618
- div([Class('flex flex-col flex-1 min-h-0 min-w-0 pt-1 pl-1')], [
626
+ h.div([h.Class('flex flex-col flex-1 min-h-0 min-w-0 pt-1 pl-1')], [
619
627
  treeView(message, 'root', expandedPaths, HashSet.empty(), HashSet.empty(), Option.none(), false),
620
628
  ]),
621
629
  ]);
@@ -653,58 +661,77 @@ const makeView = (position, mode, maybeBanner) => {
653
661
  return nodes;
654
662
  };
655
663
  const commandsTabContent = (commands, expandedPaths) => Array_.match(commands, {
656
- onEmpty: () => div([
657
- Class('flex-1 flex items-center justify-center text-dt-muted text-2xs font-mono min-w-0'),
664
+ onEmpty: () => h.div([
665
+ h.Class('flex-1 flex items-center justify-center text-dt-muted text-2xs font-mono min-w-0'),
658
666
  ], ['No Commands returned']),
659
- onNonEmpty: commandList => div([
660
- Class('flex flex-col flex-1 min-h-0 min-w-0 overflow-auto overscroll-none'),
661
- ], Array_.map(commandList, (command, index) => div([Class('flex items-start px-2 py-1 border-b gap-1.5')], [
662
- span([Class(indexClass)], [String(index + 1)]),
663
- div([Class('flex flex-col flex-1 min-w-0')], Array_.map(flattenCommand(command, index, expandedPaths), renderFlatNode)),
667
+ onNonEmpty: commandList => h.div([
668
+ h.Class('flex flex-col flex-1 min-h-0 min-w-0 overflow-auto overscroll-none'),
669
+ ], Array_.map(commandList, (command, index) => h.div([h.Class('flex items-start px-2 py-1 border-b gap-1.5')], [
670
+ h.span([h.Class(indexClass)], [String(index + 1)]),
671
+ h.div([h.Class('flex flex-col flex-1 min-w-0')], Array_.map(flattenCommand(command, index, expandedPaths), renderFlatNode)),
664
672
  ]))),
665
673
  });
666
674
  const selectedMountActivity = (model) => {
667
675
  const selectedIndex = selectedHistoryIndex(model);
668
676
  if (selectedIndex === INIT_INDEX) {
669
- return { starts: model.initMountStartNames, ends: NO_MOUNTS };
677
+ return { starts: model.initMountStarts, ends: NO_MOUNTS };
670
678
  }
671
679
  else {
672
680
  return pipe(model.entries, Array_.get(selectedIndex - model.startIndex), Option.match({
673
681
  onNone: () => ({ starts: NO_MOUNTS, ends: NO_MOUNTS }),
674
682
  onSome: entry => ({
675
- starts: entry.mountStartNames,
676
- ends: entry.mountEndNames,
683
+ starts: entry.mountStarts,
684
+ ends: entry.mountEnds,
677
685
  }),
678
686
  }));
679
687
  }
680
688
  };
681
- const mountListSection = (label, names) => div([Class('flex flex-col shrink-0')], [
682
- div([
683
- Class('px-2 py-1 border-b text-2xs text-dt-muted font-mono shrink-0'),
689
+ const flattenMount = (mount, sectionLabel, index, expandedPaths) => {
690
+ const taggedValue = Option.match(mount.args, {
691
+ onNone: () => ({ _tag: mount.name }),
692
+ onSome: argsValue => ({ ...argsValue, _tag: mount.name }),
693
+ });
694
+ const rootPath = `mount-${sectionLabel}-${index}`;
695
+ const nodes = [];
696
+ flattenTree({
697
+ value: toInspectableValue(taggedValue),
698
+ treePath: rootPath,
699
+ rootPath,
700
+ expandedPaths,
701
+ changedPaths: HashSet.empty(),
702
+ affectedPaths: HashSet.empty(),
703
+ depth: 0,
704
+ key: '',
705
+ accumulator: nodes,
706
+ indentRootChildren: false,
707
+ });
708
+ return nodes;
709
+ };
710
+ const mountListSection = (label, mounts, expandedPaths) => h.div([h.Class('flex flex-col shrink-0')], [
711
+ h.div([
712
+ h.Class('px-2 py-1 border-b text-2xs text-dt-muted font-mono shrink-0'),
684
713
  ], [label]),
685
- ...Array_.map(names, (name, index) => div([
686
- Class('flex items-center px-2 py-1 text-base font-mono text-dt border-b gap-1.5'),
687
- ], [
688
- span([Class(indexClass)], [String(index + 1)]),
689
- span([Class('json-tag')], [name]),
714
+ ...Array_.map(mounts, (mount, index) => h.div([h.Class('flex items-start px-2 py-1 border-b gap-1.5')], [
715
+ h.span([h.Class(indexClass)], [String(index + 1)]),
716
+ h.div([h.Class('flex flex-col flex-1 min-w-0')], Array_.map(flattenMount(mount, label, index, expandedPaths), renderFlatNode)),
690
717
  ])),
691
718
  ]);
692
- const mountsTabContent = (starts, ends) => {
719
+ const mountsTabContent = (starts, ends, expandedPaths) => {
693
720
  const hasAny = Array_.isReadonlyArrayNonEmpty(starts) ||
694
721
  Array_.isReadonlyArrayNonEmpty(ends);
695
722
  if (!hasAny) {
696
- return div([
697
- Class('flex-1 flex items-center justify-center text-dt-muted text-2xs font-mono min-w-0'),
723
+ return h.div([
724
+ h.Class('flex-1 flex items-center justify-center text-dt-muted text-2xs font-mono min-w-0'),
698
725
  ], ['No Mounts during this render']);
699
726
  }
700
- return div([
701
- Class('flex flex-col flex-1 min-h-0 min-w-0 overflow-auto overscroll-none'),
727
+ return h.div([
728
+ h.Class('flex flex-col flex-1 min-h-0 min-w-0 overflow-auto overscroll-none'),
702
729
  ], [
703
730
  ...(Array_.isReadonlyArrayNonEmpty(starts)
704
- ? [mountListSection('Started', starts)]
731
+ ? [mountListSection('Started', starts, expandedPaths)]
705
732
  : []),
706
733
  ...(Array_.isReadonlyArrayNonEmpty(ends)
707
- ? [mountListSection('Ended', ends)]
734
+ ? [mountListSection('Ended', ends, expandedPaths)]
708
735
  : []),
709
736
  ]);
710
737
  };
@@ -723,10 +750,14 @@ const makeView = (position, mode, maybeBanner) => {
723
750
  model.expandedPaths,
724
751
  ])), M.when('Mounts', () => {
725
752
  const { starts, ends } = selectedMountActivity(model);
726
- return lazyTabContent('Mounts', mountsTabContent, [starts, ends]);
753
+ return lazyTabContent('Mounts', mountsTabContent, [
754
+ starts,
755
+ ends,
756
+ model.expandedPaths,
757
+ ]);
727
758
  }), M.exhaustive);
728
- const inspectorPaneView = (model) => div([
729
- Class('flex flex-col border-l min-w-0 min-h-0 flex-1 dt-inspector-pane'),
759
+ const inspectorPaneView = (model) => h.div([
760
+ h.Class('flex flex-col border-l min-w-0 min-h-0 flex-1 dt-inspector-pane'),
730
761
  ], [
731
762
  Tabs.view({
732
763
  model: model.inspectorTabs,
@@ -734,14 +765,14 @@ const makeView = (position, mode, maybeBanner) => {
734
765
  tabs: INSPECTOR_TABS,
735
766
  tabListAriaLabel: 'Inspector tabs',
736
767
  persistPanels: true,
737
- attributes: [Class('flex flex-col flex-1 min-h-0')],
738
- tabListAttributes: [Class('flex border-b shrink-0')],
768
+ attributes: [h.Class('flex flex-col flex-1 min-h-0')],
769
+ tabListAttributes: [h.Class('flex border-b shrink-0')],
739
770
  tabToConfig: (tab, { isActive }) => ({
740
771
  buttonAttributes: [
741
- Class(clsx('dt-tab-button cursor-pointer text-base font-mono px-3 py-1', isActive ? 'text-dt dt-tab-active' : 'text-dt-muted')),
772
+ h.Class(clsx('dt-tab-h.button cursor-pointer text-base font-mono px-3 py-1', isActive ? 'text-dt dt-tab-active' : 'text-dt-muted')),
742
773
  ],
743
- buttonContent: span([], [tab]),
744
- panelAttributes: [Class('flex flex-col flex-1 min-h-0 min-w-0')],
774
+ buttonContent: h.span([], [tab]),
775
+ panelAttributes: [h.Class('flex flex-col flex-1 min-h-0 min-w-0')],
745
776
  panelContent: Option.match(model.maybeInspectedModel, {
746
777
  onNone: () => emptyInspectorView,
747
778
  onSome: inspectedModel => inspectorTabContent(model, tab, inspectedModel),
@@ -750,47 +781,47 @@ const makeView = (position, mode, maybeBanner) => {
750
781
  }),
751
782
  ]);
752
783
  // MESSAGE LIST
753
- const badgeView = (model) => button([
754
- Class(clsx('fixed bg-dt-bg text-dt cursor-pointer flex flex-col items-center justify-center font-mono outline-none dt-badge', BADGE_POSITION_CLASS[position], model.isPaused ? 'dt-badge-paused' : 'dt-badge-accent')),
755
- Style({ width: '22px', height: '56px', fontSize: '10px' }),
756
- OnClick(ClickedToggle()),
784
+ const badgeView = (model) => h.button([
785
+ h.Class(clsx('fixed bg-dt-bg text-dt cursor-pointer flex flex-col items-center justify-center font-mono outline-none dt-badge', BADGE_POSITION_CLASS[position], model.isPaused ? 'dt-badge-paused' : 'dt-badge-accent')),
786
+ h.Style({ width: '22px', height: '56px', fontSize: '10px' }),
787
+ h.OnClick(ClickedToggle()),
757
788
  ], [
758
789
  model.isOpen
759
- ? svg([
760
- AriaHidden(true),
761
- Xmlns('http://www.w3.org/2000/svg'),
762
- Fill('none'),
763
- ViewBox('0 0 24 24'),
764
- StrokeWidth('1.5'),
765
- Stroke('currentColor'),
766
- Style({ width: '12px', height: '12px' }),
790
+ ? h.svg([
791
+ h.AriaHidden(true),
792
+ h.Xmlns('http://www.w3.org/2000/h.svg'),
793
+ h.Fill('none'),
794
+ h.ViewBox('0 0 24 24'),
795
+ h.StrokeWidth('1.5'),
796
+ h.Stroke('currentColor'),
797
+ h.Style({ width: '12px', height: '12px' }),
767
798
  ], [
768
- path([
769
- StrokeLinecap('round'),
770
- StrokeLinejoin('round'),
771
- D('M6 18L18 6M6 6l12 12'),
799
+ h.path([
800
+ h.StrokeLinecap('round'),
801
+ h.StrokeLinejoin('round'),
802
+ h.D('M6 18L18 6M6 6l12 12'),
772
803
  ], []),
773
804
  ])
774
- : div([
775
- Class(clsx('flex flex-col items-center gap-0.5 font-semibold tracking-wider leading-none', model.isPaused ? 'text-dt-bg' : 'text-dt-muted')),
776
- ], [span([], ['D']), span([], ['E']), span([], ['V'])]),
805
+ : h.div([
806
+ h.Class(clsx('flex flex-col items-center gap-0.5 font-semibold tracking-wider leading-none', model.isPaused ? 'text-dt-bg' : 'text-dt-muted')),
807
+ ], [h.span([], ['D']), h.span([], ['E']), h.span([], ['V'])]),
777
808
  ]);
778
809
  const headerClass = 'flex items-center justify-between px-3 py-1.5 border-b shrink-0';
779
- const actionButtonClass = 'dt-resume-button bg-transparent border-none text-dt-live cursor-pointer text-base font-mono font-medium';
810
+ const actionButtonClass = 'dt-resume-h.button bg-transparent border-none text-dt-live cursor-pointer text-base font-mono font-medium';
780
811
  const statusClass = 'text-base font-mono';
781
- const clearHistoryButton = button([Class(headerButtonClass), OnClick(ClickedClear())], ['Clear history']);
812
+ const clearHistoryButton = h.button([h.Class(headerButtonClass), h.OnClick(ClickedClear())], ['Clear history']);
782
813
  const submodelLabel = (tag) => pipe(tag, String_.replace(/^Got/, ''), String_.replace(/Message$/, ''));
783
814
  const CHECK_ICON = 'M4.5 12.75l6 6 9-13.5';
784
- const checkIconView = svg([
785
- AriaHidden(true),
786
- Class('dt-filter-check shrink-0'),
787
- Xmlns('http://www.w3.org/2000/svg'),
788
- Fill('none'),
789
- ViewBox('0 0 24 24'),
790
- StrokeWidth('2'),
791
- Stroke('currentColor'),
815
+ const checkIconView = h.svg([
816
+ h.AriaHidden(true),
817
+ h.Class('dt-filter-check shrink-0'),
818
+ h.Xmlns('http://www.w3.org/2000/h.svg'),
819
+ h.Fill('none'),
820
+ h.ViewBox('0 0 24 24'),
821
+ h.StrokeWidth('2'),
822
+ h.Stroke('currentColor'),
792
823
  ], [
793
- path([D(CHECK_ICON), StrokeLinecap('round'), StrokeLinejoin('round')], []),
824
+ h.path([h.D(CHECK_ICON), h.StrokeLinecap('round'), h.StrokeLinejoin('round')], []),
794
825
  ]);
795
826
  const filterItemLabel = (item) => String_.isNonEmpty(item) ? submodelLabel(item) : 'All Messages';
796
827
  const submodelFilterView = (model) => {
@@ -805,27 +836,27 @@ const makeView = (position, mode, maybeBanner) => {
805
836
  items: [ALL_MESSAGES_VALUE, ...model.submodelTags],
806
837
  itemToConfig: item => ({
807
838
  className: 'dt-filter-item',
808
- content: div([Class('flex items-center gap-2')], [checkIconView, span([], [filterItemLabel(item)])]),
839
+ content: h.div([h.Class('flex items-center gap-2')], [checkIconView, h.span([], [filterItemLabel(item)])]),
809
840
  }),
810
- buttonContent: span([Class('flex flex-1 items-center justify-between')], [
811
- span([], [buttonLabel]),
812
- svg([
813
- AriaHidden(true),
814
- Class('json-arrow shrink-0'),
815
- Xmlns('http://www.w3.org/2000/svg'),
816
- Fill('none'),
817
- ViewBox('0 0 24 24'),
818
- StrokeWidth('2'),
819
- Stroke('currentColor'),
841
+ buttonContent: h.span([h.Class('flex flex-1 items-center justify-between')], [
842
+ h.span([], [buttonLabel]),
843
+ h.svg([
844
+ h.AriaHidden(true),
845
+ h.Class('json-arrow shrink-0'),
846
+ h.Xmlns('http://www.w3.org/2000/h.svg'),
847
+ h.Fill('none'),
848
+ h.ViewBox('0 0 24 24'),
849
+ h.StrokeWidth('2'),
850
+ h.Stroke('currentColor'),
820
851
  ], [
821
- path([
822
- D(CHEVRON_DOWN),
823
- StrokeLinecap('round'),
824
- StrokeLinejoin('round'),
852
+ h.path([
853
+ h.D(CHEVRON_DOWN),
854
+ h.StrokeLinecap('round'),
855
+ h.StrokeLinejoin('round'),
825
856
  ], []),
826
857
  ]),
827
858
  ]),
828
- buttonClassName: 'dt-filter-button',
859
+ buttonClassName: 'dt-filter-h.button',
829
860
  itemsClassName: 'dt-filter-items',
830
861
  className: 'dt-filter-wrapper',
831
862
  backdropClassName: 'dt-filter-backdrop',
@@ -834,60 +865,60 @@ const makeView = (position, mode, maybeBanner) => {
834
865
  const headerView = (model) => {
835
866
  const { status, maybeAction } = M.value(mode).pipe(M.withReturnType(), M.when('TimeTravel', () => model.isPaused
836
867
  ? {
837
- status: span([Class(`${statusClass} text-dt-paused`)], [
868
+ status: h.span([h.Class(`${statusClass} text-dt-paused`)], [
838
869
  model.pausedAtIndex === INIT_INDEX
839
870
  ? 'Paused (init)'
840
871
  : `Paused (${model.pausedAtIndex + 1})`,
841
872
  ]),
842
- maybeAction: Option.some(button([Class(actionButtonClass), OnClick(ClickedResume())], ['Resume →'])),
873
+ maybeAction: Option.some(h.button([h.Class(actionButtonClass), h.OnClick(ClickedResume())], ['Resume →'])),
843
874
  }
844
875
  : {
845
- status: span([Class(`${statusClass} text-dt-live font-medium`)], ['Live']),
876
+ status: h.span([h.Class(`${statusClass} text-dt-live font-medium`)], ['Live']),
846
877
  maybeAction: Option.none(),
847
878
  }), M.when('Inspect', () => ({
848
- status: span([Class(`${statusClass} text-dt-accent`)], [
879
+ status: h.span([h.Class(`${statusClass} text-dt-accent`)], [
849
880
  model.selectedIndex === INIT_INDEX
850
881
  ? 'Inspecting (init)'
851
882
  : `Inspecting (${model.selectedIndex + 1})`,
852
883
  ]),
853
- maybeAction: OptionExt.when(!model.isFollowingLatest, button([Class(actionButtonClass), OnClick(ClickedFollowLatest())], ['Follow Latest →'])),
884
+ maybeAction: OptionExt.when(!model.isFollowingLatest, h.button([h.Class(actionButtonClass), h.OnClick(ClickedFollowLatest())], ['Follow Latest →'])),
854
885
  })), M.exhaustive);
855
- return header([Class(headerClass)], [status, ...Option.toArray(maybeAction), clearHistoryButton]);
886
+ return h.header([h.Class(headerClass)], [status, ...Option.toArray(maybeAction), clearHistoryButton]);
856
887
  };
857
- const initRowView = (isSelected, isPausedHere) => keyed('li')('init', [
858
- Class(clsx(ROW_BASE, { selected: isSelected })),
859
- OnClick(ClickedRow({ index: INIT_INDEX })),
888
+ const initRowView = (isSelected, isPausedHere) => h.keyed('li')('init', [
889
+ h.Class(clsx(ROW_BASE, { selected: isSelected })),
890
+ h.OnClick(ClickedRow({ index: INIT_INDEX })),
860
891
  ], [
861
- ...OptionExt.when(mode === 'TimeTravel', span([Class('pause-column')], isPausedHere ? [pauseIconView] : [])).pipe(Option.toArray),
862
- span([Class('dot-column')], []),
863
- span([Class(indexClass)], []),
864
- span([Class('text-base text-dt-muted font-mono')], ['init']),
892
+ ...OptionExt.when(mode === 'TimeTravel', h.span([h.Class('pause-column')], isPausedHere ? [pauseIconView] : [])).pipe(Option.toArray),
893
+ h.span([h.Class('dot-column')], []),
894
+ h.span([h.Class(indexClass)], []),
895
+ h.span([h.Class('text-base text-dt-muted font-mono')], ['init']),
865
896
  ]);
866
- const pauseIconView = svg([
867
- AriaHidden(true),
868
- Class('dt-pause-icon'),
869
- Xmlns('http://www.w3.org/2000/svg'),
870
- Fill('none'),
871
- ViewBox('0 0 24 24'),
872
- StrokeWidth('2.5'),
873
- Stroke('currentColor'),
897
+ const pauseIconView = h.svg([
898
+ h.AriaHidden(true),
899
+ h.Class('dt-pause-icon'),
900
+ h.Xmlns('http://www.w3.org/2000/h.svg'),
901
+ h.Fill('none'),
902
+ h.ViewBox('0 0 24 24'),
903
+ h.StrokeWidth('2.5'),
904
+ h.Stroke('currentColor'),
874
905
  ], [
875
- path([
876
- StrokeLinecap('round'),
877
- StrokeLinejoin('round'),
878
- D('M5.75 3v18M18.25 3v18'),
906
+ h.path([
907
+ h.StrokeLinecap('round'),
908
+ h.StrokeLinejoin('round'),
909
+ h.D('M5.75 3v18M18.25 3v18'),
879
910
  ], []),
880
911
  ]);
881
- const messageRowView = (tag, absoluteIndex, isSelected, isPausedHere, timeDelta, isModelChanged) => keyed('li')(String(absoluteIndex), [
882
- Class(clsx(ROW_BASE, { selected: isSelected })),
883
- OnClick(ClickedRow({ index: absoluteIndex })),
912
+ const messageRowView = (tag, absoluteIndex, isSelected, isPausedHere, timeDelta, isModelChanged) => h.keyed('li')(String(absoluteIndex), [
913
+ h.Class(clsx(ROW_BASE, { selected: isSelected })),
914
+ h.OnClick(ClickedRow({ index: absoluteIndex })),
884
915
  ], [
885
- ...OptionExt.when(mode === 'TimeTravel', span([Class('pause-column')], isPausedHere ? [pauseIconView] : [])).pipe(Option.toArray),
886
- span([Class('dot-column')], isModelChanged ? [inlineDiffDotView] : []),
887
- span([Class(indexClass)], [String(absoluteIndex + 1)]),
888
- span([Class('text-base text-dt font-mono flex-1 truncate')], [tag]),
889
- span([
890
- Class('text-2xs text-dt-muted font-mono shrink-0 text-right min-w-5'),
916
+ ...OptionExt.when(mode === 'TimeTravel', h.span([h.Class('pause-column')], isPausedHere ? [pauseIconView] : [])).pipe(Option.toArray),
917
+ h.span([h.Class('dot-column')], isModelChanged ? [inlineDiffDotView] : []),
918
+ h.span([h.Class(indexClass)], [String(absoluteIndex + 1)]),
919
+ h.span([h.Class('text-base text-dt font-mono flex-1 truncate')], [tag]),
920
+ h.span([
921
+ h.Class('text-2xs text-dt-muted font-mono shrink-0 text-right min-w-5'),
891
922
  ], [formatTimeDelta(timeDelta)]),
892
923
  ]);
893
924
  const messageListBody = (entries, startIndex, selectedIndex, isPaused, pausedAtIndex, maybeFilterTag) => {
@@ -918,7 +949,7 @@ const makeView = (position, mode, maybeBanner) => {
918
949
  entry.isModelChanged,
919
950
  ]);
920
951
  }), Array_.reverse);
921
- return ul([Class('message-list flex-1 overflow-y-auto min-h-0 overscroll-none')], isFiltered
952
+ return h.ul([h.Class('message-list flex-1 overflow-y-auto min-h-0 overscroll-none')], isFiltered
922
953
  ? messageRows
923
954
  : [
924
955
  ...messageRows,
@@ -941,15 +972,15 @@ const makeView = (position, mode, maybeBanner) => {
941
972
  ]);
942
973
  };
943
974
  // PANEL
944
- const panelView = (model) => keyed('div')('dt-panel', [
945
- Class(clsx('fixed dt-panel dt-panel-wide bg-dt-bg border rounded-lg flex flex-col overflow-hidden font-mono text-dt', PANEL_POSITION_CLASS[position])),
975
+ const panelView = (model) => h.keyed('div')('dt-panel', [
976
+ h.Class(clsx('fixed dt-panel dt-panel-wide bg-dt-bg border rounded-lg flex flex-col overflow-hidden font-mono text-dt', PANEL_POSITION_CLASS[position])),
946
977
  ], [
947
- ...Option.map(maybeBanner, banner => div([
948
- Class('px-3 py-2 border-b text-sm text-dt-muted font-mono shrink-0 leading-snug'),
978
+ ...Option.map(maybeBanner, banner => h.div([
979
+ h.Class('px-3 py-2 border-b text-sm text-dt-muted font-mono shrink-0 leading-snug'),
949
980
  ], [banner])).pipe(Option.toArray),
950
981
  headerView(model),
951
- div([Class('flex flex-1 min-h-0 dt-content')], [
952
- div([Class('flex flex-col min-h-0 dt-message-pane')], [
982
+ h.div([h.Class('flex flex-1 min-h-0 dt-content')], [
983
+ h.div([h.Class('flex flex-col min-h-0 dt-message-pane')], [
953
984
  ...Array_.match(model.submodelTags, {
954
985
  onEmpty: () => [],
955
986
  onNonEmpty: () => [submodelFilterView(model)],
@@ -959,10 +990,10 @@ const makeView = (position, mode, maybeBanner) => {
959
990
  inspectorPaneView(model),
960
991
  ]),
961
992
  ]);
962
- const interactionBlocker = div([Class('dt-interaction-blocker')], []);
993
+ const interactionBlocker = h.div([h.Class('dt-interaction-blocker')], []);
963
994
  return (model) => ({
964
995
  title: 'Foldkit DevTools',
965
- body: div([], [
996
+ body: h.div([], [
966
997
  ...OptionExt.when(model.isPaused && mode === 'TimeTravel', interactionBlocker).pipe(Option.toArray),
967
998
  ...OptionExt.when(model.isOpen, panelView(model)).pipe(Option.toArray),
968
999
  badgeView(model),
@@ -970,7 +1001,6 @@ const makeView = (position, mode, maybeBanner) => {
970
1001
  });
971
1002
  };
972
1003
  // CREATE
973
- const DEVTOOLS_HOST_ID = 'foldkit-devtools';
974
1004
  const createShadowContainer = () => {
975
1005
  const existingHost = document.getElementById(DEVTOOLS_HOST_ID);
976
1006
  if (existingHost) {
@@ -978,6 +1008,14 @@ const createShadowContainer = () => {
978
1008
  }
979
1009
  const host = document.createElement('div');
980
1010
  host.id = DEVTOOLS_HOST_ID;
1011
+ host.addEventListener('pointerdown', event => {
1012
+ const activeElement = document.activeElement;
1013
+ if (activeElement !== null &&
1014
+ activeElement !== host &&
1015
+ activeElement !== document.body) {
1016
+ event.preventDefault();
1017
+ }
1018
+ }, { capture: true });
981
1019
  document.body.appendChild(host);
982
1020
  const shadow = host.attachShadow({ mode: 'open' });
983
1021
  const styleElement = document.createElement('style');