@aglyn/plugins-mui 1.0.0-beta.143

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 (177) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +22 -0
  3. package/package.json +47 -0
  4. package/src/index.d.ts +21 -0
  5. package/src/index.js +22 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/accordion.d.ts +96 -0
  8. package/src/lib/components/accordion.js +392 -0
  9. package/src/lib/components/accordion.js.map +1 -0
  10. package/src/lib/components/app-bar.d.ts +32 -0
  11. package/src/lib/components/app-bar.js +86 -0
  12. package/src/lib/components/app-bar.js.map +1 -0
  13. package/src/lib/components/blocks.d.ts +46 -0
  14. package/src/lib/components/blocks.js +1007 -0
  15. package/src/lib/components/blocks.js.map +1 -0
  16. package/src/lib/components/box.d.ts +47 -0
  17. package/src/lib/components/box.js +125 -0
  18. package/src/lib/components/box.js.map +1 -0
  19. package/src/lib/components/breadcrumbs.d.ts +44 -0
  20. package/src/lib/components/breadcrumbs.js +172 -0
  21. package/src/lib/components/breadcrumbs.js.map +1 -0
  22. package/src/lib/components/button.d.ts +89 -0
  23. package/src/lib/components/button.js +272 -0
  24. package/src/lib/components/button.js.map +1 -0
  25. package/src/lib/components/card.d.ts +61 -0
  26. package/src/lib/components/card.js +336 -0
  27. package/src/lib/components/card.js.map +1 -0
  28. package/src/lib/components/collection.d.ts +514 -0
  29. package/src/lib/components/collection.js +2999 -0
  30. package/src/lib/components/collection.js.map +1 -0
  31. package/src/lib/components/container.d.ts +42 -0
  32. package/src/lib/components/container.js +169 -0
  33. package/src/lib/components/container.js.map +1 -0
  34. package/src/lib/components/custom-html.d.ts +78 -0
  35. package/src/lib/components/custom-html.js +219 -0
  36. package/src/lib/components/custom-html.js.map +1 -0
  37. package/src/lib/components/data-table.d.ts +57 -0
  38. package/src/lib/components/data-table.js +216 -0
  39. package/src/lib/components/data-table.js.map +1 -0
  40. package/src/lib/components/document-root.d.ts +77 -0
  41. package/src/lib/components/document-root.js +118 -0
  42. package/src/lib/components/document-root.js.map +1 -0
  43. package/src/lib/components/drawer.d.ts +93 -0
  44. package/src/lib/components/drawer.js +566 -0
  45. package/src/lib/components/drawer.js.map +1 -0
  46. package/src/lib/components/function-controls.d.ts +66 -0
  47. package/src/lib/components/function-controls.js +207 -0
  48. package/src/lib/components/function-controls.js.map +1 -0
  49. package/src/lib/components/function-scope.d.ts +110 -0
  50. package/src/lib/components/function-scope.js +478 -0
  51. package/src/lib/components/function-scope.js.map +1 -0
  52. package/src/lib/components/function-widget.d.ts +75 -0
  53. package/src/lib/components/function-widget.js +307 -0
  54. package/src/lib/components/function-widget.js.map +1 -0
  55. package/src/lib/components/grid.d.ts +91 -0
  56. package/src/lib/components/grid.js +322 -0
  57. package/src/lib/components/grid.js.map +1 -0
  58. package/src/lib/components/icon.d.ts +54 -0
  59. package/src/lib/components/icon.js +163 -0
  60. package/src/lib/components/icon.js.map +1 -0
  61. package/src/lib/components/image-list.d.ts +58 -0
  62. package/src/lib/components/image-list.js +336 -0
  63. package/src/lib/components/image-list.js.map +1 -0
  64. package/src/lib/components/image.d.ts +154 -0
  65. package/src/lib/components/image.js +451 -0
  66. package/src/lib/components/image.js.map +1 -0
  67. package/src/lib/components/inline-text.d.ts +74 -0
  68. package/src/lib/components/inline-text.js +407 -0
  69. package/src/lib/components/inline-text.js.map +1 -0
  70. package/src/lib/components/language-switcher.d.ts +35 -0
  71. package/src/lib/components/language-switcher.js +179 -0
  72. package/src/lib/components/language-switcher.js.map +1 -0
  73. package/src/lib/components/layout-slot.d.ts +68 -0
  74. package/src/lib/components/layout-slot.js +192 -0
  75. package/src/lib/components/layout-slot.js.map +1 -0
  76. package/src/lib/components/link-box-accessible-name.d.ts +79 -0
  77. package/src/lib/components/link-box-accessible-name.js +158 -0
  78. package/src/lib/components/link-box-accessible-name.js.map +1 -0
  79. package/src/lib/components/link-box.d.ts +66 -0
  80. package/src/lib/components/link-box.js +212 -0
  81. package/src/lib/components/link-box.js.map +1 -0
  82. package/src/lib/components/list-item-text.d.ts +22 -0
  83. package/src/lib/components/list-item-text.js +86 -0
  84. package/src/lib/components/list-item-text.js.map +1 -0
  85. package/src/lib/components/list-item.d.ts +32 -0
  86. package/src/lib/components/list-item.js +127 -0
  87. package/src/lib/components/list-item.js.map +1 -0
  88. package/src/lib/components/list.d.ts +34 -0
  89. package/src/lib/components/list.js +108 -0
  90. package/src/lib/components/list.js.map +1 -0
  91. package/src/lib/components/markdown.d.ts +129 -0
  92. package/src/lib/components/markdown.js +853 -0
  93. package/src/lib/components/markdown.js.map +1 -0
  94. package/src/lib/components/nav-menu.d.ts +68 -0
  95. package/src/lib/components/nav-menu.js +683 -0
  96. package/src/lib/components/nav-menu.js.map +1 -0
  97. package/src/lib/components/pagination.d.ts +59 -0
  98. package/src/lib/components/pagination.js +243 -0
  99. package/src/lib/components/pagination.js.map +1 -0
  100. package/src/lib/components/paper.d.ts +47 -0
  101. package/src/lib/components/paper.js +202 -0
  102. package/src/lib/components/paper.js.map +1 -0
  103. package/src/lib/components/plugin-frame.d.ts +64 -0
  104. package/src/lib/components/plugin-frame.js +335 -0
  105. package/src/lib/components/plugin-frame.js.map +1 -0
  106. package/src/lib/components/plugin.d.ts +95 -0
  107. package/src/lib/components/plugin.js +304 -0
  108. package/src/lib/components/plugin.js.map +1 -0
  109. package/src/lib/components/product.d.ts +40 -0
  110. package/src/lib/components/product.js +287 -0
  111. package/src/lib/components/product.js.map +1 -0
  112. package/src/lib/components/reusable-instance.d.ts +57 -0
  113. package/src/lib/components/reusable-instance.js +126 -0
  114. package/src/lib/components/reusable-instance.js.map +1 -0
  115. package/src/lib/components/screen-link.d.ts +85 -0
  116. package/src/lib/components/screen-link.js +324 -0
  117. package/src/lib/components/screen-link.js.map +1 -0
  118. package/src/lib/components/search-box.d.ts +41 -0
  119. package/src/lib/components/search-box.js +125 -0
  120. package/src/lib/components/search-box.js.map +1 -0
  121. package/src/lib/components/section.d.ts +56 -0
  122. package/src/lib/components/section.js +132 -0
  123. package/src/lib/components/section.js.map +1 -0
  124. package/src/lib/components/stack.d.ts +55 -0
  125. package/src/lib/components/stack.js +309 -0
  126. package/src/lib/components/stack.js.map +1 -0
  127. package/src/lib/components/tabs.d.ts +288 -0
  128. package/src/lib/components/tabs.js +788 -0
  129. package/src/lib/components/tabs.js.map +1 -0
  130. package/src/lib/components/theme-mode-switcher.d.ts +38 -0
  131. package/src/lib/components/theme-mode-switcher.js +168 -0
  132. package/src/lib/components/theme-mode-switcher.js.map +1 -0
  133. package/src/lib/components/toolbar.d.ts +37 -0
  134. package/src/lib/components/toolbar.js +117 -0
  135. package/src/lib/components/toolbar.js.map +1 -0
  136. package/src/lib/components/typography.d.ts +33 -0
  137. package/src/lib/components/typography.js +433 -0
  138. package/src/lib/components/typography.js.map +1 -0
  139. package/src/lib/components/video-lightbox-controls.d.ts +131 -0
  140. package/src/lib/components/video-lightbox-controls.js +536 -0
  141. package/src/lib/components/video-lightbox-controls.js.map +1 -0
  142. package/src/lib/components/video-lightbox.d.ts +60 -0
  143. package/src/lib/components/video-lightbox.js +247 -0
  144. package/src/lib/components/video-lightbox.js.map +1 -0
  145. package/src/lib/components/video-playback-beacon.d.ts +80 -0
  146. package/src/lib/components/video-playback-beacon.js +128 -0
  147. package/src/lib/components/video-playback-beacon.js.map +1 -0
  148. package/src/lib/components/video-player-frame.d.ts +58 -0
  149. package/src/lib/components/video-player-frame.js +51 -0
  150. package/src/lib/components/video-player-frame.js.map +1 -0
  151. package/src/lib/components/video.d.ts +220 -0
  152. package/src/lib/components/video.js +874 -0
  153. package/src/lib/components/video.js.map +1 -0
  154. package/src/lib/constants/bundle-common.d.ts +17 -0
  155. package/src/lib/constants/bundle-common.js +18 -0
  156. package/src/lib/constants/bundle-common.js.map +1 -0
  157. package/src/lib/constants/field-presets.d.ts +93 -0
  158. package/src/lib/constants/field-presets.js +248 -0
  159. package/src/lib/constants/field-presets.js.map +1 -0
  160. package/src/lib/plugin.d.ts +80 -0
  161. package/src/lib/plugin.js +599 -0
  162. package/src/lib/plugin.js.map +1 -0
  163. package/src/lib/utils/author-html-label.d.ts +73 -0
  164. package/src/lib/utils/author-html-label.js +89 -0
  165. package/src/lib/utils/author-html-label.js.map +1 -0
  166. package/src/lib/utils/drop-cleared-props.d.ts +18 -0
  167. package/src/lib/utils/drop-cleared-props.js +74 -0
  168. package/src/lib/utils/drop-cleared-props.js.map +1 -0
  169. package/src/lib/utils/element-picker.d.ts +99 -0
  170. package/src/lib/utils/element-picker.js +140 -0
  171. package/src/lib/utils/element-picker.js.map +1 -0
  172. package/src/lib/utils/generate-preset-id.d.ts +19 -0
  173. package/src/lib/utils/generate-preset-id.js +25 -0
  174. package/src/lib/utils/generate-preset-id.js.map +1 -0
  175. package/src/lib/utils/link-target-props.d.ts +53 -0
  176. package/src/lib/utils/link-target-props.js +53 -0
  177. package/src/lib/utils/link-target-props.js.map +1 -0
