@jxsuite/ui 0.0.0 → 0.1.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.
Files changed (105) hide show
  1. package/components/jx-accordion-item.json +281 -0
  2. package/components/jx-accordion.json +33 -0
  3. package/components/jx-action-button.json +261 -33
  4. package/components/jx-action-group.json +123 -0
  5. package/components/jx-button.json +133 -25
  6. package/components/jx-checkbox.json +215 -0
  7. package/components/jx-color-area.json +263 -0
  8. package/components/jx-color-field.json +565 -0
  9. package/components/jx-color-slider.json +216 -0
  10. package/components/jx-combobox.json +544 -0
  11. package/components/jx-dialog.json +416 -0
  12. package/components/jx-divider.json +52 -0
  13. package/components/jx-dot.json +38 -0
  14. package/components/jx-field.json +162 -0
  15. package/components/jx-kbd.json +24 -0
  16. package/components/jx-listbox.json +76 -0
  17. package/components/jx-menu-item.json +5 -12
  18. package/components/jx-menu.json +45 -6
  19. package/components/jx-number-field.json +441 -0
  20. package/components/jx-option.json +350 -0
  21. package/components/jx-popover.json +227 -0
  22. package/components/jx-select.json +637 -0
  23. package/components/jx-spinner.json +129 -0
  24. package/components/jx-split.json +221 -0
  25. package/components/jx-swatch-group.json +107 -0
  26. package/components/jx-swatch.json +262 -0
  27. package/components/jx-switch.json +253 -0
  28. package/components/jx-tab-panel.json +58 -0
  29. package/components/jx-tab.json +296 -0
  30. package/components/jx-tabs.json +124 -0
  31. package/components/jx-textfield.json +639 -0
  32. package/components/jx-toast-host.json +121 -0
  33. package/components/jx-toast.json +255 -0
  34. package/components/jx-toolbar.json +81 -0
  35. package/components/jx-tooltip.json +207 -0
  36. package/components/jx-tree-item.json +325 -0
  37. package/components/jx-tree.json +202 -0
  38. package/icons/list.json +4 -0
  39. package/icons/manifest.json +12 -0
  40. package/package.json +27 -5
  41. package/project.json +164 -153
  42. package/src/behaviors/action-group.ts +255 -0
  43. package/src/behaviors/color-area.ts +303 -0
  44. package/src/behaviors/color-field.ts +465 -0
  45. package/src/behaviors/color-slider.ts +154 -0
  46. package/src/behaviors/combobox.ts +474 -0
  47. package/src/behaviors/dialog.ts +137 -0
  48. package/src/behaviors/listbox.ts +118 -0
  49. package/src/behaviors/menu.ts +80 -48
  50. package/src/behaviors/number-field.ts +161 -0
  51. package/src/behaviors/popover.ts +399 -0
  52. package/src/behaviors/select.ts +154 -0
  53. package/src/behaviors/split.ts +478 -0
  54. package/src/behaviors/swatch-group.ts +297 -0
  55. package/src/behaviors/tabs.ts +319 -0
  56. package/src/behaviors/textfield.ts +164 -0
  57. package/src/behaviors/toast-host.ts +241 -0
  58. package/src/behaviors/toast.ts +261 -0
  59. package/src/behaviors/toolbar.ts +413 -0
  60. package/src/behaviors/tooltip.ts +715 -0
  61. package/src/behaviors/tree.ts +830 -0
  62. package/src/color.ts +433 -0
  63. package/src/documents.ts +91 -1
  64. package/src/index.ts +41 -1
  65. package/src/loaders.ts +47 -0
  66. package/src/testing/anchor-support.ts +28 -0
  67. package/src/testing/popover-shim.ts +39 -6
  68. package/src/theme.ts +30 -6
  69. package/stylebook/jx-accordion-item.json +195 -0
  70. package/stylebook/jx-accordion.json +220 -0
  71. package/stylebook/jx-action-button.json +191 -23
  72. package/stylebook/jx-action-group.json +335 -0
  73. package/stylebook/jx-button.json +129 -26
  74. package/stylebook/jx-checkbox.json +245 -0
  75. package/stylebook/jx-color-area.json +272 -0
  76. package/stylebook/jx-color-field.json +261 -0
  77. package/stylebook/jx-color-slider.json +220 -0
  78. package/stylebook/jx-combobox.json +332 -0
  79. package/stylebook/jx-dialog.json +186 -0
  80. package/stylebook/jx-divider.json +125 -0
  81. package/stylebook/jx-dot.json +92 -0
  82. package/stylebook/jx-field.json +313 -0
  83. package/stylebook/jx-icon.json +4 -0
  84. package/stylebook/jx-kbd.json +90 -0
  85. package/stylebook/jx-listbox.json +337 -0
  86. package/stylebook/jx-menu.json +0 -1
  87. package/stylebook/jx-number-field.json +203 -0
  88. package/stylebook/jx-option.json +285 -0
  89. package/stylebook/jx-popover.json +123 -0
  90. package/stylebook/jx-select.json +407 -0
  91. package/stylebook/jx-spinner.json +111 -0
  92. package/stylebook/jx-split.json +170 -0
  93. package/stylebook/jx-swatch-group.json +175 -0
  94. package/stylebook/jx-swatch.json +249 -0
  95. package/stylebook/jx-switch.json +240 -0
  96. package/stylebook/jx-tab-panel.json +147 -0
  97. package/stylebook/jx-tab.json +201 -0
  98. package/stylebook/jx-tabs.json +278 -0
  99. package/stylebook/jx-textfield.json +309 -0
  100. package/stylebook/jx-toast-host.json +157 -0
  101. package/stylebook/jx-toast.json +266 -0
  102. package/stylebook/jx-toolbar.json +435 -0
  103. package/stylebook/jx-tooltip.json +162 -0
  104. package/stylebook/jx-tree-item.json +258 -0
  105. package/stylebook/jx-tree.json +429 -0
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Pretend the engine does, or does not, position by anchor — for a test that drives one of the two
3
+ * placements the kit's overlays have (specs/ui.md §6).
4
+ *
5
+ * The kit asks `CSS.supports("position-area", "block-end")` (`supportsAnchorPositioning` in
6
+ * `behaviors/popover.ts`) to decide whether the platform is placing a panel or the clamp is, and a
7
+ * DOM shim answers that question however it likes — happy-dom says yes to everything — so a test
8
+ * that means one path or the other has to say which. Redefinition rather than assignment, because
9
+ * the shim exposes `CSS` through a read-only accessor.
10
+ *
11
+ * @param answer What `CSS.supports` should say.
12
+ * @returns The undo, to run in `finally`.
13
+ */
14
+ export function withAnchorSupport(answer: boolean): () => void {
15
+ const before = Object.getOwnPropertyDescriptor(globalThis, "CSS");
16
+ Object.defineProperty(globalThis, "CSS", {
17
+ configurable: true,
18
+ value: { supports: () => answer },
19
+ });
20
+ return () => {
21
+ /* Put back what was there — or, on an engine that had no `CSS` at all, nothing. */
22
+ Object.defineProperty(
23
+ globalThis,
24
+ "CSS",
25
+ before ?? { configurable: true, value: undefined, writable: true },
26
+ );
27
+ };
28
+ }
@@ -25,16 +25,39 @@ declare global {
25
25
  }
