@akonwi/mica 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -2
- package/breadcrumbs.d.ts +5 -0
- package/breadcrumbs.js +74 -0
- package/header.d.ts +7 -0
- package/header.js +172 -0
- package/mica.css +612 -0
- package/package.json +34 -3
- package/sidebar.d.ts +11 -0
- package/sidebar.js +135 -0
- package/table.d.ts +5 -0
- package/table.js +59 -0
- package/types/react.d.ts +6 -0
package/README.md
CHANGED
|
@@ -62,8 +62,9 @@ Every component states where its behavior comes from
|
|
|
62
62
|
tooltips, toasts. Delete the stylesheet and everything still works.
|
|
63
63
|
- **JS-enhanced — script, honestly.** Where accessibility genuinely requires
|
|
64
64
|
JS: `tabs.js`, `combobox.js`, `field.js` (declarative validation),
|
|
65
|
-
`select.js`, `toast.js
|
|
66
|
-
working markup — never renders it. There
|
|
65
|
+
`select.js`, `toast.js`, `header.js`, `sidebar.js` (responsive navigation). Each is a tiny
|
|
66
|
+
standalone module that enhances working markup — never renders it. There
|
|
67
|
+
is no shared runtime.
|
|
67
68
|
|
|
68
69
|
`m-avatar` accepts authored initials or a direct native `img`, both CSS-only.
|
|
69
70
|
`avatar.js` is a separate opt-in visual enhancement: importing it locally
|
|
@@ -96,3 +97,27 @@ Read [VISION.md](VISION.md) for the philosophy and
|
|
|
96
97
|
## License
|
|
97
98
|
|
|
98
99
|
[MIT](LICENSE)
|
|
100
|
+
|
|
101
|
+
Nested header navigation uses `nav[data-navigation-menu]` with native
|
|
102
|
+
`details`/`summary` groups and authored `[data-nav-panel]` content. Import
|
|
103
|
+
`header.js` for floating desktop panels and inline mobile disclosures. See
|
|
104
|
+
[the navigation menu docs](https://akonwi.io/mica/docs/navigation-menu.html).
|
|
105
|
+
|
|
106
|
+
Bottom navigation uses `nav[data-bottom-nav]` and native links, with optional
|
|
107
|
+
consumer-authored icons. No JavaScript module is needed. See the
|
|
108
|
+
[bottom navigation docs](https://akonwi.io/mica/docs/bottom-navigation.html)
|
|
109
|
+
for a shell that keeps content clear of the bar.
|
|
110
|
+
|
|
111
|
+
Breadcrumbs use `nav[data-breadcrumbs]` with an ordered list of links and a
|
|
112
|
+
current-page label. Optional `breadcrumbs.js` enhances an authored ancestor
|
|
113
|
+
disclosure. See [the breadcrumbs docs](https://akonwi.io/mica/docs/breadcrumbs.html).
|
|
114
|
+
|
|
115
|
+
Data collections compose native `table[data-table]`, a stable filter/selection
|
|
116
|
+
toolbar, and `nav[data-pagination]`. The app owns all data operations; optional
|
|
117
|
+
`table.js` only adds overflow indicators to `m-table-scroll`. See the
|
|
118
|
+
[data table](https://akonwi.io/mica/docs/data-table.html) and
|
|
119
|
+
[pagination](https://akonwi.io/mica/docs/pagination.html) docs.
|
|
120
|
+
|
|
121
|
+
Inline alerts and guidance use CSS-only `m-callout` with neutral, success,
|
|
122
|
+
warning, and danger treatments. Icons and actions are optional authored
|
|
123
|
+
content; see [callout docs](https://akonwi.io/mica/docs/callout.html).
|
package/breadcrumbs.d.ts
ADDED
package/breadcrumbs.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/* Optional enhancement for authored breadcrumb ancestor disclosures.
|
|
2
|
+
* Native details works without this module. No content or roles are generated. */
|
|
3
|
+
class MBreadcrumbOverflow extends HTMLElement {
|
|
4
|
+
#cleanup;
|
|
5
|
+
connectedCallback() {
|
|
6
|
+
queueMicrotask(() => {
|
|
7
|
+
if (!this.isConnected || this.#cleanup) return;
|
|
8
|
+
const details = this.querySelector(':scope > details');
|
|
9
|
+
const summary = details?.firstElementChild;
|
|
10
|
+
const menu = details?.querySelector(':scope > [data-breadcrumb-menu]');
|
|
11
|
+
if (!summary?.matches('summary') || !menu || !this.closest('nav[data-breadcrumbs]')) return;
|
|
12
|
+
const controller = new AbortController();
|
|
13
|
+
const { signal } = controller;
|
|
14
|
+
let frame = 0;
|
|
15
|
+
const close = () => { details.open = false; };
|
|
16
|
+
const position = () => {
|
|
17
|
+
frame = 0;
|
|
18
|
+
if (!details.open) return;
|
|
19
|
+
const r = summary.getBoundingClientRect();
|
|
20
|
+
const v = window.visualViewport;
|
|
21
|
+
const left = v?.offsetLeft ?? 0, top = v?.offsetTop ?? 0;
|
|
22
|
+
const width = v?.width ?? document.documentElement.clientWidth, height = v?.height ?? innerHeight;
|
|
23
|
+
const css = getComputedStyle(menu);
|
|
24
|
+
const gutter = parseFloat(css.paddingLeft) || 8;
|
|
25
|
+
const gap = parseFloat(css.marginTop) || 0;
|
|
26
|
+
const w = menu.getBoundingClientRect().width;
|
|
27
|
+
const start = getComputedStyle(this).direction === 'rtl' ? r.right - w : r.left;
|
|
28
|
+
const below = Math.max(0, top + height - r.bottom - gap - gutter);
|
|
29
|
+
const above = Math.max(0, r.top - top - gap - gutter);
|
|
30
|
+
const desired = menu.scrollHeight + 2 * (parseFloat(css.borderTopWidth) || 0);
|
|
31
|
+
const flip = below < Math.min(desired, above);
|
|
32
|
+
menu.style.setProperty('--m-breadcrumb-left', `${Math.max(left + gutter, Math.min(start, left + width - w - gutter))}px`);
|
|
33
|
+
menu.style.setProperty('--m-breadcrumb-height', `${flip ? above : below}px`);
|
|
34
|
+
menu.style.setProperty('--m-breadcrumb-top', `${flip ? r.top - Math.min(desired, above) - 2 * gap : r.bottom}px`);
|
|
35
|
+
};
|
|
36
|
+
const schedule = () => { if (!frame) frame = requestAnimationFrame(position); };
|
|
37
|
+
details.addEventListener('toggle', schedule, { signal });
|
|
38
|
+
details.addEventListener('keydown', event => {
|
|
39
|
+
if (event.key === 'Escape' && details.open) {
|
|
40
|
+
event.preventDefault(); event.stopPropagation(); close(); summary.focus();
|
|
41
|
+
}
|
|
42
|
+
}, { signal });
|
|
43
|
+
document.addEventListener('click', event => { if (!this.contains(event.target)) close(); }, { signal });
|
|
44
|
+
details.addEventListener('focusout', event => {
|
|
45
|
+
if (event.relatedTarget) { if (!this.contains(event.relatedTarget)) close(); }
|
|
46
|
+
else requestAnimationFrame(() => { if (!signal.aborted && !this.contains(document.activeElement)) close(); });
|
|
47
|
+
}, { signal });
|
|
48
|
+
menu.addEventListener('click', event => {
|
|
49
|
+
if (!event.target.closest('a[href]') || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
50
|
+
queueMicrotask(() => {
|
|
51
|
+
if (event.defaultPrevented) return;
|
|
52
|
+
const focused = menu.contains(document.activeElement);
|
|
53
|
+
close();
|
|
54
|
+
if (focused) summary.focus();
|
|
55
|
+
});
|
|
56
|
+
}, { signal });
|
|
57
|
+
window.addEventListener('resize', schedule, { signal, passive: true });
|
|
58
|
+
window.addEventListener('scroll', schedule, { signal, passive: true, capture: true });
|
|
59
|
+
window.visualViewport?.addEventListener('resize', schedule, { signal });
|
|
60
|
+
window.visualViewport?.addEventListener('scroll', schedule, { signal });
|
|
61
|
+
const observer = new ResizeObserver(schedule);
|
|
62
|
+
observer.observe(menu); observer.observe(summary);
|
|
63
|
+
this.setAttribute('data-m-breadcrumb-ready', '');
|
|
64
|
+
schedule();
|
|
65
|
+
this.#cleanup = () => {
|
|
66
|
+
controller.abort(); observer.disconnect(); cancelAnimationFrame(frame); close();
|
|
67
|
+
this.removeAttribute('data-m-breadcrumb-ready');
|
|
68
|
+
for (const name of ['left', 'top', 'height']) menu.style.removeProperty(`--m-breadcrumb-${name}`);
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
disconnectedCallback() { this.#cleanup?.(); this.#cleanup = undefined; }
|
|
73
|
+
}
|
|
74
|
+
if (!customElements.get('m-breadcrumb-overflow')) customElements.define('m-breadcrumb-overflow', MBreadcrumbOverflow);
|
package/header.d.ts
ADDED
package/header.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/* Optional responsive enhancement for <m-header>.
|
|
2
|
+
* Authored nav stays inline without this module. At narrow container widths,
|
|
3
|
+
* the same nav becomes a native popover invoked by button[data-menu].
|
|
4
|
+
* No generated links, routing, shared runtime, or menu keyboard emulation.
|
|
5
|
+
*/
|
|
6
|
+
class MHeader extends HTMLElement {
|
|
7
|
+
#cleanup;
|
|
8
|
+
|
|
9
|
+
connectedCallback() {
|
|
10
|
+
queueMicrotask(() => {
|
|
11
|
+
if (!this.isConnected || this.#cleanup) return;
|
|
12
|
+
const nav = this.querySelector(':scope > nav');
|
|
13
|
+
const button = this.querySelector(':scope > button[data-menu]');
|
|
14
|
+
if (!nav || nav.hasAttribute('popover')) return;
|
|
15
|
+
const canCollapse = Boolean(button && nav.id && button.getAttribute('popovertarget') === nav.id && 'showPopover' in nav);
|
|
16
|
+
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
const { signal } = controller;
|
|
19
|
+
let frame = 0;
|
|
20
|
+
const groups = nav.hasAttribute('data-navigation-menu')
|
|
21
|
+
? [...nav.querySelectorAll(':scope > details')].filter(d => d.firstElementChild?.matches('summary') && d.querySelector(':scope > [data-nav-panel]'))
|
|
22
|
+
: [];
|
|
23
|
+
const closeGroups = () => { for (const group of groups) group.open = false; };
|
|
24
|
+
const positionGroups = () => {
|
|
25
|
+
if (this.hasAttribute('data-m-header-collapsed')) return;
|
|
26
|
+
const viewport = window.visualViewport;
|
|
27
|
+
const left = viewport?.offsetLeft ?? 0;
|
|
28
|
+
const top = viewport?.offsetTop ?? 0;
|
|
29
|
+
const width = viewport?.width ?? document.documentElement.clientWidth;
|
|
30
|
+
const height = viewport?.height ?? innerHeight;
|
|
31
|
+
for (const group of groups) {
|
|
32
|
+
if (!group.open) continue;
|
|
33
|
+
const panel = group.querySelector(':scope > [data-nav-panel]');
|
|
34
|
+
const r = group.firstElementChild.getBoundingClientRect();
|
|
35
|
+
// A computed length resolves rem/calc tokens to pixels, unlike reading
|
|
36
|
+
// the raw custom property. Gap is inert on the block panel itself.
|
|
37
|
+
const gap = Math.max(0, parseFloat(getComputedStyle(panel).rowGap) || 0);
|
|
38
|
+
panel.style.setProperty('--m-nav-max-width', `${Math.max(0, width - 2 * gap)}px`);
|
|
39
|
+
const panelWidth = panel.getBoundingClientRect().width;
|
|
40
|
+
const start = getComputedStyle(nav).direction === 'rtl' ? r.right - panelWidth : r.left;
|
|
41
|
+
panel.style.setProperty('--m-nav-left', `${Math.max(left + gap, Math.min(start, left + width - gap - panelWidth))}px`);
|
|
42
|
+
const below = Math.max(0, top + height - r.bottom - 2 * gap);
|
|
43
|
+
const above = Math.max(0, r.top - top - 2 * gap);
|
|
44
|
+
const desired = panel.scrollHeight + 2 * (parseFloat(getComputedStyle(panel).borderTopWidth) || 0);
|
|
45
|
+
const flip = below < Math.min(desired, above);
|
|
46
|
+
panel.style.setProperty('--m-nav-height', `${flip ? above : below}px`);
|
|
47
|
+
panel.style.setProperty('--m-nav-top', `${flip ? r.top - gap - Math.min(desired, above) : r.bottom + gap}px`);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const close = () => { if (nav.matches(':popover-open')) nav.hidePopover(); };
|
|
51
|
+
const position = () => {
|
|
52
|
+
frame = 0;
|
|
53
|
+
positionGroups();
|
|
54
|
+
if (!nav.matches(':popover-open')) return;
|
|
55
|
+
const r = this.getBoundingClientRect();
|
|
56
|
+
const viewport = window.visualViewport;
|
|
57
|
+
const left = viewport?.offsetLeft ?? 0;
|
|
58
|
+
const top = viewport?.offsetTop ?? 0;
|
|
59
|
+
const width = viewport?.width ?? document.documentElement.clientWidth;
|
|
60
|
+
const height = viewport?.height ?? window.innerHeight;
|
|
61
|
+
const gutter = parseFloat(getComputedStyle(nav).paddingTop) || 8;
|
|
62
|
+
const available = Math.max(0, width - 2 * gutter);
|
|
63
|
+
const menuWidth = Math.min(r.width, available);
|
|
64
|
+
nav.style.setProperty('--m-header-menu-width', `${menuWidth}px`);
|
|
65
|
+
nav.style.setProperty('--m-header-menu-left', `${Math.max(left + gutter, Math.min(r.left, left + width - gutter - menuWidth))}px`);
|
|
66
|
+
// Measure the rendered links: labels may wrap, fonts may be enlarged,
|
|
67
|
+
// and compact-only actions need not have the same height as links.
|
|
68
|
+
// Measure content without expanding the scroll container: changing its
|
|
69
|
+
// height during a scroll event resets scrollTop and strands lower links.
|
|
70
|
+
const rows = [...nav.children].map(e => e.getBoundingClientRect()).filter(r => r.height);
|
|
71
|
+
const css = getComputedStyle(nav);
|
|
72
|
+
const desired = (rows.length ? Math.max(...rows.map(r => r.bottom)) - Math.min(...rows.map(r => r.top)) : 0)
|
|
73
|
+
+ parseFloat(css.paddingTop) + parseFloat(css.paddingBottom)
|
|
74
|
+
+ parseFloat(css.borderTopWidth) + parseFloat(css.borderBottomWidth);
|
|
75
|
+
const below = Math.max(0, top + height - r.bottom - 2 * gutter);
|
|
76
|
+
const above = Math.max(0, r.top - top - 2 * gutter);
|
|
77
|
+
const flip = below < Math.min(desired, above);
|
|
78
|
+
const menuHeight = Math.min(desired, flip ? above : below);
|
|
79
|
+
nav.style.setProperty('--m-header-menu-height', `${menuHeight}px`);
|
|
80
|
+
nav.style.setProperty('--m-header-menu-top', `${flip ? Math.max(top + gutter, r.top - gutter - menuHeight) : Math.max(top + gutter, r.bottom + gutter)}px`);
|
|
81
|
+
};
|
|
82
|
+
const schedule = () => {
|
|
83
|
+
if (!frame) frame = requestAnimationFrame(position);
|
|
84
|
+
};
|
|
85
|
+
const measure = () => {
|
|
86
|
+
const collapsed = canCollapse && getComputedStyle(button).getPropertyValue('--m-header-collapse').trim() === '1';
|
|
87
|
+
if (collapsed !== this.hasAttribute('data-m-header-collapsed')) {
|
|
88
|
+
const active = document.activeElement;
|
|
89
|
+
const hidingFocus = collapsed && (nav.contains(active) || this.querySelector(':scope > [data-actions]')?.contains(active));
|
|
90
|
+
const triggerFocus = !collapsed && active === button;
|
|
91
|
+
close();
|
|
92
|
+
closeGroups();
|
|
93
|
+
if (collapsed) nav.setAttribute('popover', 'auto');
|
|
94
|
+
else nav.removeAttribute('popover');
|
|
95
|
+
this.toggleAttribute('data-m-header-collapsed', collapsed);
|
|
96
|
+
if (hidingFocus) button.focus();
|
|
97
|
+
else if (!collapsed && nav.contains(active)) {
|
|
98
|
+
const group = groups.find(d => d.contains(active));
|
|
99
|
+
(group ? group.firstElementChild : active).focus();
|
|
100
|
+
}
|
|
101
|
+
else if (triggerFocus) nav.querySelector('a[href], summary')?.focus();
|
|
102
|
+
}
|
|
103
|
+
schedule();
|
|
104
|
+
};
|
|
105
|
+
// beforetoggle fires while the popover is still hidden. Measuring in
|
|
106
|
+
// toggle gives actual link geometry, before the next paint.
|
|
107
|
+
nav.addEventListener('toggle', event => { if (event.target !== nav) return; if (nav.matches(':popover-open')) position(); else closeGroups(); }, { signal });
|
|
108
|
+
nav.addEventListener('click', event => {
|
|
109
|
+
const link = event.target.closest('a[href]');
|
|
110
|
+
if (link && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey) queueMicrotask(() => { if (!event.defaultPrevented) { closeGroups(); close(); } });
|
|
111
|
+
}, { signal });
|
|
112
|
+
window.addEventListener('scroll', schedule, { signal, capture: true, passive: true });
|
|
113
|
+
window.addEventListener('resize', measure, { signal, passive: true });
|
|
114
|
+
window.visualViewport?.addEventListener('resize', schedule, { signal, passive: true });
|
|
115
|
+
window.visualViewport?.addEventListener('scroll', schedule, { signal, passive: true });
|
|
116
|
+
const observer = new ResizeObserver(measure);
|
|
117
|
+
observer.observe(this);
|
|
118
|
+
for (const group of groups) {
|
|
119
|
+
group.addEventListener('toggle', () => {
|
|
120
|
+
if (group.open) for (const other of groups) if (other !== group) other.open = false;
|
|
121
|
+
schedule();
|
|
122
|
+
}, { signal });
|
|
123
|
+
observer.observe(group.querySelector(':scope > [data-nav-panel]'));
|
|
124
|
+
}
|
|
125
|
+
if (groups.length) {
|
|
126
|
+
nav.setAttribute('data-m-navigation-ready', '');
|
|
127
|
+
nav.addEventListener('keydown', event => {
|
|
128
|
+
if (event.key !== 'Escape') return;
|
|
129
|
+
const group = groups.find(d => d.open);
|
|
130
|
+
if (!group) return;
|
|
131
|
+
event.preventDefault();
|
|
132
|
+
event.stopPropagation();
|
|
133
|
+
group.open = false;
|
|
134
|
+
group.firstElementChild.focus();
|
|
135
|
+
}, { signal });
|
|
136
|
+
document.addEventListener('click', event => {
|
|
137
|
+
if (!nav.contains(event.target)) closeGroups();
|
|
138
|
+
}, { signal });
|
|
139
|
+
nav.addEventListener('focusout', event => {
|
|
140
|
+
if (event.relatedTarget) {
|
|
141
|
+
if (!nav.contains(event.relatedTarget)) closeGroups();
|
|
142
|
+
} else requestAnimationFrame(() => {
|
|
143
|
+
if (!signal.aborted && !nav.contains(document.activeElement)) closeGroups();
|
|
144
|
+
});
|
|
145
|
+
}, { signal });
|
|
146
|
+
}
|
|
147
|
+
this.#cleanup = () => {
|
|
148
|
+
controller.abort();
|
|
149
|
+
observer.disconnect();
|
|
150
|
+
cancelAnimationFrame(frame);
|
|
151
|
+
close();
|
|
152
|
+
closeGroups();
|
|
153
|
+
nav.removeAttribute('data-m-navigation-ready');
|
|
154
|
+
for (const group of groups) {
|
|
155
|
+
const panel = group.querySelector(':scope > [data-nav-panel]');
|
|
156
|
+
for (const name of ['left', 'top', 'height', 'max-width']) panel.style.removeProperty(`--m-nav-${name}`);
|
|
157
|
+
}
|
|
158
|
+
nav.removeAttribute('popover');
|
|
159
|
+
this.removeAttribute('data-m-header-collapsed');
|
|
160
|
+
for (const name of ['width', 'left', 'height', 'top']) nav.style.removeProperty(`--m-header-menu-${name}`);
|
|
161
|
+
};
|
|
162
|
+
measure();
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
disconnectedCallback() {
|
|
167
|
+
this.#cleanup?.();
|
|
168
|
+
this.#cleanup = undefined;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!customElements.get('m-header')) customElements.define('m-header', MHeader);
|
package/mica.css
CHANGED
|
@@ -764,6 +764,68 @@
|
|
|
764
764
|
--m-badge-color: var(--color-danger-text);
|
|
765
765
|
}
|
|
766
766
|
|
|
767
|
+
/* Callouts are CSS-only content. Variants never imply role=alert.
|
|
768
|
+
Optional icons and dismissal controls are authored, not generated.
|
|
769
|
+
For a neutral panel with colored emphasis, override --callout-background
|
|
770
|
+
and --callout-border to the shared surface and border roles. */
|
|
771
|
+
:where(m-callout) {
|
|
772
|
+
--callout-background: var(--color-surface-raised);
|
|
773
|
+
--callout-border: var(--color-border);
|
|
774
|
+
--callout-emphasis: var(--color-text);
|
|
775
|
+
display: flex;
|
|
776
|
+
align-items: flex-start;
|
|
777
|
+
gap: var(--callout-gap, var(--space-sm));
|
|
778
|
+
padding: var(--callout-padding, var(--space-md));
|
|
779
|
+
border: 1px solid var(--callout-border);
|
|
780
|
+
border-radius: var(--radius-md);
|
|
781
|
+
background: var(--callout-background);
|
|
782
|
+
color: var(--color-text);
|
|
783
|
+
font-size: var(--callout-font-size, .875rem);
|
|
784
|
+
line-height: var(--callout-line-height, 1.5);
|
|
785
|
+
}
|
|
786
|
+
:where(m-callout[variant="success"]) {
|
|
787
|
+
--callout-background: var(--color-success-surface);
|
|
788
|
+
--callout-border: var(--color-success-border);
|
|
789
|
+
--callout-emphasis: var(--color-success-text);
|
|
790
|
+
}
|
|
791
|
+
:where(m-callout[variant="warning"]) {
|
|
792
|
+
--callout-background: var(--color-warn-surface);
|
|
793
|
+
--callout-border: var(--color-warn-border);
|
|
794
|
+
--callout-emphasis: var(--color-warn-text);
|
|
795
|
+
}
|
|
796
|
+
:where(m-callout[variant="danger"]) {
|
|
797
|
+
--callout-background: var(--color-danger-surface);
|
|
798
|
+
--callout-border: var(--color-danger-border);
|
|
799
|
+
--callout-emphasis: var(--color-danger-text);
|
|
800
|
+
}
|
|
801
|
+
:where(m-callout[hidden]) { display: none; }
|
|
802
|
+
:where(m-callout > [data-callout-body]) { flex: 1; min-inline-size: 0; overflow-wrap: anywhere; }
|
|
803
|
+
:where(m-callout [data-callout-title]) {
|
|
804
|
+
margin: 0 0 var(--space-2xs);
|
|
805
|
+
color: var(--callout-emphasis);
|
|
806
|
+
font: inherit;
|
|
807
|
+
font-weight: var(--callout-title-weight, 600);
|
|
808
|
+
}
|
|
809
|
+
:where(m-callout > [data-callout-body] > p:not([data-callout-title])) { margin: 0; }
|
|
810
|
+
:where(m-callout > [data-callout-body] > p + p) { margin-block-start: var(--space-xs); }
|
|
811
|
+
:where(m-callout a) { color: var(--callout-emphasis); }
|
|
812
|
+
:where(m-callout > [data-callout-icon]) {
|
|
813
|
+
inline-size: var(--callout-icon-size, 1.125rem);
|
|
814
|
+
block-size: var(--callout-icon-size, 1.125rem);
|
|
815
|
+
flex: 0 0 auto;
|
|
816
|
+
margin-block-start: var(--callout-icon-offset, .125rem);
|
|
817
|
+
color: var(--callout-emphasis);
|
|
818
|
+
}
|
|
819
|
+
:where(m-callout [data-callout-actions]) { display: flex; flex-wrap: wrap; gap: var(--space-xs); margin-block-start: var(--space-sm); }
|
|
820
|
+
:where(m-callout > button[data-callout-dismiss]) {
|
|
821
|
+
flex: 0 0 auto;
|
|
822
|
+
inline-size: var(--callout-dismiss-size, 2rem);
|
|
823
|
+
block-size: var(--callout-dismiss-size, 2rem);
|
|
824
|
+
padding: 0;
|
|
825
|
+
margin-block-start: calc(-1 * var(--space-2xs));
|
|
826
|
+
margin-inline-end: calc(-1 * var(--space-2xs));
|
|
827
|
+
}
|
|
828
|
+
|
|
767
829
|
:where(m-badge[count]) {
|
|
768
830
|
--m-badge-min-width: 1.5rem;
|
|
769
831
|
--m-badge-padding: var(--space-2xs);
|
|
@@ -2011,6 +2073,556 @@
|
|
|
2011
2073
|
display: block;
|
|
2012
2074
|
}
|
|
2013
2075
|
|
|
2076
|
+
/* --- header navigation: native links + optional header.js collapse --- */
|
|
2077
|
+
:where(m-header) {
|
|
2078
|
+
container: m-header / inline-size;
|
|
2079
|
+
display: flex;
|
|
2080
|
+
flex-wrap: wrap;
|
|
2081
|
+
align-items: center;
|
|
2082
|
+
gap: var(--header-gap, var(--space-md));
|
|
2083
|
+
padding: var(--header-pad, var(--space-sm)) var(--header-gutter, var(--space-md));
|
|
2084
|
+
border-block-end: 1px solid var(--color-border);
|
|
2085
|
+
background: var(--color-surface);
|
|
2086
|
+
color: var(--color-text);
|
|
2087
|
+
}
|
|
2088
|
+
:where(m-header > [data-brand]) {
|
|
2089
|
+
display: inline-flex;
|
|
2090
|
+
align-items: center;
|
|
2091
|
+
gap: var(--space-xs);
|
|
2092
|
+
min-block-size: var(--control-height);
|
|
2093
|
+
color: var(--color-text);
|
|
2094
|
+
font-weight: var(--header-strong-weight, 650);
|
|
2095
|
+
text-decoration: none;
|
|
2096
|
+
overflow-wrap: anywhere;
|
|
2097
|
+
}
|
|
2098
|
+
:where(m-header > nav) {
|
|
2099
|
+
display: flex;
|
|
2100
|
+
flex: 1 1 auto;
|
|
2101
|
+
flex-wrap: wrap;
|
|
2102
|
+
align-items: center;
|
|
2103
|
+
gap: var(--space-2xs);
|
|
2104
|
+
min-inline-size: 0;
|
|
2105
|
+
justify-content: var(--header-nav-align, start);
|
|
2106
|
+
}
|
|
2107
|
+
:where(m-header[align="end"]) { --header-nav-align: end; }
|
|
2108
|
+
:where(m-header > nav > a) {
|
|
2109
|
+
display: inline-flex;
|
|
2110
|
+
align-items: center;
|
|
2111
|
+
min-block-size: var(--control-height);
|
|
2112
|
+
padding: var(--space-2xs) var(--space-sm);
|
|
2113
|
+
border-radius: var(--radius-md);
|
|
2114
|
+
color: var(--color-text-muted);
|
|
2115
|
+
font-size: var(--header-font-size, .875rem);
|
|
2116
|
+
font-weight: var(--header-font-weight, 500);
|
|
2117
|
+
text-decoration: none;
|
|
2118
|
+
overflow-wrap: anywhere;
|
|
2119
|
+
}
|
|
2120
|
+
:where(m-header > nav > a):hover:not([aria-disabled="true"]) { background: var(--color-surface-raised); color: var(--color-text); }
|
|
2121
|
+
:where(m-header > nav > a)[aria-current="page"]{ background: var(--color-surface-raised); color: var(--color-text); font-weight: var(--header-strong-weight, 650); }
|
|
2122
|
+
:where(m-header > nav > a)[aria-disabled="true"]{ color: var(--color-text-muted); opacity: .5; }
|
|
2123
|
+
:where(m-header > [data-actions]) { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-xs); margin-inline-start: auto; }
|
|
2124
|
+
/* A small enhancement switches the same nav between inline and native
|
|
2125
|
+
popover. Without script, all links stay visible in the wrapping header. */
|
|
2126
|
+
:where(m-header > [data-menu]) { display: none; }
|
|
2127
|
+
:where(m-header > nav > [data-compact]) { display: none; }
|
|
2128
|
+
:where(m-header[data-m-header-collapsed]) { flex-wrap: nowrap; }
|
|
2129
|
+
:where(m-header[data-m-header-collapsed] > [data-brand]) { min-inline-size: 0; }
|
|
2130
|
+
:where(m-header[data-m-header-collapsed] > [data-actions]) { display: none; }
|
|
2131
|
+
:where(m-header[data-m-header-collapsed] > [data-menu]) { display: inline-flex; margin-inline-start: auto; flex: none; }
|
|
2132
|
+
:where(m-header > nav[popover]):not(:popover-open) { display: none; }
|
|
2133
|
+
:where(m-header > nav[popover]):popover-open{
|
|
2134
|
+
display: flex;
|
|
2135
|
+
flex-direction: column;
|
|
2136
|
+
flex-wrap: nowrap;
|
|
2137
|
+
justify-content: start;
|
|
2138
|
+
align-items: stretch;
|
|
2139
|
+
gap: var(--space-2xs);
|
|
2140
|
+
position: fixed;
|
|
2141
|
+
position-area: none;
|
|
2142
|
+
max-inline-size: none;
|
|
2143
|
+
scale: 1;
|
|
2144
|
+
inset: auto;
|
|
2145
|
+
top: var(--m-header-menu-top);
|
|
2146
|
+
left: var(--m-header-menu-left);
|
|
2147
|
+
width: var(--m-header-menu-width);
|
|
2148
|
+
max-block-size: var(--m-header-menu-height);
|
|
2149
|
+
overflow-y: auto;
|
|
2150
|
+
margin: 0;
|
|
2151
|
+
padding: var(--space-xs);
|
|
2152
|
+
border: 1px solid var(--color-border-strong);
|
|
2153
|
+
border-radius: var(--radius-md);
|
|
2154
|
+
background: var(--color-surface-overlay);
|
|
2155
|
+
color: var(--color-text);
|
|
2156
|
+
box-shadow: none;
|
|
2157
|
+
opacity: 1;
|
|
2158
|
+
transform: none;
|
|
2159
|
+
transition: none;
|
|
2160
|
+
animation: none;
|
|
2161
|
+
}
|
|
2162
|
+
:where(m-header > nav[popover] > a) { min-block-size: var(--header-target-height, 2.75rem); flex-shrink: 0; }
|
|
2163
|
+
:where(m-header > nav[popover] > [data-compact]) { display: inline-flex; }
|
|
2164
|
+
:where(m-header > nav[popover] > :not([data-compact]) + [data-compact]) { border-block-start: 1px solid var(--color-border); }
|
|
2165
|
+
:where(m-header > [data-menu] svg) { inline-size: 1rem; block-size: 1rem; }
|
|
2166
|
+
|
|
2167
|
+
|
|
2168
|
+
:where(m-header > [data-menu]) { --m-header-collapse: 0; }
|
|
2169
|
+
@container m-header (inline-size < 40rem) {
|
|
2170
|
+
:where(m-header > [data-menu]) { --m-header-collapse: 1; }
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
|
|
2174
|
+
/* Navigation disclosures: native details remain usable without header.js.
|
|
2175
|
+
Enhancement adds floating placement; mobile keeps content in flow.
|
|
2176
|
+
Panel contents are authored links and layouts, never ARIA menu widgets. */
|
|
2177
|
+
:where(m-header > nav[data-navigation-menu]) {
|
|
2178
|
+
--nav-panel-width: 34rem;
|
|
2179
|
+
--nav-panel-gap: var(--space-xs);
|
|
2180
|
+
--nav-panel-pad: var(--space-xs);
|
|
2181
|
+
--nav-panel-link-pad: var(--space-sm);
|
|
2182
|
+
--nav-panel-description-size: .8125rem;
|
|
2183
|
+
--nav-panel-heading-size: .6875rem;
|
|
2184
|
+
--nav-panel-z: 10;
|
|
2185
|
+
}
|
|
2186
|
+
:where(m-header > nav[data-navigation-menu] > details) { margin: 0; padding: 0; border: 0; background: none; max-inline-size: 100%; }
|
|
2187
|
+
:where(m-header > nav[data-navigation-menu] > details)::details-content { transition: none; overflow: visible; }
|
|
2188
|
+
:where(m-header > nav[data-navigation-menu] > details > summary) {
|
|
2189
|
+
display: flex;
|
|
2190
|
+
align-items: center;
|
|
2191
|
+
gap: var(--space-xs);
|
|
2192
|
+
min-block-size: var(--control-height);
|
|
2193
|
+
padding: var(--space-2xs) var(--space-sm);
|
|
2194
|
+
font-size: var(--header-font-size, .875rem);
|
|
2195
|
+
font-weight: var(--header-font-weight, 500);
|
|
2196
|
+
color: var(--color-text-muted);
|
|
2197
|
+
border-radius: var(--radius-md);
|
|
2198
|
+
}
|
|
2199
|
+
:where(m-header > nav[data-navigation-menu] > details > summary):hover {
|
|
2200
|
+
text-decoration: none; color: var(--color-text); background: var(--color-surface-raised);
|
|
2201
|
+
}
|
|
2202
|
+
:where(m-header > nav[data-navigation-menu] > details)[open] > :where(summary) {
|
|
2203
|
+
color: var(--color-text); background: var(--color-surface-raised);
|
|
2204
|
+
}
|
|
2205
|
+
:where(m-header > nav[data-navigation-menu] [data-nav-panel]) {
|
|
2206
|
+
inline-size: min(var(--nav-panel-width), var(--m-nav-max-width, calc(100vw - 2 * var(--nav-panel-gap))));
|
|
2207
|
+
max-inline-size: 100%;
|
|
2208
|
+
max-block-size: var(--m-nav-height, none);
|
|
2209
|
+
margin: 0;
|
|
2210
|
+
padding: var(--nav-panel-pad);
|
|
2211
|
+
row-gap: var(--nav-panel-gap);
|
|
2212
|
+
border: 1px solid var(--color-border);
|
|
2213
|
+
border-radius: var(--radius-md);
|
|
2214
|
+
background: var(--color-surface-overlay);
|
|
2215
|
+
overflow: auto;
|
|
2216
|
+
}
|
|
2217
|
+
:where(m-header > nav[data-m-navigation-ready] > details > [data-nav-panel]) {
|
|
2218
|
+
position: fixed; z-index: var(--nav-panel-z);
|
|
2219
|
+
left: var(--m-nav-left); top: var(--m-nav-top);
|
|
2220
|
+
}
|
|
2221
|
+
:where(m-header > nav[data-navigation-menu] [data-nav-panel] a) {
|
|
2222
|
+
display: block;
|
|
2223
|
+
padding: var(--nav-panel-link-pad);
|
|
2224
|
+
border-radius: var(--radius-md);
|
|
2225
|
+
color: var(--color-text);
|
|
2226
|
+
text-decoration: none;
|
|
2227
|
+
font-size: var(--header-font-size, .875rem);
|
|
2228
|
+
overflow-wrap: anywhere;
|
|
2229
|
+
}
|
|
2230
|
+
:where(m-header > nav[data-navigation-menu] [data-nav-panel] a):hover:not([aria-disabled="true"]),
|
|
2231
|
+
:where(m-header > nav[data-navigation-menu] [data-nav-panel] a)[aria-current="page"] { background: var(--color-surface-raised); }
|
|
2232
|
+
:where(m-header > nav[data-navigation-menu] [data-nav-panel] a)[aria-disabled="true"] { opacity: .5; }
|
|
2233
|
+
:where(m-header > nav[data-navigation-menu] [data-nav-panel] strong) { display: block; font-weight: var(--header-strong-weight, 650); }
|
|
2234
|
+
:where(m-header > nav[data-navigation-menu] [data-nav-panel] small) {
|
|
2235
|
+
display: block; margin-block-start: var(--space-2xs);
|
|
2236
|
+
font-size: var(--nav-panel-description-size); color: var(--color-text-muted);
|
|
2237
|
+
}
|
|
2238
|
+
:where(m-header > nav[data-navigation-menu] [data-nav-panel] h2) {
|
|
2239
|
+
margin: 0; padding: var(--nav-panel-link-pad);
|
|
2240
|
+
font-size: var(--nav-panel-heading-size); font-weight: var(--header-font-weight, 500); color: var(--color-text-muted);
|
|
2241
|
+
}
|
|
2242
|
+
:where(m-header[data-m-header-collapsed] > nav[data-navigation-menu] > details) { flex-shrink: 0; }
|
|
2243
|
+
:where(m-header[data-m-header-collapsed] > nav[data-navigation-menu] > details > summary) { min-block-size: var(--header-target-height, 2.75rem); }
|
|
2244
|
+
:where(m-header[data-m-header-collapsed] > nav[data-navigation-menu] > details > [data-nav-panel]) {
|
|
2245
|
+
position: static; inline-size: auto; max-block-size: none;
|
|
2246
|
+
margin: var(--space-xs) var(--space-sm); padding: 0 0 0 var(--space-xs);
|
|
2247
|
+
padding-inline: var(--space-xs) 0;
|
|
2248
|
+
border: 0; border-inline-start: 1px solid var(--color-border);
|
|
2249
|
+
border-radius: 0; background: transparent; overflow: visible;
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
/* Data collections: application-owned rows and state, native table semantics.
|
|
2253
|
+
Column widths and sticky offsets are authored for the application's schema.
|
|
2254
|
+
Selection actions replace filters; keep filters inert while selection is active.
|
|
2255
|
+
For a taller action set, increase --table-toolbar-min-height to reserve space. */
|
|
2256
|
+
:where(:root) {
|
|
2257
|
+
--table-font-size: .8125rem;
|
|
2258
|
+
--table-padding: var(--space-lg);
|
|
2259
|
+
--table-cell-padding: var(--space-sm);
|
|
2260
|
+
--table-min-width: 54rem;
|
|
2261
|
+
--table-toolbar-min-height: 0px;
|
|
2262
|
+
--table-selected-background: var(--color-surface-raised);
|
|
2263
|
+
--table-edge-width: var(--space-md);
|
|
2264
|
+
--table-edge-color: color-mix(in oklch, var(--color-text) 14%, transparent);
|
|
2265
|
+
--table-state-padding: var(--space-2xl);
|
|
2266
|
+
}
|
|
2267
|
+
:where([data-data-table]) { container: mica-table / inline-size; background: var(--color-surface); border: 1px solid var(--color-border); min-inline-size: 0; }
|
|
2268
|
+
:where([data-table-heading]) { display:flex; justify-content:space-between; align-items:center; gap:var(--space-md); padding:var(--table-padding) var(--table-padding) var(--space-sm); }
|
|
2269
|
+
:where([data-table-heading] > button) { flex-shrink:0; white-space:nowrap; }
|
|
2270
|
+
:where([data-table-heading] h2) { margin:0; font-size:var(--table-heading-size, 1.375rem); }
|
|
2271
|
+
:where([data-table-heading] p) { margin:var(--space-2xs) 0 0; color:var(--color-text-muted); font-size:var(--table-font-size); }
|
|
2272
|
+
:where([data-table-toolbar]) { position:relative; min-block-size:var(--table-toolbar-min-height); }
|
|
2273
|
+
:where([data-table-filters], [data-table-selection]) { display:flex; flex-wrap:wrap; align-items:center; gap:var(--space-xs); padding:var(--space-sm) var(--table-padding); }
|
|
2274
|
+
:where([data-table-filters] [data-table-search]) { flex:1; min-inline-size:12rem; }
|
|
2275
|
+
:where([data-table-search] input) { inline-size:100%; }
|
|
2276
|
+
:where([data-table-filters] select) { max-inline-size:11rem; }
|
|
2277
|
+
:where([data-table-filters] button) { white-space:nowrap; }
|
|
2278
|
+
:where([data-table-selection]) { position:absolute; inset:0; align-content:center; gap:var(--space-sm); font-size:var(--table-font-size); }
|
|
2279
|
+
:where([data-table-toolbar]):has(> [data-table-selection]:not([hidden])) > :where([data-table-filters]) { visibility:hidden; }
|
|
2280
|
+
:where([data-table-selection] [data-table-clear]) { margin-inline-start:auto; }
|
|
2281
|
+
:where([data-data-table] [hidden], [data-pagination] [hidden], [data-table-selection][hidden], [data-table-filters] [hidden], [data-table] [hidden]) { display:none; }
|
|
2282
|
+
:where(m-table-scroll) { display:block; position:relative; min-inline-size:0; }
|
|
2283
|
+
:where(m-table-scroll)::before, :where(m-table-scroll)::after { content:''; position:absolute; inset-block:1px; inline-size:var(--table-edge-width); z-index:4; pointer-events:none; opacity:0; }
|
|
2284
|
+
:where(m-table-scroll)::before { left:var(--m-table-edge-left, 0px); background:linear-gradient(to right,var(--table-edge-color),transparent); }
|
|
2285
|
+
:where(m-table-scroll)::after { right:var(--m-table-edge-right, 0px); background:linear-gradient(to left,var(--table-edge-color),transparent); }
|
|
2286
|
+
:where(m-table-scroll[data-m-overflow-left])::before, :where(m-table-scroll[data-m-overflow-right])::after { opacity:1; }
|
|
2287
|
+
:where([data-table-region]) { position:relative; overflow:auto; overscroll-behavior:contain; border-block:1px solid var(--color-border); }
|
|
2288
|
+
:where(table[data-table]) { margin:0; inline-size:100%; min-inline-size:var(--table-min-width, 54rem); border-collapse:separate; border-spacing:0; table-layout:fixed; }
|
|
2289
|
+
:where([data-table] th, [data-table] td) { padding:var(--table-cell-padding, var(--space-sm)); vertical-align:middle; font-size:var(--table-font-size, .8125rem); }
|
|
2290
|
+
:where([data-table] thead th) { background:var(--color-surface-raised); color:var(--color-text-muted); font-weight:500; border-block-end:1px solid var(--color-border); }
|
|
2291
|
+
:where([data-table] th button) { display:inline-flex; align-items:center; gap:var(--space-xs); padding:0; min-block-size:var(--table-sort-target, 2rem); border:0; background:none; color:inherit; font:inherit; text-align:inherit; white-space:nowrap; }
|
|
2292
|
+
:where([data-table] th[aria-sort]) button { color:var(--color-text); }
|
|
2293
|
+
:where([data-table] td a) { color:var(--color-text); font-weight:600; text-decoration:none; }
|
|
2294
|
+
:where([data-table] td) a:hover { text-decoration:underline; }
|
|
2295
|
+
:where([data-table-description]) { display:block; color:var(--color-text-muted); margin-block-start:var(--space-2xs); font-size:var(--table-description-size, .75rem); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
2296
|
+
:where([data-table] tbody tr:last-child td) { border-block-end:0; }
|
|
2297
|
+
:where([data-table] tbody tr[data-selected]) > td { background:var(--table-selected-background, var(--color-surface-raised)); }
|
|
2298
|
+
:where([data-table-state]) { padding:var(--table-state-padding, var(--space-2xl)) var(--table-padding, var(--space-lg)); text-align:center; white-space:normal; }
|
|
2299
|
+
:where([data-table-state] strong) { display:block; color:var(--color-text); font-size:var(--table-state-font-size, 1rem); }
|
|
2300
|
+
:where([data-table-state] p) { margin-block:var(--space-xs) var(--space-md); color:var(--color-text-muted); }
|
|
2301
|
+
:where([data-table-region][data-state] table) { min-inline-size:100%; table-layout:auto; }
|
|
2302
|
+
:where([data-table-region][data-state] thead) { display:none; }
|
|
2303
|
+
:where([data-table-footer]) { container: mica-pagination / inline-size; display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:var(--space-md); padding:var(--space-md) var(--table-padding); font-size:var(--table-font-size); }
|
|
2304
|
+
:where([data-page-meta]) { display:flex; align-items:center; gap:var(--space-md); flex-wrap:wrap; color:var(--color-text-muted); }
|
|
2305
|
+
:where([data-page-size]) { display:flex; align-items:center; gap:var(--space-xs); }
|
|
2306
|
+
:where([data-page-size] select) { inline-size:auto; min-block-size:2rem; padding-block:var(--space-2xs); font-size:1rem; }
|
|
2307
|
+
:where(nav[data-pagination]) { --pagination-target:2.125rem; --pagination-font-size:.8125rem; display:flex; flex-wrap:wrap; align-items:center; gap:var(--space-2xs); }
|
|
2308
|
+
:where([data-pagination] > a, [data-pagination] > button, [data-pagination] > [aria-disabled]) { display:inline-flex; justify-content:center; align-items:center; min-inline-size:var(--pagination-target); min-block-size:var(--pagination-target); padding:var(--space-2xs) var(--space-xs); border:0; background:transparent; color:var(--color-text); font-size:var(--pagination-font-size); text-decoration:none; }
|
|
2309
|
+
:where([data-pagination]) > a:hover { background:var(--color-surface-raised); }
|
|
2310
|
+
:where([data-pagination]) > [aria-current=page] { background:var(--color-surface-raised); font-weight:650; color:var(--color-text); }
|
|
2311
|
+
:where([data-pagination]) > :is([aria-disabled=true], :disabled) { color:var(--color-text-muted); cursor:default; }
|
|
2312
|
+
:where([data-page-status]) { display:none; color:var(--color-text-muted); font-size:var(--pagination-font-size); }
|
|
2313
|
+
@container mica-table (max-width:44rem) {
|
|
2314
|
+
:where([data-table-heading]) { padding:var(--space-md); }
|
|
2315
|
+
:where([data-table-filters]) { padding:0 var(--space-md) var(--space-sm); }
|
|
2316
|
+
:where([data-table-search]) { flex-basis:100%; }
|
|
2317
|
+
:where([data-table-filters] select) { flex:1; min-inline-size:0; max-inline-size:none; }
|
|
2318
|
+
:where([data-table-selection]) { padding-inline:var(--space-md); }
|
|
2319
|
+
:where([data-table-footer]) { padding:var(--space-md); gap:var(--space-sm); }
|
|
2320
|
+
:where([data-table] [data-table-sticky]) { position:sticky; inset-inline-start:var(--table-sticky-offset, 0px); background:var(--color-surface); z-index:1; }
|
|
2321
|
+
:where([data-table] thead [data-table-sticky]) { background:var(--color-surface-raised); z-index:3; }
|
|
2322
|
+
}
|
|
2323
|
+
/* A compact status is opt-in: include data-page-status to let numbered
|
|
2324
|
+
links collapse. Without it, every authored page link remains visible. */
|
|
2325
|
+
@container mica-pagination (max-width:40rem) {
|
|
2326
|
+
:where([data-page-meta]) { inline-size:100%; justify-content:space-between; gap:var(--space-xs); }
|
|
2327
|
+
:where([data-pagination]):has([data-page-status]) { inline-size:100%; justify-content:space-between; }
|
|
2328
|
+
:where([data-pagination]):has([data-page-status]) > :is([data-page-number], [data-page-ellipsis]) { display:none; }
|
|
2329
|
+
:where([data-pagination] [data-page-status]) { display:block; }
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
/* Breadcrumbs: native hierarchy with decorative separators. The current
|
|
2333
|
+
page is plain text; nothing is automatically removed on narrow screens.
|
|
2334
|
+
Override --breadcrumb-current-weight or --breadcrumb-separator-color
|
|
2335
|
+
to tune emphasis without changing the authored trail. */
|
|
2336
|
+
:where(nav[data-breadcrumbs]) {
|
|
2337
|
+
--breadcrumb-font-size: .8125rem;
|
|
2338
|
+
--breadcrumb-gap: var(--space-xs);
|
|
2339
|
+
--breadcrumb-row-gap: var(--space-2xs);
|
|
2340
|
+
--breadcrumb-current-weight: 500;
|
|
2341
|
+
--breadcrumb-separator-size: .3rem;
|
|
2342
|
+
--breadcrumb-separator-color: var(--color-text-muted);
|
|
2343
|
+
--breadcrumb-menu-width: 14rem;
|
|
2344
|
+
--breadcrumb-overflow-font-size: 1rem;
|
|
2345
|
+
--breadcrumb-menu-target: 2.25rem;
|
|
2346
|
+
--breadcrumb-menu-z: 5;
|
|
2347
|
+
font-size: var(--breadcrumb-font-size);
|
|
2348
|
+
line-height: 1.5;
|
|
2349
|
+
color: var(--color-text-muted);
|
|
2350
|
+
}
|
|
2351
|
+
:where(nav[data-breadcrumbs] ol) {
|
|
2352
|
+
display: flex; align-items: center; flex-wrap: wrap;
|
|
2353
|
+
list-style: none; margin: 0; padding: 0;
|
|
2354
|
+
column-gap: var(--breadcrumb-gap); row-gap: var(--breadcrumb-row-gap);
|
|
2355
|
+
}
|
|
2356
|
+
:where(nav[data-breadcrumbs] li) {
|
|
2357
|
+
display: flex; align-items: center; gap: var(--breadcrumb-gap);
|
|
2358
|
+
min-inline-size: 0; max-inline-size: 100%;
|
|
2359
|
+
}
|
|
2360
|
+
:where(nav[data-breadcrumbs] > ol > li + li)::before {
|
|
2361
|
+
content: ''; display: block; flex: none;
|
|
2362
|
+
inline-size: var(--breadcrumb-separator-size); block-size: var(--breadcrumb-separator-size);
|
|
2363
|
+
border: solid var(--breadcrumb-separator-color); border-width: 1px 1px 0 0;
|
|
2364
|
+
transform: rotate(45deg); margin-inline: var(--space-2xs);
|
|
2365
|
+
}
|
|
2366
|
+
:where(nav[data-breadcrumbs]:dir(rtl) > ol > li + li)::before { transform: rotate(225deg); }
|
|
2367
|
+
:where(nav[data-breadcrumbs] a) {
|
|
2368
|
+
color: var(--color-text-muted); text-decoration: none;
|
|
2369
|
+
overflow-wrap: anywhere; padding-block: var(--space-2xs);
|
|
2370
|
+
}
|
|
2371
|
+
:where(nav[data-breadcrumbs] a):hover { text-decoration: underline; color: var(--color-text); }
|
|
2372
|
+
:where(nav[data-breadcrumbs] [aria-current="page"]) {
|
|
2373
|
+
color: var(--color-text); font-weight: var(--breadcrumb-current-weight);
|
|
2374
|
+
overflow-wrap: anywhere; padding-block: var(--space-2xs);
|
|
2375
|
+
}
|
|
2376
|
+
:where(m-breadcrumb-overflow) { display: block; min-inline-size: 0; }
|
|
2377
|
+
:where(nav[data-breadcrumbs] details) { position: relative; margin: 0; border: 0; padding: 0; flex: none; }
|
|
2378
|
+
:where(nav[data-breadcrumbs] summary) {
|
|
2379
|
+
display: grid; place-items: center;
|
|
2380
|
+
inline-size: var(--breadcrumb-menu-target); min-block-size: var(--breadcrumb-menu-target);
|
|
2381
|
+
padding: 0; list-style: none; font-size: var(--breadcrumb-overflow-font-size);
|
|
2382
|
+
font-weight: var(--breadcrumb-current-weight); border-radius: var(--radius-md);
|
|
2383
|
+
color: var(--color-text-muted); cursor: pointer;
|
|
2384
|
+
}
|
|
2385
|
+
:where(nav[data-breadcrumbs] summary)::-webkit-details-marker { display: none; }
|
|
2386
|
+
:where(nav[data-breadcrumbs] summary)::after { display: none; }
|
|
2387
|
+
:where(nav[data-breadcrumbs] summary):hover,
|
|
2388
|
+
:where(nav[data-breadcrumbs] details)[open] > :where(summary) {
|
|
2389
|
+
background: var(--color-surface-raised); color: var(--color-text); text-decoration: none;
|
|
2390
|
+
}
|
|
2391
|
+
:where(nav[data-breadcrumbs] details)::details-content { transition: none; overflow: visible; }
|
|
2392
|
+
:where(nav[data-breadcrumbs] [data-breadcrumb-menu]) {
|
|
2393
|
+
position: absolute; inset-block-start: 100%; inset-inline-start: 0;
|
|
2394
|
+
inline-size: var(--breadcrumb-menu-width); max-inline-size: calc(100vw - var(--space-2xl));
|
|
2395
|
+
max-block-size: var(--m-breadcrumb-height, none); overflow: auto;
|
|
2396
|
+
z-index: var(--breadcrumb-menu-z); display: flex; flex-direction: column;
|
|
2397
|
+
align-items: stretch; flex-wrap: nowrap; gap: 0;
|
|
2398
|
+
margin: var(--space-2xs) 0 0; padding: var(--space-xs);
|
|
2399
|
+
border: 1px solid var(--color-border); border-radius: var(--radius-md);
|
|
2400
|
+
background: var(--color-surface-overlay);
|
|
2401
|
+
}
|
|
2402
|
+
:where(m-breadcrumb-overflow[data-m-breadcrumb-ready] [data-breadcrumb-menu]) {
|
|
2403
|
+
position: fixed; inset: auto; left: var(--m-breadcrumb-left); top: var(--m-breadcrumb-top);
|
|
2404
|
+
}
|
|
2405
|
+
:where(nav[data-breadcrumbs] [data-breadcrumb-menu] li) { display: block; max-inline-size: none; flex: none; }
|
|
2406
|
+
:where(nav[data-breadcrumbs] [data-breadcrumb-menu] a) {
|
|
2407
|
+
display: block; padding: var(--space-xs); min-block-size: var(--breadcrumb-menu-target);
|
|
2408
|
+
}
|
|
2409
|
+
:where(nav[data-breadcrumbs] [data-breadcrumb-menu] a):hover { background: var(--color-surface-raised); text-decoration: none; }
|
|
2410
|
+
|
|
2411
|
+
/* Bottom navigation is ordinary site navigation, not a tab widget.
|
|
2412
|
+
Place it in the last row of an app shell; no fixed positioning or
|
|
2413
|
+
breakpoint is imposed. For emphasis without fill, override
|
|
2414
|
+
--bottom-nav-active-background: transparent on the nav. */
|
|
2415
|
+
:where(nav[data-bottom-nav]) {
|
|
2416
|
+
--bottom-nav-pad: var(--space-xs);
|
|
2417
|
+
--bottom-nav-gap: var(--space-2xs);
|
|
2418
|
+
--bottom-nav-target-height: 3.5rem;
|
|
2419
|
+
--bottom-nav-font-size: .75rem;
|
|
2420
|
+
--bottom-nav-font-weight: 500;
|
|
2421
|
+
--bottom-nav-active-weight: 650;
|
|
2422
|
+
--bottom-nav-icon-size: 1.125rem;
|
|
2423
|
+
--bottom-nav-active-background: var(--color-surface-raised);
|
|
2424
|
+
display: flex;
|
|
2425
|
+
gap: var(--bottom-nav-gap);
|
|
2426
|
+
padding: var(--bottom-nav-pad);
|
|
2427
|
+
padding-block-end: max(var(--bottom-nav-pad), env(safe-area-inset-bottom));
|
|
2428
|
+
/* Safe-area insets are physical, even in an RTL document. */
|
|
2429
|
+
padding-left: max(var(--bottom-nav-pad), env(safe-area-inset-left));
|
|
2430
|
+
padding-right: max(var(--bottom-nav-pad), env(safe-area-inset-right));
|
|
2431
|
+
border-block-start: 1px solid var(--color-border);
|
|
2432
|
+
background: var(--color-surface);
|
|
2433
|
+
}
|
|
2434
|
+
:where(nav[data-bottom-nav]:not(:has([data-bottom-icon]))) {
|
|
2435
|
+
--bottom-nav-target-height: 2.75rem;
|
|
2436
|
+
--bottom-nav-font-size: .8125rem;
|
|
2437
|
+
}
|
|
2438
|
+
:where(nav[data-bottom-nav] > a) {
|
|
2439
|
+
display: flex;
|
|
2440
|
+
flex: 1;
|
|
2441
|
+
flex-direction: column;
|
|
2442
|
+
justify-content: center;
|
|
2443
|
+
align-items: center;
|
|
2444
|
+
gap: var(--bottom-nav-gap);
|
|
2445
|
+
min-inline-size: 0;
|
|
2446
|
+
min-block-size: var(--bottom-nav-target-height);
|
|
2447
|
+
padding: var(--space-xs) var(--space-2xs);
|
|
2448
|
+
border-radius: var(--radius-md);
|
|
2449
|
+
color: var(--color-text-muted);
|
|
2450
|
+
font-size: var(--bottom-nav-font-size);
|
|
2451
|
+
font-weight: var(--bottom-nav-font-weight);
|
|
2452
|
+
line-height: 1.25;
|
|
2453
|
+
text-align: center;
|
|
2454
|
+
text-decoration: none;
|
|
2455
|
+
overflow-wrap: anywhere;
|
|
2456
|
+
}
|
|
2457
|
+
:where(nav[data-bottom-nav] > a):hover:not([aria-disabled="true"]) {
|
|
2458
|
+
background: var(--color-surface-raised);
|
|
2459
|
+
color: var(--color-text);
|
|
2460
|
+
}
|
|
2461
|
+
:where(nav[data-bottom-nav] > a)[aria-current="page"] {
|
|
2462
|
+
background: var(--bottom-nav-active-background);
|
|
2463
|
+
color: var(--color-text);
|
|
2464
|
+
font-weight: var(--bottom-nav-active-weight);
|
|
2465
|
+
}
|
|
2466
|
+
:where(nav[data-bottom-nav] > a)[aria-disabled="true"] { opacity: .45; cursor: default; }
|
|
2467
|
+
:where(nav[data-bottom-nav] [data-bottom-icon]) {
|
|
2468
|
+
inline-size: var(--bottom-nav-icon-size);
|
|
2469
|
+
block-size: var(--bottom-nav-icon-size);
|
|
2470
|
+
flex: none;
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
/* --- sidebar: authored navigation, optional sidebar.js shell behavior --- */
|
|
2474
|
+
:where(m-sidebar-layout) {
|
|
2475
|
+
--sidebar-width: 15.5rem;
|
|
2476
|
+
--sidebar-rail-width: 3.75rem;
|
|
2477
|
+
--sidebar-mobile-width: 18rem;
|
|
2478
|
+
--sidebar-pad: var(--space-xs);
|
|
2479
|
+
--sidebar-icon-size: 1rem;
|
|
2480
|
+
--sidebar-font-size: .875rem;
|
|
2481
|
+
--sidebar-group-size: .6875rem;
|
|
2482
|
+
--sidebar-subnav-size: .8125rem;
|
|
2483
|
+
--sidebar-active-weight: 600;
|
|
2484
|
+
--sidebar-close-size: 2.75rem;
|
|
2485
|
+
--sidebar-inset: 0px;
|
|
2486
|
+
--sidebar-content-border: 0px;
|
|
2487
|
+
--sidebar-edge: 1px;
|
|
2488
|
+
display: grid;
|
|
2489
|
+
grid-template-columns: var(--m-sidebar-track, var(--sidebar-width)) minmax(0, 1fr);
|
|
2490
|
+
block-size: var(--sidebar-height, 100svh);
|
|
2491
|
+
min-block-size: 0;
|
|
2492
|
+
background: var(--color-surface-raised);
|
|
2493
|
+
}
|
|
2494
|
+
:where(m-sidebar-layout[variant="inset"]) {
|
|
2495
|
+
--sidebar-inset: var(--space-xs);
|
|
2496
|
+
--sidebar-content-border: 1px;
|
|
2497
|
+
--sidebar-edge: 0px;
|
|
2498
|
+
}
|
|
2499
|
+
:where(m-sidebar-layout > [data-sidebar-main]) {
|
|
2500
|
+
min-inline-size: 0;
|
|
2501
|
+
min-block-size: 0;
|
|
2502
|
+
overflow: auto;
|
|
2503
|
+
margin-block: var(--sidebar-inset);
|
|
2504
|
+
margin-inline: 0 var(--sidebar-inset);
|
|
2505
|
+
border: var(--sidebar-content-border) solid var(--color-border);
|
|
2506
|
+
background: var(--color-surface);
|
|
2507
|
+
}
|
|
2508
|
+
:where(m-sidebar) {
|
|
2509
|
+
display: flex;
|
|
2510
|
+
flex-direction: column;
|
|
2511
|
+
min-inline-size: 0;
|
|
2512
|
+
min-block-size: 0;
|
|
2513
|
+
block-size: 100%;
|
|
2514
|
+
padding: var(--sidebar-pad, var(--space-xs));
|
|
2515
|
+
border-inline-end: var(--sidebar-edge, 1px) solid var(--color-border);
|
|
2516
|
+
background: var(--color-surface-raised);
|
|
2517
|
+
}
|
|
2518
|
+
:where(m-sidebar > header, m-sidebar > footer) { flex: none; }
|
|
2519
|
+
:where(m-sidebar > header) { display: flex; align-items: center; }
|
|
2520
|
+
:where(m-sidebar > nav) {
|
|
2521
|
+
flex: 1 1 auto;
|
|
2522
|
+
min-block-size: 0;
|
|
2523
|
+
overflow-y: auto;
|
|
2524
|
+
overscroll-behavior: contain;
|
|
2525
|
+
padding-block: var(--space-md);
|
|
2526
|
+
}
|
|
2527
|
+
:where(m-sidebar > nav section) { margin: 0 0 var(--space-lg); }
|
|
2528
|
+
:where(m-sidebar > nav h2) {
|
|
2529
|
+
margin: 0;
|
|
2530
|
+
padding: var(--space-xs);
|
|
2531
|
+
font-size: var(--sidebar-group-size, .6875rem);
|
|
2532
|
+
font-weight: 500;
|
|
2533
|
+
color: var(--color-text-muted);
|
|
2534
|
+
}
|
|
2535
|
+
:where(m-sidebar [data-sidebar-item], m-sidebar [data-sidebar-subnav] > a) {
|
|
2536
|
+
display: flex;
|
|
2537
|
+
align-items: center;
|
|
2538
|
+
gap: var(--space-xs);
|
|
2539
|
+
min-block-size: var(--control-height);
|
|
2540
|
+
block-size: auto;
|
|
2541
|
+
padding: var(--space-xs);
|
|
2542
|
+
border: 0;
|
|
2543
|
+
border-radius: var(--radius-md);
|
|
2544
|
+
background: transparent;
|
|
2545
|
+
color: var(--color-text-muted);
|
|
2546
|
+
font-size: var(--sidebar-font-size, .875rem);
|
|
2547
|
+
text-decoration: none;
|
|
2548
|
+
text-align: start;
|
|
2549
|
+
}
|
|
2550
|
+
:where(m-sidebar [data-sidebar-item], m-sidebar [data-sidebar-subnav] > a):hover:not(:disabled, [aria-disabled="true"]) {
|
|
2551
|
+
color: var(--color-text);
|
|
2552
|
+
background: var(--color-surface);
|
|
2553
|
+
}
|
|
2554
|
+
:where(m-sidebar a)[aria-current="page"] {
|
|
2555
|
+
color: var(--color-text);
|
|
2556
|
+
background: var(--color-surface);
|
|
2557
|
+
font-weight: var(--sidebar-active-weight, 600);
|
|
2558
|
+
/* Active is a quiet fill, never a framed button. */
|
|
2559
|
+
box-shadow: none;
|
|
2560
|
+
}
|
|
2561
|
+
:where(m-sidebar [data-sidebar-item])[aria-disabled="true"] { opacity: .45; }
|
|
2562
|
+
:where(m-sidebar [data-sidebar-icon]) {
|
|
2563
|
+
inline-size: var(--sidebar-icon-size, 1rem);
|
|
2564
|
+
block-size: var(--sidebar-icon-size, 1rem);
|
|
2565
|
+
flex: none;
|
|
2566
|
+
object-fit: contain;
|
|
2567
|
+
}
|
|
2568
|
+
:where(m-sidebar [data-sidebar-label]) { flex: 1; min-inline-size: 0; }
|
|
2569
|
+
:where(m-sidebar [data-sidebar-badge]) { margin-inline-start: auto; color: var(--color-text-muted); font-size: var(--sidebar-subnav-size, .8125rem); }
|
|
2570
|
+
:where(m-sidebar > header > [data-sidebar-item], m-sidebar > footer > [data-sidebar-item]) { inline-size: 100%; }
|
|
2571
|
+
:where(m-sidebar details) { margin: 0; padding: 0; border: 0; background: none; }
|
|
2572
|
+
:where(m-sidebar summary) { cursor: pointer; list-style: none; }
|
|
2573
|
+
:where(m-sidebar summary)::-webkit-details-marker { display: none; }
|
|
2574
|
+
:where(m-sidebar summary)::after { display: none; }
|
|
2575
|
+
:where(m-sidebar [data-sidebar-chevron]) { inline-size: var(--space-sm); block-size: var(--space-sm); flex: none; }
|
|
2576
|
+
:where(m-sidebar details)[open] > :where(summary) > :where([data-sidebar-chevron]) { transform: rotate(90deg); }
|
|
2577
|
+
:where(m-sidebar [data-sidebar-subnav]) {
|
|
2578
|
+
margin: var(--space-2xs) 0 var(--space-xs) var(--sidebar-icon-size, 1rem);
|
|
2579
|
+
margin-inline: var(--sidebar-icon-size, 1rem) 0;
|
|
2580
|
+
padding: 0;
|
|
2581
|
+
padding-inline-start: var(--space-md);
|
|
2582
|
+
border-inline-start: 1px solid var(--color-border);
|
|
2583
|
+
}
|
|
2584
|
+
:where(m-sidebar [data-sidebar-subnav] > a) { font-size: var(--sidebar-subnav-size, .8125rem); }
|
|
2585
|
+
:where([data-sidebar-toggle], [data-sidebar-close]) { display: none; }
|
|
2586
|
+
:where(m-sidebar-layout[data-m-sidebar-rail]) { --m-sidebar-track: var(--sidebar-rail-width); }
|
|
2587
|
+
:where(m-sidebar-layout[data-m-sidebar-rail] m-sidebar) :where([data-sidebar-label], [data-sidebar-badge], h2, [data-sidebar-chevron], [data-sidebar-subnav]) { display: none; }
|
|
2588
|
+
:where(m-sidebar-layout[data-m-sidebar-rail] m-sidebar [data-sidebar-item]) { justify-content: center; }
|
|
2589
|
+
:where(m-sidebar-layout[data-m-sidebar-rail] m-sidebar details:has([aria-current="page"]) > summary) { color: var(--color-text); background: var(--color-surface); }
|
|
2590
|
+
:where(m-sidebar-layout[data-m-sidebar-can-rail] [data-sidebar-toggle], m-sidebar-layout[data-m-sidebar-mobile] [data-sidebar-toggle]) { display: inline-flex; }
|
|
2591
|
+
:where(m-sidebar-layout[data-m-sidebar-mobile]) { grid-template-columns: minmax(0, 1fr); --sidebar-inset: 0px; --sidebar-content-border: 0px; }
|
|
2592
|
+
:where(dialog[data-sidebar-dialog]) {
|
|
2593
|
+
position: fixed;
|
|
2594
|
+
inset: 0 auto 0 0;
|
|
2595
|
+
inset-inline: 0 auto;
|
|
2596
|
+
margin: 0;
|
|
2597
|
+
padding: 0;
|
|
2598
|
+
inline-size: min(var(--sidebar-mobile-width, 18rem), calc(100vw - var(--space-2xl)));
|
|
2599
|
+
max-inline-size: none;
|
|
2600
|
+
block-size: 100dvh;
|
|
2601
|
+
max-block-size: 100dvh;
|
|
2602
|
+
border: 0;
|
|
2603
|
+
border-radius: 0;
|
|
2604
|
+
background: var(--color-surface-raised);
|
|
2605
|
+
opacity: 1;
|
|
2606
|
+
scale: 1;
|
|
2607
|
+
transform: none;
|
|
2608
|
+
transition: none;
|
|
2609
|
+
animation: none;
|
|
2610
|
+
}
|
|
2611
|
+
:where(dialog[data-sidebar-dialog])::backdrop { transition: none; }
|
|
2612
|
+
:where(dialog[data-sidebar-dialog] > m-sidebar) {
|
|
2613
|
+
flex: 1 1 auto;
|
|
2614
|
+
overflow: hidden;
|
|
2615
|
+
border: 0;
|
|
2616
|
+
padding: var(--sidebar-pad, var(--space-xs));
|
|
2617
|
+
padding-block-start: max(var(--sidebar-pad, var(--space-xs)), env(safe-area-inset-top));
|
|
2618
|
+
padding-block-end: max(var(--sidebar-pad, var(--space-xs)), env(safe-area-inset-bottom));
|
|
2619
|
+
}
|
|
2620
|
+
:where(dialog[data-sidebar-dialog] m-sidebar > header > [data-sidebar-item]) { flex: 1; inline-size: 0; min-inline-size: 0; }
|
|
2621
|
+
:where(dialog[data-sidebar-dialog] [data-sidebar-close]) { display: inline-flex; flex: none; inline-size: var(--sidebar-close-size, 2.75rem); block-size: var(--sidebar-close-size, 2.75rem); padding: var(--space-xs); }
|
|
2622
|
+
@media (max-width: 44rem) {
|
|
2623
|
+
:where(m-sidebar-layout:not([data-m-sidebar-ready])) { grid-template-columns: minmax(0, 1fr); block-size: auto; }
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2014
2626
|
/* --- visually hidden: off-screen, still announced ------------------ *
|
|
2015
2627
|
*
|
|
2016
2628
|
* The one utility mica ships, and an accessibility primitive rather
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akonwi/mica",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Custom elements. Native behavior. Nearly no JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"css",
|
|
@@ -55,6 +55,22 @@
|
|
|
55
55
|
},
|
|
56
56
|
"./types/react": {
|
|
57
57
|
"types": "./types/react.d.ts"
|
|
58
|
+
},
|
|
59
|
+
"./header.js": {
|
|
60
|
+
"types": "./header.d.ts",
|
|
61
|
+
"default": "./header.js"
|
|
62
|
+
},
|
|
63
|
+
"./sidebar.js": {
|
|
64
|
+
"types": "./sidebar.d.ts",
|
|
65
|
+
"default": "./sidebar.js"
|
|
66
|
+
},
|
|
67
|
+
"./breadcrumbs.js": {
|
|
68
|
+
"types": "./breadcrumbs.d.ts",
|
|
69
|
+
"default": "./breadcrumbs.js"
|
|
70
|
+
},
|
|
71
|
+
"./table.js": {
|
|
72
|
+
"types": "./table.d.ts",
|
|
73
|
+
"default": "./table.js"
|
|
58
74
|
}
|
|
59
75
|
},
|
|
60
76
|
"files": [
|
|
@@ -76,7 +92,15 @@
|
|
|
76
92
|
"combobox.js",
|
|
77
93
|
"combobox.d.ts",
|
|
78
94
|
"types/react.d.ts",
|
|
79
|
-
"THIRD_PARTY_LICENSES.md"
|
|
95
|
+
"THIRD_PARTY_LICENSES.md",
|
|
96
|
+
"header.js",
|
|
97
|
+
"header.d.ts",
|
|
98
|
+
"sidebar.js",
|
|
99
|
+
"sidebar.d.ts",
|
|
100
|
+
"breadcrumbs.js",
|
|
101
|
+
"breadcrumbs.d.ts",
|
|
102
|
+
"table.js",
|
|
103
|
+
"table.d.ts"
|
|
80
104
|
],
|
|
81
105
|
"sideEffects": true,
|
|
82
106
|
"devDependencies": {
|
|
@@ -93,6 +117,13 @@
|
|
|
93
117
|
"docs:serve": "bun run docs:build && bun tools/serve.ts",
|
|
94
118
|
"snapshot": "bun tools/snapshot.ts",
|
|
95
119
|
"snapshot:check": "bun tools/snapshot.ts --check",
|
|
96
|
-
"snapshot:setup": "playwright install chromium webkit"
|
|
120
|
+
"snapshot:setup": "playwright install chromium webkit",
|
|
121
|
+
"header:check": "bun tools/header-check.ts",
|
|
122
|
+
"sidebar:check": "bun tools/sidebar-check.ts",
|
|
123
|
+
"navigation-menu:check": "bun tools/navigation-menu-check.ts",
|
|
124
|
+
"bottom-navigation:check": "bun tools/bottom-navigation-check.ts",
|
|
125
|
+
"breadcrumbs:check": "bun tools/breadcrumbs-check.ts",
|
|
126
|
+
"data-table:check": "bun tools/data-table-check.ts",
|
|
127
|
+
"callout:check": "bun tools/callout-check.ts"
|
|
97
128
|
}
|
|
98
129
|
}
|
package/sidebar.d.ts
ADDED
package/sidebar.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/* Optional sidebar shell enhancement. The panel, dialog, controls, and all
|
|
2
|
+
* icons are authored HTML. Only the same panel is moved into the native
|
|
3
|
+
* dialog on narrow containers; no UI or navigation is generated. */
|
|
4
|
+
class MSidebarLayout extends HTMLElement {
|
|
5
|
+
#cleanup;
|
|
6
|
+
#actions;
|
|
7
|
+
#refresh;
|
|
8
|
+
static observedAttributes = ['collapse'];
|
|
9
|
+
attributeChangedCallback() { this.#refresh?.(); }
|
|
10
|
+
open() { this.#actions?.open(); }
|
|
11
|
+
close() { this.#actions?.close(); }
|
|
12
|
+
toggle() { this.#actions?.toggle(); }
|
|
13
|
+
|
|
14
|
+
connectedCallback() {
|
|
15
|
+
queueMicrotask(() => {
|
|
16
|
+
if (!this.isConnected || this.#cleanup) return;
|
|
17
|
+
const sidebar = this.querySelector(':scope > m-sidebar');
|
|
18
|
+
const main = this.querySelector(':scope > [data-sidebar-main]');
|
|
19
|
+
const dialog = this.querySelector(':scope > dialog[data-sidebar-dialog]');
|
|
20
|
+
if (!sidebar?.id || !main || !dialog || !('showModal' in dialog) || dialog.open || dialog.children.length) return;
|
|
21
|
+
const triggers = [...main.querySelectorAll('button[data-sidebar-toggle]')].filter(b => b.getAttribute('aria-controls') === sidebar.id);
|
|
22
|
+
const closers = [...sidebar.querySelectorAll('button[data-sidebar-close]')];
|
|
23
|
+
if (!triggers.length || !closers.length) return;
|
|
24
|
+
const originalExpanded = triggers.map(b => b.getAttribute('aria-expanded'));
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
const { signal } = controller;
|
|
27
|
+
let mobile = false;
|
|
28
|
+
let rail = false;
|
|
29
|
+
let canRail = false;
|
|
30
|
+
let opener = triggers[0];
|
|
31
|
+
const focusable = 'a[href],button:not(:disabled),summary';
|
|
32
|
+
const closePopovers = () => sidebar.querySelectorAll('[popover]:popover-open').forEach(p => p.hidePopover());
|
|
33
|
+
const update = () => {
|
|
34
|
+
this.toggleAttribute('data-m-sidebar-mobile', mobile);
|
|
35
|
+
this.toggleAttribute('data-m-sidebar-rail', rail && canRail && !mobile);
|
|
36
|
+
this.toggleAttribute('data-m-sidebar-can-rail', canRail && !mobile);
|
|
37
|
+
for (const button of triggers) button.setAttribute('aria-expanded', String(mobile ? dialog.open : !(rail && canRail)));
|
|
38
|
+
};
|
|
39
|
+
const setRail = value => {
|
|
40
|
+
closePopovers();
|
|
41
|
+
const active = document.activeElement;
|
|
42
|
+
rail = value && canRail;
|
|
43
|
+
update();
|
|
44
|
+
if (sidebar.contains(active) && active instanceof HTMLElement && !active.getClientRects().length) triggers[0].focus();
|
|
45
|
+
};
|
|
46
|
+
const open = () => {
|
|
47
|
+
if (mobile) {
|
|
48
|
+
if (!dialog.open) dialog.showModal();
|
|
49
|
+
update();
|
|
50
|
+
} else setRail(false);
|
|
51
|
+
};
|
|
52
|
+
const close = () => {
|
|
53
|
+
if (mobile) {
|
|
54
|
+
closePopovers();
|
|
55
|
+
if (dialog.open) dialog.close();
|
|
56
|
+
update();
|
|
57
|
+
} else setRail(true);
|
|
58
|
+
};
|
|
59
|
+
const toggle = () => mobile ? (dialog.open ? close() : open()) : setRail(!rail);
|
|
60
|
+
const measure = () => {
|
|
61
|
+
const active = document.activeElement;
|
|
62
|
+
// Icon collapse is explicitly requested and requires named, authored
|
|
63
|
+
// icons. Invalid icon anatomy falls back to the expanded desktop panel.
|
|
64
|
+
const items = [...sidebar.querySelectorAll('[data-sidebar-item]')].filter(e => !e.closest('[data-sidebar-subnav], [popover]'));
|
|
65
|
+
canRail = this.getAttribute('collapse') === 'icon' && items.length > 0 && items.every(e => e.querySelector('[data-sidebar-icon]') && (e.hasAttribute('aria-label') || e.hasAttribute('aria-labelledby')));
|
|
66
|
+
const nextMobile = this.getBoundingClientRect().width < 44 * parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
67
|
+
if (nextMobile !== mobile || sidebar.parentElement !== (nextMobile ? dialog : this)) {
|
|
68
|
+
const hadSidebarFocus = sidebar.contains(active);
|
|
69
|
+
closePopovers();
|
|
70
|
+
if (dialog.open) dialog.close();
|
|
71
|
+
mobile = nextMobile;
|
|
72
|
+
if (mobile) dialog.append(sidebar);
|
|
73
|
+
else this.insertBefore(sidebar, main);
|
|
74
|
+
update();
|
|
75
|
+
if (hadSidebarFocus && active instanceof HTMLElement) {
|
|
76
|
+
if (mobile) opener.focus();
|
|
77
|
+
else {
|
|
78
|
+
// Preserve a focused nested link when returning to desktop.
|
|
79
|
+
if (active.closest('[data-sidebar-subnav]')) rail = false;
|
|
80
|
+
update();
|
|
81
|
+
if (active.matches('[data-sidebar-close]')) sidebar.querySelector(focusable)?.focus();
|
|
82
|
+
else active.focus();
|
|
83
|
+
}
|
|
84
|
+
} else if (!mobile && triggers.includes(active) && !canRail) sidebar.querySelector(focusable)?.focus();
|
|
85
|
+
} else update();
|
|
86
|
+
};
|
|
87
|
+
for (const button of triggers) button.addEventListener('click', () => { opener = button; toggle(); }, { signal });
|
|
88
|
+
for (const button of closers) button.addEventListener('click', close, { signal });
|
|
89
|
+
dialog.addEventListener('close', () => {
|
|
90
|
+
update();
|
|
91
|
+
// WebKit can leave focus on body after closing a modal that contained
|
|
92
|
+
// a popover. Preserve router-assigned focus elsewhere in the page.
|
|
93
|
+
if (mobile && (document.activeElement === document.body || dialog.contains(document.activeElement))) opener.focus();
|
|
94
|
+
}, { signal });
|
|
95
|
+
dialog.addEventListener('click', event => {
|
|
96
|
+
if (event.target !== dialog) return;
|
|
97
|
+
const r = dialog.getBoundingClientRect();
|
|
98
|
+
if (event.clientX < r.left || event.clientX > r.right || event.clientY < r.top || event.clientY > r.bottom) close();
|
|
99
|
+
}, { signal });
|
|
100
|
+
sidebar.addEventListener('click', event => {
|
|
101
|
+
const summary = event.target.closest('summary');
|
|
102
|
+
if (summary && rail && !mobile) {
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
setRail(false);
|
|
105
|
+
summary.parentElement.open = true;
|
|
106
|
+
}
|
|
107
|
+
const link = event.target.closest('a[href]');
|
|
108
|
+
if (link && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey) queueMicrotask(() => { if (!event.defaultPrevented && mobile) close(); });
|
|
109
|
+
}, { signal });
|
|
110
|
+
const observer = new ResizeObserver(measure);
|
|
111
|
+
observer.observe(this);
|
|
112
|
+
window.addEventListener('resize', measure, { signal, passive: true });
|
|
113
|
+
this.#actions = { open, close, toggle };
|
|
114
|
+
this.#refresh = measure;
|
|
115
|
+
this.#cleanup = () => {
|
|
116
|
+
controller.abort();
|
|
117
|
+
observer.disconnect();
|
|
118
|
+
closePopovers();
|
|
119
|
+
if (dialog.open) dialog.close();
|
|
120
|
+
this.insertBefore(sidebar, main);
|
|
121
|
+
for (const name of ['ready', 'mobile', 'rail', 'can-rail']) this.removeAttribute(`data-m-sidebar-${name}`);
|
|
122
|
+
triggers.forEach((b, i) => originalExpanded[i] === null ? b.removeAttribute('aria-expanded') : b.setAttribute('aria-expanded', originalExpanded[i]));
|
|
123
|
+
};
|
|
124
|
+
this.setAttribute('data-m-sidebar-ready', '');
|
|
125
|
+
measure();
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
disconnectedCallback() {
|
|
130
|
+
this.#cleanup?.();
|
|
131
|
+
this.#cleanup = this.#actions = this.#refresh = undefined;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!customElements.get('m-sidebar-layout')) customElements.define('m-sidebar-layout', MSidebarLayout);
|
package/table.d.ts
ADDED
package/table.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** Optional overflow indicators. Authored rows, selection, sorting, and paging
|
|
2
|
+
* remain application-owned. The native scroll region works without this module. */
|
|
3
|
+
class MicaTableScroll extends HTMLElement {
|
|
4
|
+
connectedCallback() {
|
|
5
|
+
this._abort?.abort();
|
|
6
|
+
this._resize?.disconnect();
|
|
7
|
+
this._mutation?.disconnect();
|
|
8
|
+
this._abort = new AbortController();
|
|
9
|
+
this._update = () => {
|
|
10
|
+
cancelAnimationFrame(this._frame);
|
|
11
|
+
this._frame = requestAnimationFrame(() => this._measure());
|
|
12
|
+
};
|
|
13
|
+
this.addEventListener('scroll', this._update, { capture: true, passive: true, signal: this._abort.signal });
|
|
14
|
+
window.addEventListener('resize', this._update, { signal: this._abort.signal });
|
|
15
|
+
this._resize = new ResizeObserver(this._update);
|
|
16
|
+
this._observe = () => {
|
|
17
|
+
this._resize.disconnect();
|
|
18
|
+
this._resize.observe(this);
|
|
19
|
+
const region = this.querySelector(':scope > [data-table-region]');
|
|
20
|
+
if (region) this._resize.observe(region);
|
|
21
|
+
const table = region?.querySelector('table');
|
|
22
|
+
if (table) this._resize.observe(table);
|
|
23
|
+
this._update();
|
|
24
|
+
};
|
|
25
|
+
this._mutation = new MutationObserver(this._observe);
|
|
26
|
+
this._mutation.observe(this, { subtree: true, childList: true, attributes: true, attributeFilter: ['hidden', 'data-state', 'dir'] });
|
|
27
|
+
this._observe();
|
|
28
|
+
}
|
|
29
|
+
_measure() {
|
|
30
|
+
const region = this.querySelector(':scope > [data-table-region]');
|
|
31
|
+
const table = region?.querySelector('table');
|
|
32
|
+
if (!region || !table) return;
|
|
33
|
+
const r = region.getBoundingClientRect(), t = table.getBoundingClientRect();
|
|
34
|
+
const overflow = !region.hasAttribute('data-state') && region.scrollWidth > region.clientWidth + 1;
|
|
35
|
+
this.toggleAttribute('data-m-overflow-left', overflow && t.left < r.left - 1);
|
|
36
|
+
this.toggleAttribute('data-m-overflow-right', overflow && t.right > r.right + 1);
|
|
37
|
+
let left = 0, right = 0;
|
|
38
|
+
const rtl = getComputedStyle(region).direction === 'rtl';
|
|
39
|
+
for (const cell of table.querySelectorAll('thead [data-table-sticky]')) {
|
|
40
|
+
if (getComputedStyle(cell).position !== 'sticky' || !cell.getClientRects().length) continue;
|
|
41
|
+
const c = cell.getBoundingClientRect();
|
|
42
|
+
if (rtl) right = Math.max(right, r.right - c.left);
|
|
43
|
+
else left = Math.max(left, c.right - r.left);
|
|
44
|
+
}
|
|
45
|
+
this.style.setProperty('--m-table-edge-left', `${left}px`);
|
|
46
|
+
this.style.setProperty('--m-table-edge-right', `${right}px`);
|
|
47
|
+
}
|
|
48
|
+
disconnectedCallback() {
|
|
49
|
+
this._abort?.abort();
|
|
50
|
+
this._resize?.disconnect();
|
|
51
|
+
this._mutation?.disconnect();
|
|
52
|
+
cancelAnimationFrame(this._frame);
|
|
53
|
+
this.removeAttribute('data-m-overflow-left');
|
|
54
|
+
this.removeAttribute('data-m-overflow-right');
|
|
55
|
+
this.style.removeProperty('--m-table-edge-left');
|
|
56
|
+
this.style.removeProperty('--m-table-edge-right');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (!customElements.get('m-table-scroll')) customElements.define('m-table-scroll', MicaTableScroll);
|
package/types/react.d.ts
CHANGED
|
@@ -51,6 +51,11 @@ declare module 'react' {
|
|
|
51
51
|
|
|
52
52
|
// components
|
|
53
53
|
'm-segmented': MicaElement
|
|
54
|
+
'm-sidebar': MicaElement
|
|
55
|
+
'm-sidebar-layout': MicaElement<{ variant?: 'docked' | 'inset'; collapse?: 'icon' }>
|
|
56
|
+
'm-table-scroll': MicaElement
|
|
57
|
+
'm-breadcrumb-overflow': MicaElement
|
|
58
|
+
'm-header': MicaElement<{ align?: 'start' | 'end' }>
|
|
54
59
|
'm-stepper': MicaElement
|
|
55
60
|
'm-tabs': MicaElement<{ variant?: 'underline' }>
|
|
56
61
|
'm-badge': MicaElement<{
|
|
@@ -61,6 +66,7 @@ declare module 'react' {
|
|
|
61
66
|
blobatar?: string
|
|
62
67
|
contained?: boolean
|
|
63
68
|
}>
|
|
69
|
+
'm-callout': MicaElement<{ variant?: 'neutral' | 'success' | 'warning' | 'danger' }>
|
|
64
70
|
'm-skeleton': MicaElement
|
|
65
71
|
'm-toast': MicaElement<{
|
|
66
72
|
popover?: 'manual'
|