@@ -0,0 +1,788 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
3
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
+ /**
5
+ * @license
6
+ * Copyright 2026 Aglyn LLC
7
+ *
8
+ * Licensed under the Apache License, Version 2.0 (the "License");
9
+ * you may not use this file except in compliance with the License.
10
+ * You may obtain a copy of the License at
11
+ *
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+ *
14
+ * Unless required by applicable law or agreed to in writing, software
15
+ * distributed under the License is distributed on an "AS IS" BASIS,
16
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ * See the License for the specific language governing permissions and
18
+ * limitations under the License.
19
+ */ import * as Aglyn from "@aglyn/aglyn";
20
+ import { mdiTab, mdiViewCarousel } from "@aglyn/shared-data-mdi";
21
+ import { AppLink } from "@aglyn/shared-ui-jsx";
22
+ import Box from "@mui/material/Box";
23
+ import MuiTab from "@mui/material/Tab";
24
+ import MuiTabs from "@mui/material/Tabs";
25
+ import Typography from "@mui/material/Typography";
26
+ import { createContext, forwardRef, useContext, useState } from "react";
27
+ import { BUNDLE_ID } from "../constants/bundle-common.js";
28
+ import { dropClearedProps } from "../utils/drop-cleared-props.js";
29
+ import { generatePresetId } from "../utils/generate-preset-id.js";
30
+ // Component ids are persisted in screen documents; never rename.
31
+ export const TABS_ID = 'muiTabs';
32
+ export const TAB_PANEL_ID = 'muiTabPanel';
33
+ /**
34
+ * How many tabs in a strip can carry a screen link (AGL-1312). The strip is
35
+ * authored as a free-form label list, so the links have to be flat props —
36
+ * one per position — and a flat prop needs a name that exists up front.
37
+ *
38
+ * Eight is well past any usable navigation row (the marketing content-tabs
39
+ * row is three) and costs nothing at render: the attributes panel only shows
40
+ * a link picker for a tab that actually exists, so a two-tab strip shows two.
41
+ */ export const TAB_LINK_SLOTS = 8;
42
+ /** Prop name carrying the link of the 1-based `position`-th tab. */ export function tabLinkProp(position) {
43
+ return `tabLink${position}`;
44
+ }
45
+ /**
46
+ * Splits the authored label list. Newline-separated is the documented
47
+ * form; commas are accepted because a one-line "A, B, C" is what people
48
+ * type first.
49
+ */ export function parseLabels(value) {
50
+ if (value == null) return [];
51
+ return String(value).split(/[\n,]/).map((label)=>label.trim()).filter(Boolean);
52
+ }
53
+ /** Stable DOM id fragment for the tab ⇄ panel aria wiring. */ export function labelSlug(label) {
54
+ return label.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'tab';
55
+ }
56
+ /** Case/whitespace-insensitive: authors don't retype labels exactly. */ export function labelsMatch(a, b) {
57
+ return String(a != null ? a : '').trim().toLowerCase() === String(b != null ? b : '').trim().toLowerCase();
58
+ }
59
+ /**
60
+ * Which tab the strip opens on, by index into the parsed label list.
61
+ *
62
+ * Two rules, in order:
63
+ *
64
+ * 1. the tab the author NAMED in `opensOn` (AGL-3164), matched by label with
65
+ * the same case/whitespace tolerance panels are matched with;
66
+ * 2. otherwise the landing tab — the first one WITHOUT a screen link, which
67
+ * is how a navigation row says "this is the page you are on" (AGL-1312),
68
+ * and which is tab 0 on a strip with no links at all, i.e. exactly what
69
+ * this element did before the attribute existed.
70
+ *
71
+ * The named tab is refused when it carries a screen link: a linked tab
72
+ * navigates instead of revealing a panel (AGL-1312), so it is not a tab the
73
+ * strip can rest on, and opening on it would show the indicator over a panel
74
+ * nobody can see. It falls back rather than rendering nothing.
75
+ *
76
+ * Never returns -1. `landing` is -1 when EVERY tab navigates — the strip
77
+ * shows no indicator there, but callers still read a label with this index.
78
+ */ export function openingTabIndex(labels, opensOn, linkIds = []) {
79
+ const named = labels.findIndex((label, index)=>!linkIds[index] && labelsMatch(label, opensOn));
80
+ if (named >= 0) return named;
81
+ const landing = labels.findIndex((_label, index)=>!linkIds[index]);
82
+ return landing < 0 ? 0 : landing;
83
+ }
84
+ export const TabsContext = /*#__PURE__*/ createContext(undefined);
85
+ /**
86
+ * Tabs (https://mui.com/material-ui/react-tabs/).
87
+ *
88
+ * The strip is authored on the container as a label list, and each Tab
89
+ * Panel child names the label it belongs to. It does **not** slice its
90
+ * React children by index: the node renderer hands every component a
91
+ * single `Branch` fragment rather than one element per child node
92
+ * (`libs/aglyn-node-renderer`), so an index-based split would silently
93
+ * put all the content behind the first tab. Matching by label also
94
+ * survives reordering panels in the hierarchy.
95
+ *
96
+ * The strip renders from the author's own list, so it is complete in the
97
+ * SSR output — no registration pass, no empty-then-filled flash, and it
98
+ * still reads as a tab list with JavaScript disabled.
99
+ *
100
+ * ## Link mode (AGL-1312)
101
+ *
102
+ * Any tab may carry a screen link (`tabLink1`…`tabLink8`, by position). A
103
+ * tab that has one renders as a real anchor — navigable, crawlable,
104
+ * middle-clickable — because the row it exists for (Blog | Changelog |
105
+ * Newsroom) is navigation ACROSS screens, which a panel switcher can only
106
+ * fake with empty panels or a hand-styled row of buttons. The anchor is an
107
+ * `AppLink`, never a bare `href`: a raw href full-reloads the SPA.
108
+ *
109
+ * A tab WITHOUT a link keeps today's behavior exactly, and mixed rows work
110
+ * — the marketing pattern IS a mixed row, since the tab for the page you
111
+ * are already on must not link to itself. That unlinked tab is also how the
112
+ * author says which page this copy of the row is on: the strip lands on it,
113
+ * so the same three-tab row placed on three screens shows the indicator in
114
+ * the right place on each, with no "selected tab" attribute to keep in sync
115
+ * — `opensOn` below is for panel rows and gives way to this one.
116
+ *
117
+ * ## Which tab opens (AGL-3164)
118
+ *
119
+ * `opensOn` names it, by LABEL. Unset opens the first tab, which is what
120
+ * every Tabs authored before the attribute existed does; a label that no
121
+ * longer matches opens the first tab too, rather than nothing. The rule
122
+ * lives in {@link openingTabIndex} and is mirrored server-side by
123
+ * `deferLazyPanelNodes` (tenant-runtime), which decides which panel's nodes
124
+ * reach the page at all — so the tab that opens is the one that was
125
+ * rendered, and the page does not paint one panel and swap to another.
126
+ *
127
+ * **Accessibility.** The row is a `tablist` only while NOTHING in it
128
+ * navigates. As soon as one tab carries a link the whole strip is marked up
129
+ * as navigation: a `nav` landmark, no `role="tab"`, no `aria-selected`, and
130
+ * no roving tab stop (every link is its own tab stop, as in any nav). The
131
+ * alternative — an `<a href>` wearing `role="tab"` — tells assistive tech
132
+ * the activation will reveal a panel when it actually loads a page, and a
133
+ * tablist whose children are not tabs is invalid either way. The tab for
134
+ * the current screen is marked `aria-current="page"`; tabs that still
135
+ * switch panels stay buttons and keep their `aria-controls`, which is an
136
+ * accurate disclosure relationship on its own. The half-applied version of
137
+ * this — links inside a tablist, or panels still claiming `role="tabpanel"`
138
+ * with no tablist above them — is what this deliberately avoids.
139
+ */ const TabsElement = /*#__PURE__*/ forwardRef((rawProps, ref)=>{
140
+ var _parsed_activeIndex;
141
+ // Cleared props dropped up front (AGL-1451). The four selects below
142
+ // already coalesce their own value, but `rest` is spread onto the root
143
+ // and the per-tab link props are read out of it, so a `null` from the
144
+ // field's ✕ (AGL-1226) must not survive this far.
145
+ const props = dropClearedProps(rawProps);
146
+ const { labels, ariaLabel, orientation, variant, centered, textColor, indicatorColor, ssrPanels, lazyPanels, opensOn, children } = props, rest = _object_without_properties_loose(props, [
147
+ "labels",
148
+ "ariaLabel",
149
+ "orientation",
150
+ "variant",
151
+ "centered",
152
+ "textColor",
153
+ "indicatorColor",
154
+ "ssrPanels",
155
+ "lazyPanels",
156
+ "opensOn",
157
+ "children"
158
+ ]);
159
+ const { editorInert, suppressNavigation } = Aglyn.useScreenLink(undefined);
160
+ // Read the routing map directly rather than calling `useScreenLink` per
161
+ // tab: the number of tabs changes with the label list, and a hook per
162
+ // list item is a rules-of-hooks violation waiting for the author to add
163
+ // a fourth tab.
164
+ const { screens } = useContext(Aglyn.ScreenLinkContext);
165
+ // Screen ids by tab position, lifted out of `rest` so they can never
166
+ // reach the DOM as unknown attributes (the nav-menu does the same with
167
+ // its legacy trigger prop).
168
+ const linkIds = [];
169
+ for(let slot = 1; slot <= TAB_LINK_SLOTS; slot += 1){
170
+ const key = tabLinkProp(slot);
171
+ const value = rest[key];
172
+ linkIds.push(typeof value === 'string' && value ? value : undefined);
173
+ delete rest[key];
174
+ }
175
+ const parsed = parseLabels(labels);
176
+ // Authored links decide the mode, NOT resolved hrefs: the canvas and
177
+ // preview suppress navigation, and they must still show the element the
178
+ // page will actually ship (the same rule Screen Link follows).
179
+ const navMode = parsed.some((_label, index)=>!!linkIds[index]);
180
+ /**
181
+ * The tab a navigation row LANDS on is the one with no link: the row is
182
+ * placed on every screen it names, and on each of those the tab for that
183
+ * screen is left unlinked, because a page that links to itself helps
184
+ * nobody. So "first tab with no link" is how the author says "this is
185
+ * the page you are on" — without a second attribute, and without this
186
+ * element having to know the current route.
187
+ *
188
+ * `-1` means every tab navigates: nothing is current, and the strip
189
+ * shows no indicator rather than inventing one under the first tab.
190
+ * With no links at all this is 0, i.e. exactly today's behavior.
191
+ */ const landing = parsed.findIndex((_label, index)=>!linkIds[index]);
192
+ // Which tab this strip opens on (AGL-3164): the one the author named,
193
+ // and otherwise `landing` — so an unset attribute, or one naming a label
194
+ // that has since been renamed away, behaves exactly as it always did.
195
+ const opening = openingTabIndex(parsed, opensOn, linkIds);
196
+ const [active, setActive] = useState(opening);
197
+ // Once a lazy panel has been opened it stays mounted: re-tabbing between
198
+ // two plans should not re-render 50 rows each time, and anything the
199
+ // reader typed or scrolled inside a panel survives a round trip.
200
+ //
201
+ // Seeded with the OPENING label, because that panel is open from the
202
+ // start without anyone clicking it. Leaving it out unmounted the opening
203
+ // panel the moment the reader moved away — visibly inconsistent with
204
+ // every other panel they had visited, and caught by the round-trip test.
205
+ const [opened, setOpened] = useState(()=>{
206
+ const first = parsed[opening];
207
+ return new Set(first === undefined ? [] : [
208
+ first
209
+ ]);
210
+ });
211
+ // A label removed from the list must not leave the strip pointing at
212
+ // a tab that no longer exists — MUI warns and drops the indicator.
213
+ //
214
+ // On the besigner canvas the authored attribute is read every render
215
+ // instead of the state seeded at mount: `onChange` is inert there, so
216
+ // nothing else can have moved the selection, and a canvas that kept
217
+ // showing the tab that was open when the element mounted would make
218
+ // "Opens on" look like it had done nothing.
219
+ const chosen = editorInert ? opening : active < parsed.length ? active : 0;
220
+ // A tab that navigates is never the selected one; if the author has just
221
+ // given the open tab a link, fall back to one that still has a panel.
222
+ const activeIndex = linkIds[chosen] && landing >= 0 ? landing : chosen;
223
+ const resolvedVariant = variant || 'standard';
224
+ const vertical = orientation === 'vertical';
225
+ const stripLabel = ariaLabel || 'Tabs';
226
+ const strip = /*#__PURE__*/ _jsx(MuiTabs, _extends({
227
+ // No indicator when every tab navigates: there is no selection to
228
+ // show, and marking one anyway would point at the wrong page.
229
+ value: parsed.length && landing >= 0 ? activeIndex : false,
230
+ onChange: editorInert ? undefined : (_event, next)=>{
231
+ // A navigating tab must not move the selection on its way
232
+ // out: the indicator would jump to a tab whose panel is
233
+ // empty while the next page is still loading.
234
+ if (linkIds[next]) return;
235
+ setActive(next);
236
+ const label = parsed[next];
237
+ if (label !== undefined) {
238
+ setOpened((prev)=>prev.has(label) ? prev : new Set(prev).add(label));
239
+ }
240
+ },
241
+ orientation: vertical ? 'vertical' : 'horizontal',
242
+ variant: resolvedVariant,
243
+ // MUI ignores `centered` on a scrollable strip and warns in dev.
244
+ centered: resolvedVariant === 'standard' ? !!centered : undefined,
245
+ textColor: textColor || 'primary',
246
+ indicatorColor: indicatorColor || 'primary'
247
+ }, navMode ? {
248
+ component: 'nav',
249
+ slotProps: {
250
+ root: {
251
+ 'aria-label': stripLabel
252
+ },
253
+ list: {
254
+ role: undefined
255
+ }
256
+ }
257
+ } : {
258
+ 'aria-label': stripLabel
259
+ }, {
260
+ children: parsed.map((label, index)=>{
261
+ const slug = labelSlug(label);
262
+ const key = `${slug}-${index}`;
263
+ if (!navMode) {
264
+ return /*#__PURE__*/ _jsx(MuiTab, {
265
+ label: label,
266
+ id: `tab-${slug}`,
267
+ "aria-controls": `tabpanel-${slug}`
268
+ }, key);
269
+ }
270
+ // Suppressed navigation (canvas, preview) keeps the item in the
271
+ // row but without an href, so a click cannot leave the editor.
272
+ const href = suppressNavigation ? undefined : Aglyn.resolveScreenHref(screens, linkIds[index]);
273
+ // An authored target that the routing map does not have: the
274
+ // screen was unpublished or deleted out from under this row
275
+ // (AGL-1893). Computed from the MAP, never from `href`, so a
276
+ // canvas or preview render — where nothing has an href on purpose
277
+ // — does not paint every working link as dead.
278
+ const broken = Aglyn.isScreenLinkBroken(screens, linkIds[index]);
279
+ return /*#__PURE__*/ _jsx(MuiTab, _extends({
280
+ label: label,
281
+ id: `tab-${slug}`,
282
+ // Only a tab that still reveals a panel controls anything.
283
+ "aria-controls": linkIds[index] ? undefined : `tabpanel-${slug}`
284
+ }, href ? {
285
+ component: AppLink,
286
+ componentVariant: 'naked',
287
+ href
288
+ } : null, {
289
+ /**
290
+ * A tab whose screen is gone stops being a control (AGL-1893).
291
+ *
292
+ * It used to render as a bare `<button>` indistinguishable
293
+ * from its working neighbours: no href, no `aria-controls`,
294
+ * and `onChange` returns early on it because the AUTHORED id
295
+ * is still there — so it looked live, announced live, and did
296
+ * nothing at all. Live on `/changelog` and `/newsroom` for two
297
+ * days without anything anywhere saying so.
298
+ *
299
+ * Disabled rather than hidden, on purpose:
300
+ *
301
+ * - the routing map differs between the render that produced
302
+ * an ISR page and the render that hydrates it, and AGL-1268
303
+ * settled that a node's ELEMENT must not depend on it —
304
+ * removing the tab would make the strip's DOM shape depend
305
+ * on it, which is strictly worse than an attribute;
306
+ * - `MuiTabs` addresses its children by index, so dropping one
307
+ * silently repoints `value` at the wrong tab;
308
+ * - the failure directions are not symmetric. A wrongly
309
+ * disabled tab is a dimmed label; a wrongly hidden one is
310
+ * navigation that vanished from a customer's site with no
311
+ * trace of why.
312
+ *
313
+ * Falling through to panel behaviour instead — the other
314
+ * option AGL-1893 weighed — is not available to this element:
315
+ * the node renderer hands it ONE `Branch` fragment rather than
316
+ * per-panel children, so a Tabs container cannot know whether
317
+ * a panel with this label exists, and on the real `/changelog`
318
+ * shape it does not.
319
+ *
320
+ * `landing`, `opening`, `activeIndex`, `aria-controls` and the
321
+ * `onChange` guard all still read the AUTHORED id, unchanged.
322
+ * They must: `deferLazyPanelNodes` computes the opening label
323
+ * server-side from the raw `tabLink{n}` props and has no
324
+ * routing map to resolve ids with, so a client that resolved
325
+ * differently would keep a panel the server withheld.
326
+ */ disabled: broken && !editorInert ? true : undefined
327
+ }, Aglyn.brokenScreenLinkProps(broken, !!editorInert), {
328
+ sx: broken && editorInert ? Aglyn.BROKEN_SCREEN_LINK_SX : undefined,
329
+ // MUI writes these before spreading the rest, so passing them
330
+ // undefined is what removes them (see the a11y note above).
331
+ role: undefined,
332
+ "aria-selected": undefined,
333
+ "aria-current": landing >= 0 && index === activeIndex ? 'page' : undefined,
334
+ // Undo the roving tab stop: in a nav every item is reachable
335
+ // with Tab, and MUI's arrow-key handler already stands down
336
+ // because it only moves between `role="tab"` siblings.
337
+ tabIndex: undefined
338
+ }), key);
339
+ })
340
+ }));
341
+ return /*#__PURE__*/ _jsx(TabsContext.Provider, {
342
+ value: {
343
+ activeLabel: (_parsed_activeIndex = parsed[activeIndex]) != null ? _parsed_activeIndex : '',
344
+ // Every panel at once on the canvas: a hidden panel cannot be
345
+ // selected or styled, the same reason Accordion force-expands.
346
+ showAll: !!editorInert,
347
+ // Lazy is the default (AGL-1283 option 3); `ssrPanels` is the SEO
348
+ // escape hatch. The legacy `lazyPanels` opt-in is subsumed: `true`
349
+ // now means what the default means, and a stored `false` was only
350
+ // ever the old default, never an author's intent.
351
+ lazyPanels: !ssrPanels,
352
+ opened,
353
+ navMode
354
+ },
355
+ children: /*#__PURE__*/ _jsxs(Box, _extends({
356
+ ref: ref
357
+ }, rest, {
358
+ // MERGE, never replace. `sx` written after `{...rest}` silently
359
+ // discarded whatever the author set on the node — every Tabs on
360
+ // every site rendered with default MUI styling and no way to
361
+ // change it, with nothing in the editor to suggest the value had
362
+ // been dropped (AGL-1284). Vertical orientation still needs the
363
+ // flex row, so it goes first and the author can override it.
364
+ sx: [
365
+ {
366
+ display: vertical ? 'flex' : undefined
367
+ },
368
+ ...Array.isArray(rest.sx) ? rest.sx : [
369
+ rest.sx
370
+ ]
371
+ ],
372
+ children: [
373
+ strip,
374
+ children
375
+ ]
376
+ }))
377
+ });
378
+ });
379
+ TabsElement.displayName = 'AglynTabs';
380
+ /**
381
+ * One tab's content. Outside a Tabs container it renders plainly rather
382
+ * than disappearing — a panel dragged out of its tabs would otherwise
383
+ * vanish from the canvas with no explanation.
384
+ */ export const TabPanelElement = /*#__PURE__*/ forwardRef((props, ref)=>{
385
+ const { label, aglynDeferred, children } = props, rest = _object_without_properties_loose(props, [
386
+ "label",
387
+ "aglynDeferred",
388
+ "children"
389
+ ]);
390
+ const context = useContext(TabsContext);
391
+ const selected = !context || labelsMatch(label, context.activeLabel);
392
+ const visible = selected || !!(context == null ? void 0 : context.showAll);
393
+ /**
394
+ * With `lazyPanels`, an unopened panel renders its wrapper but not its
395
+ * children — the wrapper stays so the `aria-controls` target and the
396
+ * tab ⇄ panel wiring still resolve, and so nothing about the accessible
397
+ * structure depends on whether the reader has clicked yet.
398
+ *
399
+ * `opened` is checked as well as `selected` because a panel that has been
400
+ * visited stays mounted; only the never-opened ones are skipped.
401
+ */ const mounted = !context || !context.lazyPanels || context.showAll || selected || context.opened.has(String(label != null ? label : ''));
402
+ /**
403
+ * In link mode the strip is a nav, not a tablist (AGL-1312), so a panel
404
+ * has no tablist to belong to and drops the tab-pattern ARIA rather than
405
+ * claiming half of it. The id stays — the button that still reveals this
406
+ * panel points `aria-controls` at it, which is a valid relationship on
407
+ * its own — and an orphan panel (dragged out of its Tabs, no context)
408
+ * keeps today's markup untouched.
409
+ */ const navMode = !!(context == null ? void 0 : context.navMode);
410
+ return /*#__PURE__*/ _jsxs(Box, _extends({
411
+ ref: ref,
412
+ role: navMode ? undefined : 'tabpanel',
413
+ hidden: !visible,
414
+ id: `tabpanel-${labelSlug(String(label != null ? label : ''))}`,
415
+ "aria-labelledby": navMode ? undefined : `tab-${labelSlug(String(label != null ? label : ''))}`
416
+ }, rest, {
417
+ // Same merge bug as the Tabs container (AGL-1284): the author's `sx`
418
+ // was dropped, so a panel could not even have its padding changed.
419
+ // Order matters — the default padding is first so the author can
420
+ // override it, and the hide rule is LAST so an authored `display`
421
+ // can never force a deselected panel back on screen.
422
+ sx: [
423
+ {
424
+ p: 2
425
+ },
426
+ ...Array.isArray(rest.sx) ? rest.sx : [
427
+ rest.sx
428
+ ],
429
+ ...visible ? [] : [
430
+ {
431
+ display: 'none'
432
+ }
433
+ ]
434
+ ],
435
+ children: [
436
+ (context == null ? void 0 : context.showAll) && !selected ? // Canvas only: says which tab this content belongs to, so a
437
+ // stack of panels is readable and a label typo is obvious.
438
+ /*#__PURE__*/ _jsx(Typography, {
439
+ variant: "caption",
440
+ color: "text.secondary",
441
+ sx: {
442
+ display: 'block',
443
+ mb: 0.5
444
+ },
445
+ children: label ? `Tab: ${label}` : 'Tab: (no label set)'
446
+ }) : null,
447
+ mounted ? aglynDeferred && visible ? // Opened, but its nodes are still in flight (AGL-1287). Only while
448
+ // VISIBLE — a deferred panel nobody has opened must stay silent, or
449
+ // every unopened tab would render a spinner into the page.
450
+ //
451
+ // Deliberately not a skeleton of the content: a withheld subtree can
452
+ // be a 50-row table or a paragraph, and this component has no way to
453
+ // know which, so a shaped skeleton would be a guess that flashes
454
+ // wrong. `role="status"` announces it to a screen reader, which
455
+ // otherwise gets a panel that is silently empty for seconds.
456
+ /*#__PURE__*/ _jsx(Typography, {
457
+ role: "status",
458
+ variant: "body2",
459
+ color: "text.secondary",
460
+ sx: {
461
+ display: 'block',
462
+ py: 2
463
+ },
464
+ children: 'Loading…'
465
+ }) : children : null
466
+ ]
467
+ }));
468
+ });
469
+ TabPanelElement.displayName = 'AglynTabPanel';
470
+ /** `centered` is a standard-strip-only prop; MUI warns when scrollable. */ const STANDARD_ONLY = {
471
+ when: 'variant',
472
+ is: 'scrollable',
473
+ notMatch: true
474
+ };
475
+ /**
476
+ * Shows a per-tab link picker only once the label list has that many tabs,
477
+ * so a three-tab strip offers three pickers rather than eight.
478
+ *
479
+ * Expressed as a serialisable `pattern` rather than an `is` predicate:
480
+ * conditions travel with the component schema, and a function in there is a
481
+ * live grenade the day a schema is cloned or sent across a boundary. An
482
+ * "item" is a run without a separator that holds something visible, and a
483
+ * separator run swallows blank lines, so it counts exactly what
484
+ * `parseLabels` counts.
485
+ */ export function atLeastLabels(count) {
486
+ if (count <= 1) return {
487
+ when: 'labels',
488
+ isNotEmpty: true
489
+ };
490
+ const item = '[^\\n,]*\\S[^\\n,]*';
491
+ const separator = '[\\n,][\\s,]*';
492
+ return {
493
+ when: 'labels',
494
+ pattern: `(?:${item}${separator}){${count - 1}}${item}`
495
+ };
496
+ }
497
+ /**
498
+ * The "Opens on" field's `resolveProps`: its answers are the element's OWN
499
+ * tab labels, read from the sibling `labels` field as they are typed
500
+ * (AGL-3164).
501
+ *
502
+ * A picker rather than a typed index or a typed label. An index goes on
503
+ * meaning "the second tab" after a reorder, with nothing on screen to say
504
+ * the wrong panel now opens; a typed label breaks on a rename and on every
505
+ * typo in between. Choosing from the list the author has just written is the
506
+ * only form of this control someone who does not write code can get right,
507
+ * which is the whole reason it exists instead of the tabs being reordered by
508
+ * hand to obtain a behavior.
509
+ *
510
+ * The answers follow the label list as it is edited because the field's
511
+ * `condition` names `labels`: data-driven-forms subscribes a conditional
512
+ * field to its condition's trigger fields, and that subscription is what
513
+ * re-runs this.
514
+ *
515
+ * A stored label the list no longer holds is offered back as its own option,
516
+ * named as gone. The element itself already falls back to the first tab, so
517
+ * the page is never broken by one; but a picker that rendered BLANK would
518
+ * read as "not set", and the author would never learn that the choice they
519
+ * made has stopped being honoured — the same reason a Screen picker names a
520
+ * target the host no longer has (AGL-1893).
521
+ */ export function opensOnFieldProps(_props, field, formOptions) {
522
+ var _ref;
523
+ var _formOptions_getFieldState, _formOptions_getFieldState1, _formOptions_getState_values, _formOptions_getState, _formOptions_getState1, _field_input;
524
+ // The registered field first, the form's values as the fallback: a field
525
+ // that has not registered yet has no state, and the values are there from
526
+ // the first render.
527
+ const authored = (_ref = formOptions == null ? void 0 : (_formOptions_getFieldState1 = formOptions.getFieldState) == null ? void 0 : (_formOptions_getFieldState = _formOptions_getFieldState1.call(formOptions, 'labels')) == null ? void 0 : _formOptions_getFieldState.value) != null ? _ref : formOptions == null ? void 0 : (_formOptions_getState1 = formOptions.getState) == null ? void 0 : (_formOptions_getState = _formOptions_getState1.call(formOptions)) == null ? void 0 : (_formOptions_getState_values = _formOptions_getState.values) == null ? void 0 : _formOptions_getState_values['labels'];
528
+ const options = parseLabels(authored).map((label)=>({
529
+ value: label,
530
+ label
531
+ }));
532
+ const stored = field == null ? void 0 : (_field_input = field.input) == null ? void 0 : _field_input.value;
533
+ if (typeof stored === 'string' && stored.trim() && !options.some((option)=>labelsMatch(option.value, stored))) {
534
+ options.push({
535
+ value: stored,
536
+ label: `${stored} (no longer a tab)`
537
+ });
538
+ }
539
+ return {
540
+ options
541
+ };
542
+ }
543
+ /** "1st", "2nd", "3rd", … for the link fields' own descriptions. */ function ordinal(position) {
544
+ const suffix = position % 10 === 1 && position !== 11 ? 'st' : position % 10 === 2 && position !== 12 ? 'nd' : position % 10 === 3 && position !== 13 ? 'rd' : 'th';
545
+ return `${position}${suffix}`;
546
+ }
547
+ /**
548
+ * One Screen picker per tab position (AGL-1312), the same field the Screen
549
+ * Link element uses — so a tab's target is chosen from the host's screens
550
+ * and survives a slug rename, and the attributes panel needs no new UI.
551
+ *
552
+ * Deliberately NOT paired with a "link mode" switch: a mode select would
553
+ * need its default to persist, and an `''` option value cannot (AGL-1191 —
554
+ * the form strips it before save). Carrying a link IS the mode, per tab,
555
+ * which is also what makes a mixed row expressible at all.
556
+ */ const TAB_LINK_FIELDS = Array.from({
557
+ length: TAB_LINK_SLOTS
558
+ }, (_unused, index)=>{
559
+ const position = index + 1;
560
+ return {
561
+ name: tabLinkProp(position),
562
+ label: `Tab ${position} link`,
563
+ description: `Screen the ${ordinal(position)} tab navigates to. A tab with a ` + 'link is a real link — it can be opened in a new tab and search ' + 'engines follow it — and it no longer switches panels. Leave it ' + 'unset for a tab that just reveals its own panel, including the ' + 'tab for the page this row is already on.',
564
+ component: Aglyn.FieldComponentType.SCREEN_SELECT,
565
+ condition: atLeastLabels(position)
566
+ };
567
+ });
568
+ export const tabsSchema = {
569
+ $id: TABS_ID,
570
+ pluginId: BUNDLE_ID,
571
+ displayName: 'Tabs',
572
+ description: 'Tab strip whose Tab Panel children each name the tab that reveals ' + 'them.',
573
+ category: Aglyn.ComponentCategory.NAVIGATION,
574
+ icon: {
575
+ path: mdiTab.path,
576
+ sx: {
577
+ color: '#2196f3'
578
+ }
579
+ },
580
+ restrictChildren: [
581
+ Aglyn.LinealDirectiveFlag.LIMIT_TO,
582
+ {
583
+ components: [
584
+ TAB_PANEL_ID
585
+ ]
586
+ }
587
+ ],
588
+ attributes: [
589
+ {
590
+ name: 'labels',
591
+ label: 'Tabs',
592
+ description: 'One tab label per line. Each Tab Panel below shows under the ' + 'tab whose label it names.',
593
+ component: Aglyn.FieldComponentType.TEXTAREA
594
+ },
595
+ ...TAB_LINK_FIELDS,
596
+ {
597
+ name: 'opensOn',
598
+ label: 'Opens on',
599
+ description: 'The tab a visitor lands on. Leave it unset and the first tab ' + 'opens, as it always has. Pick from the tabs above rather than ' + 'reordering them — the strip is the design, and which tab opens ' + 'is not. If the tab named here is later renamed or removed, the ' + 'first tab opens again.',
600
+ component: Aglyn.FieldComponentType.SELECT,
601
+ // Nothing to choose on a one-tab strip — and naming `labels` is also
602
+ // what keeps the answers following the list (see opensOnFieldProps).
603
+ condition: atLeastLabels(2),
604
+ resolveProps: opensOnFieldProps,
605
+ // The corner ✕ is the way back to "the first tab": no option could
606
+ // carry that meaning, since an `''` value is stripped before save
607
+ // (AGL-1191) and every other value names a real tab.
608
+ clearable: true
609
+ },
610
+ {
611
+ name: 'orientation',
612
+ label: 'Orientation',
613
+ description: 'Vertical puts the strip beside the panels instead of above them.',
614
+ component: Aglyn.FieldComponentType.SELECT,
615
+ // Real sentinels throughout this schema rather than deletions
616
+ // (AGL-1451): each "(default)" here names MUI's OWN value for that
617
+ // prop — horizontal, standard, primary — so the option is a real
618
+ // choice an author may want to pin, not a synonym for "unset".
619
+ // Spelled `''` they could not persist at all (AGL-1191): picking
620
+ // Horizontal on a vertical strip silently reverted to vertical.
621
+ options: [
622
+ {
623
+ value: 'horizontal',
624
+ label: 'Horizontal (default)'
625
+ },
626
+ {
627
+ value: 'vertical',
628
+ label: 'Vertical'
629
+ }
630
+ ]
631
+ },
632
+ {
633
+ name: 'variant',
634
+ label: 'Variant',
635
+ description: 'Scrollable keeps long strips usable on small screens; full width ' + 'divides the available space evenly.',
636
+ component: Aglyn.FieldComponentType.SELECT,
637
+ options: [
638
+ {
639
+ value: 'standard',
640
+ label: 'Standard (default)'
641
+ },
642
+ {
643
+ value: 'scrollable',
644
+ label: 'Scrollable'
645
+ },
646
+ {
647
+ value: 'fullWidth',
648
+ label: 'Full width'
649
+ }
650
+ ]
651
+ },
652
+ {
653
+ name: 'ssrPanels',
654
+ label: 'Keep every panel in the page source',
655
+ description: 'Panels are normally built the first time their tab is opened, ' + 'which keeps big tab sets light. Turn this on when panel content ' + 'appears nowhere else on the page and search engines must be able ' + 'to read it — every panel is then rendered into the page source ' + 'up front, which can make a large tab set much heavier.',
656
+ component: Aglyn.FieldComponentType.SWITCH
657
+ },
658
+ {
659
+ name: 'centered',
660
+ label: 'Centered?',
661
+ description: 'Centers the tabs in the strip.',
662
+ component: Aglyn.FieldComponentType.SWITCH,
663
+ // MUI ignores centered on a scrollable strip and warns in dev.
664
+ condition: STANDARD_ONLY
665
+ },
666
+ {
667
+ name: 'textColor',
668
+ label: 'Text color',
669
+ description: 'Theme color of the tab labels.',
670
+ component: Aglyn.FieldComponentType.SELECT,
671
+ options: [
672
+ {
673
+ value: 'primary',
674
+ label: 'Primary (default)'
675
+ },
676
+ {
677
+ value: 'secondary',
678
+ label: 'Secondary'
679
+ },
680
+ {
681
+ value: 'inherit',
682
+ label: 'Inherit'
683
+ }
684
+ ]
685
+ },
686
+ {
687
+ name: 'indicatorColor',
688
+ label: 'Indicator color',
689
+ description: 'Theme color of the underline marking the active tab.',
690
+ component: Aglyn.FieldComponentType.SELECT,
691
+ options: [
692
+ {
693
+ value: 'primary',
694
+ label: 'Primary (default)'
695
+ },
696
+ {
697
+ value: 'secondary',
698
+ label: 'Secondary'
699
+ }
700
+ ]
701
+ },
702
+ {
703
+ name: 'ariaLabel',
704
+ label: 'Accessible label',
705
+ description: 'Names the row for screen readers. Default "Tabs". Give a row ' + 'whose tabs link to other screens its own name — it becomes a ' + 'navigation landmark, and two landmarks called the same thing are ' + 'no help to anyone.',
706
+ component: Aglyn.FieldComponentType.TEXT_FIELD
707
+ }
708
+ ]
709
+ };
710
+ export const tabPanelSchema = {
711
+ $id: TAB_PANEL_ID,
712
+ pluginId: BUNDLE_ID,
713
+ displayName: 'Tab Panel',
714
+ description: 'The content shown under one tab.',
715
+ category: Aglyn.ComponentCategory.NAVIGATION,
716
+ icon: {
717
+ path: mdiViewCarousel.path,
718
+ sx: {
719
+ color: '#2196f3'
720
+ }
721
+ },
722
+ attributes: [
723
+ {
724
+ name: 'label',
725
+ label: 'Shows under tab',
726
+ description: "The tab label this panel belongs to — type it exactly as it " + 'appears in the Tabs list above (capitalisation is ignored).',
727
+ component: Aglyn.FieldComponentType.TEXT_FIELD
728
+ }
729
+ ]
730
+ };
731
+ const tabPanel = (label, body)=>({
732
+ $id: null,
733
+ componentId: TAB_PANEL_ID,
734
+ pluginId: BUNDLE_ID,
735
+ props: {
736
+ label
737
+ },
738
+ nodes: [
739
+ {
740
+ $id: null,
741
+ componentId: 'muiTypography',
742
+ pluginId: BUNDLE_ID,
743
+ props: {
744
+ variant: 'body2',
745
+ children: body
746
+ }
747
+ }
748
+ ]
749
+ });
750
+ export const tabsPresets = [
751
+ {
752
+ // Labels and panels ship already matched: the one place this element
753
+ // can be misconfigured is never hit on the common path.
754
+ $id: generatePresetId(TABS_ID),
755
+ type: Aglyn.NodeType.PRESET,
756
+ displayName: 'Tabs',
757
+ pluginId: BUNDLE_ID,
758
+ description: 'Three tabs with matching panels, ready to edit',
759
+ category: Aglyn.ComponentCategory.NAVIGATION,
760
+ icon: tabsSchema.icon,
761
+ data: {
762
+ $id: null,
763
+ componentId: TABS_ID,
764
+ pluginId: BUNDLE_ID,
765
+ props: {
766
+ labels: 'Overview\nDetails\nFAQ'
767
+ },
768
+ nodes: [
769
+ tabPanel('Overview', 'What this is, in a sentence or two.'),
770
+ tabPanel('Details', 'The specifics that back up the overview.'),
771
+ tabPanel('FAQ', 'The questions people actually ask.')
772
+ ]
773
+ }
774
+ },
775
+ {
776
+ $id: generatePresetId(TAB_PANEL_ID),
777
+ type: Aglyn.NodeType.PRESET,
778
+ displayName: 'Tab Panel',
779
+ pluginId: BUNDLE_ID,
780
+ description: 'One more panel to add to an existing Tabs element',
781
+ category: Aglyn.ComponentCategory.NAVIGATION,
782
+ icon: tabPanelSchema.icon,
783
+ data: tabPanel('', 'Name the tab this panel belongs to.')
784
+ }
785
+ ];
786
+ export default TabsElement;
787
+
788
+ //# sourceMappingURL=tabs.js.map