26
26
  }
27
27
 
28
- function toggleEvent(type: "beforetoggle" | "toggle", oldState: string, newState: string): Event {
28
+ /**
29
+ * A ToggleEvent, as the platform dispatches one: `oldState`, `newState`, and — for an opening shown
30
+ * from a source — `source`, which Chrome 152 carries on both `beforetoggle` and `toggle` and which
31
+ * the kit reads to know what a panel is anchored to.
32
+ */
33
+ function toggleEvent(
34
+ type: "beforetoggle" | "toggle",
35
+ oldState: string,
36
+ newState: string,
37
+ source?: Element | null,
38
+ ): Event {
29
39
  const event = new Event(type, { bubbles: false, cancelable: type === "beforetoggle" });
30
- Object.assign(event, { newState, oldState });
40
+ Object.assign(event, { newState, oldState, ...(source ? { source } : {}) });
31
41
  return event;
32
42
  }
33
43
 
44
+ /**
45
+ * Whether the element light-dismisses and answers Escape: `auto` and `hint` both do, `manual` does
46
+ * not.
47
+ */
34
48
  function isAuto(el: HTMLElement): boolean {
35
49
  return el.getAttribute("popover") !== "manual";
36
50
  }
37
51
 
52
+ /** The popover's mode, defaulting the way an invalid value does — to `manual`. */
53
+ function modeOf(el: HTMLElement): "auto" | "hint" | "manual" {
54
+ const raw = el.getAttribute("popover");
55
+ if (raw === "hint") {
56
+ return "hint";
57
+ }
58
+ return raw === "auto" || raw === "" ? "auto" : "manual";
59
+ }
60
+
38
61
  export function installPopoverShim(): void {
39
62
  const proto = HTMLElement.prototype as HTMLElement & {
40
63
  showPopover?: (options?: { source?: Element }) => void;
@@ -79,18 +102,28 @@ export function installPopoverShim(): void {
79
102
  } else {
80
103
  state.invokers.delete(this);
81
104
  }
82
- if (isAuto(this)) {
105
+ /* Showing an `auto` closes every other `auto` and every `hint`; showing a `hint` closes only
106
+ other hints. That asymmetry is the whole reason `hint` exists — a tip can sit over an open
107
+ menu explaining a row — and it is measured, not assumed: with an auto popover open, Chrome
108
+ 152 accepts `hint.showPopover()` and leaves the auto one open. Modelling hint as auto here
109
+ made a correct element look broken, so the one assertion that would have caught a
110
+ regression could not be written. */
111
+ const mode = modeOf(this);
112
+ if (mode !== "manual") {
113
+ const closes = mode === "auto" ? ["auto", "hint"] : ["hint"];
114
+ // A copy, because `hide` deletes from the set being walked.
83
115
  const showing = [...state.open];
84
116
  for (const other of showing) {
85
- if (isAuto(other) && !other.contains(this)) {
117
+ if (closes.includes(modeOf(other)) && !other.contains(this)) {
86
118
  hide(other);
87
119
  }
88
120
  }
89
121
  }
90
- this.dispatchEvent(toggleEvent("beforetoggle", "closed", "open"));
122
+ const source = options?.source ?? null;
123
+ this.dispatchEvent(toggleEvent("beforetoggle", "closed", "open", source));
91
124
  state.open.add(this);
92
125
  this.dataset.popoverOpen = "";
93
- queueMicrotask(() => this.dispatchEvent(toggleEvent("toggle", "closed", "open")));
126
+ queueMicrotask(() => this.dispatchEvent(toggleEvent("toggle", "closed", "open", source)));
94
127
  };
95
128
  proto.hidePopover = function hidePopover(this: HTMLElement): void {
96
129
  if (!this.hasAttribute("popover")) {
package/src/theme.ts CHANGED
@@ -16,16 +16,40 @@ import project from "../project.json";
16
16
  /** The cascade layer every kit stylesheet lives in. */
17
17
  export const THEME_LAYER = "jx-ui";
18
18
 
19
- /** The token block as authored: `project.json`'s `style`. */
20
- export const themeTokens = project.style as JxStyle;
19
+ /** The `style` block as authored: one key, the cascade layer, holding every declaration. */
20
+ const themeStyle = project.style as JxStyle;
21
21
 
22
- /** The theme as CSS text: every token on `:root`, inside `@layer jx-ui`. */
22
+ /**
23
+ * The tokens themselves, from inside the layer.
24
+ *
25
+ * Every caller wants the declarations, never the wrapper — so this names them and `themeStyle`
26
+ * stays private. Pointing it at the raw block instead would hand each caller one key called `@layer
27
+ * jx-ui` and let it look up a token that is one level down, which returns `undefined` rather than
28
+ * failing.
29
+ */
30
+ export const themeTokens = (themeStyle[`@layer ${THEME_LAYER}`] ?? {}) as JxStyle;
31
+
32
+ /**
33
+ * The theme as CSS text: every token on `:root`, inside `@layer jx-ui`.
34
+ *
35
+ * Not one character of this is written here. The layer used to be a template literal wrapped around
36
+ * the builder's output, which made this module a THIRD reader of the same `style` block — beside
37
+ * the runtime and the site builder — and the three could disagree about what the block meant. The
38
+ * layer is now an authored `"@layer jx-ui"` key, so the builder emits it like any other at-rule and
39
+ * this function is the builder and nothing else.
40
+ */
23
41
  export function themeCSS(): string {
24
- const rules = buildStyleRules(themeTokens, { scope: ":root" }).map((rule) => rule.text);
25
- return `@layer ${THEME_LAYER} {\n${rules.join("\n")}\n}\n`;
42
+ return `${buildStyleRules(themeStyle, { scope: ":root" })
43
+ .map((rule) => rule.text)
44
+ .join("\n")}\n`;
26
45
  }
27
46
 
28
- /** The custom-property names the theme declares at the root, in declaration order. */
47
+ /**
48
+ * The custom-property names the theme declares at the root, in declaration order.
49
+ *
50
+ * Read from inside the layer, like every other token access — a filter over the raw `style` block
51
+ * would return an empty array rather than fail, which is the shape of silence this slice is about.
52
+ */
29
53
  export function themeTokenNames(): string[] {
30
54
  return Object.keys(themeTokens).filter((key) => key.startsWith("--"));
31
55
  }
@@ -0,0 +1,195 @@
1
+ {
2
+ "$schema": "https://jxsuite.com/schema/v1",
3
+ "$id": "StylebookAccordionItem",
4
+ "tagName": "main",
5
+ "$elements": [
6
+ {
7
+ "$ref": "../components/jx-icon.json"
8
+ },
9
+ {
10
+ "$ref": "../components/jx-spinner.json"
11
+ },
12
+ {
13
+ "$ref": "../components/jx-action-button.json"
14
+ },
15
+ {
16
+ "$ref": "../components/jx-accordion-item.json"
17
+ },
18
+ {
19
+ "$ref": "../components/jx-accordion.json"
20
+ },
21
+ {
22
+ "$ref": "../components/jx-dot.json"
23
+ }
24
+ ],
25
+ "state": {
26
+ "last": "nothing yet",
27
+ "note": {
28
+ "$prototype": "Function",
29
+ "body": [
30
+ {
31
+ "operator": "=",
32
+ "target": {
33
+ "$ref": "#/state/last"
34
+ },
35
+ "value": {
36
+ "$ref": "event#/detail"
37
+ }
38
+ }
39
+ ]
40
+ }
41
+ },
42
+ "style": {
43
+ "fontFamily": "var(--jx-font-sans)",
44
+ "fontSize": "var(--jx-text-md)",
45
+ "color": "var(--jx-fg)",
46
+ "background": "var(--jx-bg)",
47
+ "padding": "var(--jx-space-6)",
48
+ "minHeight": "100vh",
49
+ "& > h1": {
50
+ "fontSize": "var(--jx-text-lg)",
51
+ "fontWeight": "600",
52
+ "margin": "0 0 var(--jx-space-5)"
53
+ },
54
+ "& > h2": {
55
+ "fontSize": "var(--jx-text-md)",
56
+ "fontWeight": "600",
57
+ "color": "var(--jx-fg-dim)",
58
+ "margin": "var(--jx-space-5) 0 var(--jx-space-2)"
59
+ },
60
+ "& > p": {
61
+ "color": "var(--jx-fg-dim)",
62
+ "margin": "0 0 var(--jx-space-5)"
63
+ },
64
+ "& jx-accordion": {
65
+ "maxWidth": "420px",
66
+ "border": "1px solid var(--jx-border)",
67
+ "borderRadius": "var(--jx-radius-md)",
68
+ "background": "var(--jx-bg-panel)"
69
+ }
70
+ },
71
+ "children": [
72
+ {
73
+ "tagName": "h1",
74
+ "textContent": "jx-accordion-item"
75
+ },
76
+ {
77
+ "tagName": "p",
78
+ "textContent": "A native details and summary pair made one addressable thing: the platform owns the toggle, Enter and Space, find-in-page reveal and print, and the element adds a label, a caret, an actions row that stays visible while the section is shut, and an open a host can both write and read back. The stack around it is jx-accordion, which draws the seam between one section and the next. Last toggle said: ${state.last}."
79
+ },
80
+ {
81
+ "tagName": "h2",
82
+ "textContent": "A panel of sections"
83
+ },
84
+ {
85
+ "tagName": "jx-accordion",
86
+ "children": [
87
+ {
88
+ "tagName": "jx-accordion-item",
89
+ "$props": {
90
+ "label": "Layout",
91
+ "open": true
92
+ },
93
+ "ontoggle": {
94
+ "$ref": "#/state/note"
95
+ },
96
+ "children": [
97
+ {
98
+ "tagName": "p",
99
+ "textContent": "Display, position and the box model."
100
+ }
101
+ ]
102
+ },
103
+ {
104
+ "tagName": "jx-accordion-item",
105
+ "$props": {
106
+ "label": "Typography"
107
+ },
108
+ "ontoggle": {
109
+ "$ref": "#/state/note"
110
+ },
111
+ "children": [
112
+ {
113
+ "tagName": "jx-dot",
114
+ "attributes": {
115
+ "slot": "heading",
116
+ "aria-hidden": "true"
117
+ }
118
+ },
119
+ {
120
+ "tagName": "jx-action-button",
121
+ "attributes": {
122
+ "slot": "actions"
123
+ },
124
+ "$props": {
125
+ "label": "Clear typography overrides",
126
+ "hint": "Clear typography overrides",
127
+ "icon": "arrow-u-up-left",
128
+ "size": "sm"
129
+ }
130
+ },
131
+ {
132
+ "tagName": "p",
133
+ "textContent": "The interactive control in the actions row does not toggle the section, and it is still there while the section is closed."
134
+ }
135
+ ]
136
+ },
137
+ {
138
+ "tagName": "jx-accordion-item",
139
+ "$props": {
140
+ "label": "Custom",
141
+ "level": "3"
142
+ },
143
+ "ontoggle": {
144
+ "$ref": "#/state/note"
145
+ },
146
+ "children": [
147
+ {
148
+ "tagName": "p",
149
+ "textContent": "This one opts in to heading semantics with level=\"3\", so it is reachable by heading navigation."
150
+ }
151
+ ]
152
+ }
153
+ ]
154
+ },
155
+ {
156
+ "tagName": "h2",
157
+ "textContent": "One at a time"
158
+ },
159
+ {
160
+ "tagName": "jx-accordion",
161
+ "$props": {
162
+ "multiple": false
163
+ },
164
+ "children": [
165
+ {
166
+ "tagName": "jx-accordion-item",
167
+ "$props": {
168
+ "label": "First",
169
+ "name": "stylebook-exclusive",
170
+ "open": true
171
+ },
172
+ "children": [
173
+ {
174
+ "tagName": "p",
175
+ "textContent": "Every item here carries the same name, so the platform closes the others when one opens."
176
+ }
177
+ ]
178
+ },
179
+ {
180
+ "tagName": "jx-accordion-item",
181
+ "$props": {
182
+ "label": "Second",
183
+ "name": "stylebook-exclusive"
184
+ },
185
+ "children": [
186
+ {
187
+ "tagName": "p",
188
+ "textContent": "No shipped Studio panel wants this — all six keep several sections open at once — but the platform gives it for one forwarded attribute."
189
+ }
190
+ ]
191
+ }
192
+ ]
193
+ }
194
+ ]
195
+ }
@@ -0,0 +1,220 @@
1
+ {
2
+ "$schema": "https://jxsuite.com/schema/v1",
3
+ "$id": "StylebookAccordion",
4
+ "tagName": "main",
5
+ "$elements": [
6
+ {
7
+ "$ref": "../components/jx-icon.json"
8
+ },
9
+ {
10
+ "$ref": "../components/jx-accordion-item.json"
11
+ },
12
+ {
13
+ "$ref": "../components/jx-accordion.json"
14
+ }
15
+ ],
16
+ "state": {
17
+ "last": "nothing yet",
18
+ "note": {
19
+ "$prototype": "Function",
20
+ "body": [
21
+ {
22
+ "operator": "=",
23
+ "target": {
24
+ "$ref": "#/state/last"
25
+ },
26
+ "value": {
27
+ "$ref": "event#/detail"
28
+ }
29
+ }
30
+ ]
31
+ }
32
+ },
33
+ "style": {
34
+ "fontFamily": "var(--jx-font-sans)",
35
+ "fontSize": "var(--jx-text-md)",
36
+ "color": "var(--jx-fg)",
37
+ "background": "var(--jx-bg)",
38
+ "padding": "var(--jx-space-6)",
39
+ "minHeight": "100vh",
40
+ "& > h1": {
41
+ "fontSize": "var(--jx-text-lg)",
42
+ "fontWeight": "600",
43
+ "margin": "0 0 var(--jx-space-5)"
44
+ },
45
+ "& > h2": {
46
+ "fontSize": "var(--jx-text-md)",
47
+ "fontWeight": "600",
48
+ "color": "var(--jx-fg-dim)",
49
+ "margin": "var(--jx-space-5) 0 var(--jx-space-2)"
50
+ },
51
+ "& > p": {
52
+ "color": "var(--jx-fg-dim)",
53
+ "margin": "0 0 var(--jx-space-5)"
54
+ }
55
+ },
56
+ "children": [
57
+ {
58
+ "tagName": "h1",
59
+ "textContent": "jx-accordion"
60
+ },
61
+ {
62
+ "tagName": "p",
63
+ "textContent": "The stack a panel of sections stands in: a column that clips its children's width and draws one hairline BETWEEN them, so a rule falls in every seam and never above the first VISIBLE section. The first stack below opens with a section a panel has bound away, and no hairline is drawn across the top of what is left. The rule is the container's because it is a rule about a child's POSITION among its siblings, which no section can see about itself. Last toggle said: ${state.last}."
64
+ },
65
+ {
66
+ "tagName": "h2",
67
+ "textContent": "A panel of sections"
68
+ },
69
+ {
70
+ "tagName": "jx-accordion",
71
+ "style": {
72
+ "maxWidth": "420px",
73
+ "border": "1px solid var(--jx-border)",
74
+ "borderRadius": "var(--jx-radius-md)",
75
+ "background": "var(--jx-bg-panel)"
76
+ },
77
+ "children": [
78
+ {
79
+ "tagName": "jx-accordion-item",
80
+ "attributes": {
81
+ "hidden": ""
82
+ },
83
+ "$props": {
84
+ "label": "Bound away"
85
+ },
86
+ "children": [
87
+ {
88
+ "tagName": "p",
89
+ "textContent": "A panel that binds hidden on a section is why the seam asks for the first VISIBLE child rather than the first child. Unhide this in the inspector and the hairline moves to the section below it."
90
+ }
91
+ ]
92
+ },
93
+ {
94
+ "tagName": "jx-accordion-item",
95
+ "$props": {
96
+ "label": "Layout",
97
+ "open": true
98
+ },
99
+ "ontoggle": {
100
+ "$ref": "#/state/note"
101
+ },
102
+ "children": [
103
+ {
104
+ "tagName": "p",
105
+ "textContent": "Display, position and the box model. The panel's frame is written on the usage site rather than in the element, because a border and a background are this page's idea of a panel and not the stack's."
106
+ }
107
+ ]
108
+ },
109
+ {
110
+ "tagName": "jx-accordion-item",
111
+ "$props": {
112
+ "label": "Typography"
113
+ },
114
+ "ontoggle": {
115
+ "$ref": "#/state/note"
116
+ },
117
+ "children": [
118
+ {
119
+ "tagName": "p",
120
+ "textContent": "The hairline above this section is the container's rule, and the first section a reader sees has none — the hidden one above it does not count."
121
+ }
122
+ ]
123
+ },
124
+ {
125
+ "tagName": "jx-accordion-item",
126
+ "$props": {
127
+ "label": "Nesting"
128
+ },
129
+ "ontoggle": {
130
+ "$ref": "#/state/note"
131
+ },
132
+ "children": [
133
+ {
134
+ "tagName": "p",
135
+ "textContent": "A stack inside a section keeps its own seams, because the rule reaches direct children only."
136
+ },
137
+ {
138
+ "tagName": "jx-accordion",
139
+ "children": [
140
+ {
141
+ "tagName": "jx-accordion-item",
142
+ "$props": {
143
+ "label": "Inner first"
144
+ },
145
+ "children": [
146
+ {
147
+ "tagName": "p",
148
+ "textContent": "No rule above this one either."
149
+ }
150
+ ]
151
+ },
152
+ {
153
+ "tagName": "jx-accordion-item",
154
+ "$props": {
155
+ "label": "Inner second"
156
+ },
157
+ "children": [
158
+ {
159
+ "tagName": "p",
160
+ "textContent": "One seam, in the inner stack."
161
+ }
162
+ ]
163
+ }
164
+ ]
165
+ }
166
+ ]
167
+ }
168
+ ]
169
+ },
170
+ {
171
+ "tagName": "h2",
172
+ "textContent": "One at a time"
173
+ },
174
+ {
175
+ "tagName": "p",
176
+ "textContent": "multiple=\"false\" is the container writing down what the panel is for; the platform's own switch is name on each section, and only the section may carry it."
177
+ },
178
+ {
179
+ "tagName": "jx-accordion",
180
+ "$props": {
181
+ "multiple": false
182
+ },
183
+ "style": {
184
+ "maxWidth": "420px",
185
+ "border": "1px solid var(--jx-border)",
186
+ "borderRadius": "var(--jx-radius-md)",
187
+ "background": "var(--jx-bg-panel)"
188
+ },
189
+ "children": [
190
+ {
191
+ "tagName": "jx-accordion-item",
192
+ "$props": {
193
+ "label": "First",
194
+ "name": "stylebook-exclusive",
195
+ "open": true
196
+ },
197
+ "children": [
198
+ {
199
+ "tagName": "p",
200
+ "textContent": "Every item here carries the same name, so the platform closes the others when one opens."
201
+ }
202
+ ]
203
+ },
204
+ {
205
+ "tagName": "jx-accordion-item",
206
+ "$props": {
207
+ "label": "Second",
208
+ "name": "stylebook-exclusive"
209
+ },
210
+ "children": [
211
+ {
212
+ "tagName": "p",
213
+ "textContent": "No shipped Studio panel wants this — all six keep several sections open at once — but the platform gives it for one forwarded attribute."
214
+ }
215
+ ]
216
+ }
217
+ ]
218
+ }
219
+ ]
220
+ }