@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.
- package/LICENSE +201 -0
- package/README.md +22 -0
- package/package.json +47 -0
- package/src/index.d.ts +21 -0
- package/src/index.js +22 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/accordion.d.ts +96 -0
- package/src/lib/components/accordion.js +392 -0
- package/src/lib/components/accordion.js.map +1 -0
- package/src/lib/components/app-bar.d.ts +32 -0
- package/src/lib/components/app-bar.js +86 -0
- package/src/lib/components/app-bar.js.map +1 -0
- package/src/lib/components/blocks.d.ts +46 -0
- package/src/lib/components/blocks.js +1007 -0
- package/src/lib/components/blocks.js.map +1 -0
- package/src/lib/components/box.d.ts +47 -0
- package/src/lib/components/box.js +125 -0
- package/src/lib/components/box.js.map +1 -0
- package/src/lib/components/breadcrumbs.d.ts +44 -0
- package/src/lib/components/breadcrumbs.js +172 -0
- package/src/lib/components/breadcrumbs.js.map +1 -0
- package/src/lib/components/button.d.ts +89 -0
- package/src/lib/components/button.js +272 -0
- package/src/lib/components/button.js.map +1 -0
- package/src/lib/components/card.d.ts +61 -0
- package/src/lib/components/card.js +336 -0
- package/src/lib/components/card.js.map +1 -0
- package/src/lib/components/collection.d.ts +514 -0
- package/src/lib/components/collection.js +2999 -0
- package/src/lib/components/collection.js.map +1 -0
- package/src/lib/components/container.d.ts +42 -0
- package/src/lib/components/container.js +169 -0
- package/src/lib/components/container.js.map +1 -0
- package/src/lib/components/custom-html.d.ts +78 -0
- package/src/lib/components/custom-html.js +219 -0
- package/src/lib/components/custom-html.js.map +1 -0
- package/src/lib/components/data-table.d.ts +57 -0
- package/src/lib/components/data-table.js +216 -0
- package/src/lib/components/data-table.js.map +1 -0
- package/src/lib/components/document-root.d.ts +77 -0
- package/src/lib/components/document-root.js +118 -0
- package/src/lib/components/document-root.js.map +1 -0
- package/src/lib/components/drawer.d.ts +93 -0
- package/src/lib/components/drawer.js +566 -0
- package/src/lib/components/drawer.js.map +1 -0
- package/src/lib/components/function-controls.d.ts +66 -0
- package/src/lib/components/function-controls.js +207 -0
- package/src/lib/components/function-controls.js.map +1 -0
- package/src/lib/components/function-scope.d.ts +110 -0
- package/src/lib/components/function-scope.js +478 -0
- package/src/lib/components/function-scope.js.map +1 -0
- package/src/lib/components/function-widget.d.ts +75 -0
- package/src/lib/components/function-widget.js +307 -0
- package/src/lib/components/function-widget.js.map +1 -0
- package/src/lib/components/grid.d.ts +91 -0
- package/src/lib/components/grid.js +322 -0
- package/src/lib/components/grid.js.map +1 -0
- package/src/lib/components/icon.d.ts +54 -0
- package/src/lib/components/icon.js +163 -0
- package/src/lib/components/icon.js.map +1 -0
- package/src/lib/components/image-list.d.ts +58 -0
- package/src/lib/components/image-list.js +336 -0
- package/src/lib/components/image-list.js.map +1 -0
- package/src/lib/components/image.d.ts +154 -0
- package/src/lib/components/image.js +451 -0
- package/src/lib/components/image.js.map +1 -0
- package/src/lib/components/inline-text.d.ts +74 -0
- package/src/lib/components/inline-text.js +407 -0
- package/src/lib/components/inline-text.js.map +1 -0
- package/src/lib/components/language-switcher.d.ts +35 -0
- package/src/lib/components/language-switcher.js +179 -0
- package/src/lib/components/language-switcher.js.map +1 -0
- package/src/lib/components/layout-slot.d.ts +68 -0
- package/src/lib/components/layout-slot.js +192 -0
- package/src/lib/components/layout-slot.js.map +1 -0
- package/src/lib/components/link-box-accessible-name.d.ts +79 -0
- package/src/lib/components/link-box-accessible-name.js +158 -0
- package/src/lib/components/link-box-accessible-name.js.map +1 -0
- package/src/lib/components/link-box.d.ts +66 -0
- package/src/lib/components/link-box.js +212 -0
- package/src/lib/components/link-box.js.map +1 -0
- package/src/lib/components/list-item-text.d.ts +22 -0
- package/src/lib/components/list-item-text.js +86 -0
- package/src/lib/components/list-item-text.js.map +1 -0
- package/src/lib/components/list-item.d.ts +32 -0
- package/src/lib/components/list-item.js +127 -0
- package/src/lib/components/list-item.js.map +1 -0
- package/src/lib/components/list.d.ts +34 -0
- package/src/lib/components/list.js +108 -0
- package/src/lib/components/list.js.map +1 -0
- package/src/lib/components/markdown.d.ts +129 -0
- package/src/lib/components/markdown.js +853 -0
- package/src/lib/components/markdown.js.map +1 -0
- package/src/lib/components/nav-menu.d.ts +68 -0
- package/src/lib/components/nav-menu.js +683 -0
- package/src/lib/components/nav-menu.js.map +1 -0
- package/src/lib/components/pagination.d.ts +59 -0
- package/src/lib/components/pagination.js +243 -0
- package/src/lib/components/pagination.js.map +1 -0
- package/src/lib/components/paper.d.ts +47 -0
- package/src/lib/components/paper.js +202 -0
- package/src/lib/components/paper.js.map +1 -0
- package/src/lib/components/plugin-frame.d.ts +64 -0
- package/src/lib/components/plugin-frame.js +335 -0
- package/src/lib/components/plugin-frame.js.map +1 -0
- package/src/lib/components/plugin.d.ts +95 -0
- package/src/lib/components/plugin.js +304 -0
- package/src/lib/components/plugin.js.map +1 -0
- package/src/lib/components/product.d.ts +40 -0
- package/src/lib/components/product.js +287 -0
- package/src/lib/components/product.js.map +1 -0
- package/src/lib/components/reusable-instance.d.ts +57 -0
- package/src/lib/components/reusable-instance.js +126 -0
- package/src/lib/components/reusable-instance.js.map +1 -0
- package/src/lib/components/screen-link.d.ts +85 -0
- package/src/lib/components/screen-link.js +324 -0
- package/src/lib/components/screen-link.js.map +1 -0
- package/src/lib/components/search-box.d.ts +41 -0
- package/src/lib/components/search-box.js +125 -0
- package/src/lib/components/search-box.js.map +1 -0
- package/src/lib/components/section.d.ts +56 -0
- package/src/lib/components/section.js +132 -0
- package/src/lib/components/section.js.map +1 -0
- package/src/lib/components/stack.d.ts +55 -0
- package/src/lib/components/stack.js +309 -0
- package/src/lib/components/stack.js.map +1 -0
- package/src/lib/components/tabs.d.ts +288 -0
- package/src/lib/components/tabs.js +788 -0
- package/src/lib/components/tabs.js.map +1 -0
- package/src/lib/components/theme-mode-switcher.d.ts +38 -0
- package/src/lib/components/theme-mode-switcher.js +168 -0
- package/src/lib/components/theme-mode-switcher.js.map +1 -0
- package/src/lib/components/toolbar.d.ts +37 -0
- package/src/lib/components/toolbar.js +117 -0
- package/src/lib/components/toolbar.js.map +1 -0
- package/src/lib/components/typography.d.ts +33 -0
- package/src/lib/components/typography.js +433 -0
- package/src/lib/components/typography.js.map +1 -0
- package/src/lib/components/video-lightbox-controls.d.ts +131 -0
- package/src/lib/components/video-lightbox-controls.js +536 -0
- package/src/lib/components/video-lightbox-controls.js.map +1 -0
- package/src/lib/components/video-lightbox.d.ts +60 -0
- package/src/lib/components/video-lightbox.js +247 -0
- package/src/lib/components/video-lightbox.js.map +1 -0
- package/src/lib/components/video-playback-beacon.d.ts +80 -0
- package/src/lib/components/video-playback-beacon.js +128 -0
- package/src/lib/components/video-playback-beacon.js.map +1 -0
- package/src/lib/components/video-player-frame.d.ts +58 -0
- package/src/lib/components/video-player-frame.js +51 -0
- package/src/lib/components/video-player-frame.js.map +1 -0
- package/src/lib/components/video.d.ts +220 -0
- package/src/lib/components/video.js +874 -0
- package/src/lib/components/video.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +17 -0
- package/src/lib/constants/bundle-common.js +18 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/constants/field-presets.d.ts +93 -0
- package/src/lib/constants/field-presets.js +248 -0
- package/src/lib/constants/field-presets.js.map +1 -0
- package/src/lib/plugin.d.ts +80 -0
- package/src/lib/plugin.js +599 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/utils/author-html-label.d.ts +73 -0
- package/src/lib/utils/author-html-label.js +89 -0
- package/src/lib/utils/author-html-label.js.map +1 -0
- package/src/lib/utils/drop-cleared-props.d.ts +18 -0
- package/src/lib/utils/drop-cleared-props.js +74 -0
- package/src/lib/utils/drop-cleared-props.js.map +1 -0
- package/src/lib/utils/element-picker.d.ts +99 -0
- package/src/lib/utils/element-picker.js +140 -0
- package/src/lib/utils/element-picker.js.map +1 -0
- package/src/lib/utils/generate-preset-id.d.ts +19 -0
- package/src/lib/utils/generate-preset-id.js +25 -0
- package/src/lib/utils/generate-preset-id.js.map +1 -0
- package/src/lib/utils/link-target-props.d.ts +53 -0
- package/src/lib/utils/link-target-props.js +53 -0
- 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
|