@f-ewald/components 1.18.0 → 1.20.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 +3 -0
- package/custom-elements.json +620 -0
- package/dist/icons.d.ts +2 -0
- package/dist/icons.d.ts.map +1 -1
- package/dist/icons.js +2 -0
- package/dist/icons.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/markdown-editor.d.ts +34 -0
- package/dist/markdown-editor.d.ts.map +1 -0
- package/dist/markdown-editor.js +130 -0
- package/dist/markdown-editor.js.map +1 -0
- package/dist/tab-bar.d.ts +51 -0
- package/dist/tab-bar.d.ts.map +1 -0
- package/dist/tab-bar.js +233 -0
- package/dist/tab-bar.js.map +1 -0
- package/dist/tab-item.d.ts +27 -0
- package/dist/tab-item.d.ts.map +1 -0
- package/dist/tab-item.js +67 -0
- package/dist/tab-item.js.map +1 -0
- package/dist/utils/front-matter.d.ts +19 -0
- package/dist/utils/front-matter.d.ts.map +1 -0
- package/dist/utils/front-matter.js +37 -0
- package/dist/utils/front-matter.js.map +1 -0
- package/docs/design-language.md +11 -1
- package/docs/markdown-editor.md +61 -0
- package/docs/tab-bar.md +64 -0
- package/docs/tab-item.md +40 -0
- package/llms.txt +79 -0
- package/package.json +2 -1
package/dist/tab-bar.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { LitElement, css, html, nothing } from "lit";
|
|
8
|
+
import { customElement, property, queryAssignedElements, state } from "lit/decorators.js";
|
|
9
|
+
import { repeat } from "lit/directives/repeat.js";
|
|
10
|
+
import { tokens } from "./tokens.js";
|
|
11
|
+
let instanceCount = 0;
|
|
12
|
+
/**
|
|
13
|
+
* WAI-ARIA tabs pattern (automatic activation, roving tabindex) driving a
|
|
14
|
+
* strip of declarative `tab-item` children. `tab-bar` renders the `role="tab"`
|
|
15
|
+
* button strip itself, reading `label`/`value`/`selected` off each slotted
|
|
16
|
+
* `tab-item`; each `tab-item` owns its own visibility via its reflected
|
|
17
|
+
* `selected` attribute.
|
|
18
|
+
*
|
|
19
|
+
* The active tab's underline uses `--ui-primary`; a `--ui-border` line spans
|
|
20
|
+
* the full strip beneath every tab, standing in for the inactive state since
|
|
21
|
+
* this design system has no dedicated secondary accent color.
|
|
22
|
+
*
|
|
23
|
+
* @element tab-bar
|
|
24
|
+
* @slot - `tab-item` elements.
|
|
25
|
+
* @fires change - The active tab changed via click or keyboard; detail: `TabChangeDetail`.
|
|
26
|
+
*/
|
|
27
|
+
let TabBar = class TabBar extends LitElement {
|
|
28
|
+
constructor() {
|
|
29
|
+
super(...arguments);
|
|
30
|
+
/** Accessible name for the tablist (e.g. "Editor mode"). */
|
|
31
|
+
this.label = "";
|
|
32
|
+
this._version = 0;
|
|
33
|
+
this._uid = `tab-bar-${++instanceCount}`;
|
|
34
|
+
}
|
|
35
|
+
static { this.styles = [
|
|
36
|
+
tokens,
|
|
37
|
+
css `
|
|
38
|
+
:host {
|
|
39
|
+
display: block;
|
|
40
|
+
}
|
|
41
|
+
.tablist {
|
|
42
|
+
display: flex;
|
|
43
|
+
gap: 1rem;
|
|
44
|
+
border-bottom: 1px solid var(--ui-border, #e2e8f0);
|
|
45
|
+
}
|
|
46
|
+
.tab {
|
|
47
|
+
appearance: none;
|
|
48
|
+
background: none;
|
|
49
|
+
border: none;
|
|
50
|
+
border-bottom: 2px solid transparent;
|
|
51
|
+
margin-bottom: -1px;
|
|
52
|
+
min-height: 2rem;
|
|
53
|
+
padding: 0.5rem 0.25rem;
|
|
54
|
+
font-family: var(
|
|
55
|
+
--ui-font,
|
|
56
|
+
ui-sans-serif,
|
|
57
|
+
system-ui,
|
|
58
|
+
sans-serif,
|
|
59
|
+
"Apple Color Emoji",
|
|
60
|
+
"Segoe UI Emoji",
|
|
61
|
+
"Segoe UI Symbol",
|
|
62
|
+
"Noto Color Emoji"
|
|
63
|
+
);
|
|
64
|
+
font-size: var(--ui-font-size-sm, 0.75rem);
|
|
65
|
+
font-weight: var(--ui-font-weight-medium, 500);
|
|
66
|
+
line-height: var(--ui-line-height-tight, 1.25);
|
|
67
|
+
color: var(--ui-text-muted, #64748b);
|
|
68
|
+
cursor: pointer;
|
|
69
|
+
}
|
|
70
|
+
.tab:hover {
|
|
71
|
+
color: var(--ui-text, #0f172a);
|
|
72
|
+
}
|
|
73
|
+
.tab[aria-selected="true"] {
|
|
74
|
+
color: var(--ui-text, #0f172a);
|
|
75
|
+
font-weight: var(--ui-font-weight-semibold, 600);
|
|
76
|
+
border-bottom-color: var(--ui-primary, #4f46e5);
|
|
77
|
+
}
|
|
78
|
+
.tab:focus-visible {
|
|
79
|
+
outline: none;
|
|
80
|
+
border-radius: var(--ui-radius-sm, 0.25rem);
|
|
81
|
+
box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
|
|
82
|
+
}
|
|
83
|
+
.panels {
|
|
84
|
+
margin-top: 1rem;
|
|
85
|
+
}
|
|
86
|
+
@media (forced-colors: active) {
|
|
87
|
+
.tab[aria-selected="true"] {
|
|
88
|
+
border-bottom-color: Highlight;
|
|
89
|
+
}
|
|
90
|
+
.tab:focus-visible {
|
|
91
|
+
outline: 2px solid CanvasText;
|
|
92
|
+
outline-offset: 2px;
|
|
93
|
+
box-shadow: none;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
`,
|
|
97
|
+
]; }
|
|
98
|
+
connectedCallback() {
|
|
99
|
+
super.connectedCallback();
|
|
100
|
+
this._observer ??= new MutationObserver(() => {
|
|
101
|
+
this._version++;
|
|
102
|
+
this._ensureSelection();
|
|
103
|
+
});
|
|
104
|
+
this._observer.observe(this, {
|
|
105
|
+
attributes: true,
|
|
106
|
+
attributeFilter: ["label", "value", "selected"],
|
|
107
|
+
childList: true,
|
|
108
|
+
subtree: true,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
disconnectedCallback() {
|
|
112
|
+
super.disconnectedCallback();
|
|
113
|
+
this._observer?.disconnect();
|
|
114
|
+
this._observer = undefined;
|
|
115
|
+
}
|
|
116
|
+
updated() {
|
|
117
|
+
this._tabItems.forEach((item, index) => {
|
|
118
|
+
item.setAttribute("aria-labelledby", this._tabButtonId(index));
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
_handleSlotChange() {
|
|
122
|
+
this._version++;
|
|
123
|
+
this._ensureSelection();
|
|
124
|
+
}
|
|
125
|
+
/** Defaults the first tab-item to selected if none was marked so by the consumer. */
|
|
126
|
+
_ensureSelection() {
|
|
127
|
+
const items = this._tabItems;
|
|
128
|
+
if (items.length > 0 && !items.some((item) => item.selected)) {
|
|
129
|
+
items[0].selected = true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
_currentIndex() {
|
|
133
|
+
const index = this._tabItems.findIndex((item) => item.selected);
|
|
134
|
+
return index === -1 ? 0 : index;
|
|
135
|
+
}
|
|
136
|
+
_tabButtonId(index) {
|
|
137
|
+
return `${this._uid}-tab-${index}`;
|
|
138
|
+
}
|
|
139
|
+
_select(index, options) {
|
|
140
|
+
const items = this._tabItems;
|
|
141
|
+
const target = items[index];
|
|
142
|
+
if (!target)
|
|
143
|
+
return;
|
|
144
|
+
const changed = !target.selected;
|
|
145
|
+
for (const item of items)
|
|
146
|
+
item.selected = item === target;
|
|
147
|
+
this._version++;
|
|
148
|
+
if (options?.focus)
|
|
149
|
+
this._focusButton(index);
|
|
150
|
+
if (changed) {
|
|
151
|
+
this.dispatchEvent(new CustomEvent("change", {
|
|
152
|
+
detail: { value: target.value || target.label, index },
|
|
153
|
+
bubbles: true,
|
|
154
|
+
composed: true,
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
_focusButton(index) {
|
|
159
|
+
void this.updateComplete.then(() => {
|
|
160
|
+
this.shadowRoot?.querySelectorAll(".tab")[index]?.focus();
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/** Arrow/Home/End roving-tabindex navigation with automatic activation. */
|
|
164
|
+
_onKeydown(event) {
|
|
165
|
+
const items = this._tabItems;
|
|
166
|
+
if (items.length === 0)
|
|
167
|
+
return;
|
|
168
|
+
const current = this._currentIndex();
|
|
169
|
+
let next;
|
|
170
|
+
switch (event.key) {
|
|
171
|
+
case "ArrowRight":
|
|
172
|
+
case "ArrowDown":
|
|
173
|
+
next = (current + 1) % items.length;
|
|
174
|
+
break;
|
|
175
|
+
case "ArrowLeft":
|
|
176
|
+
case "ArrowUp":
|
|
177
|
+
next = (current - 1 + items.length) % items.length;
|
|
178
|
+
break;
|
|
179
|
+
case "Home":
|
|
180
|
+
next = 0;
|
|
181
|
+
break;
|
|
182
|
+
case "End":
|
|
183
|
+
next = items.length - 1;
|
|
184
|
+
break;
|
|
185
|
+
default:
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
event.preventDefault();
|
|
189
|
+
this._select(next, { focus: true });
|
|
190
|
+
}
|
|
191
|
+
render() {
|
|
192
|
+
const items = this._tabItems ?? [];
|
|
193
|
+
const current = this._currentIndex();
|
|
194
|
+
return html `
|
|
195
|
+
<div
|
|
196
|
+
class="tablist"
|
|
197
|
+
role="tablist"
|
|
198
|
+
aria-label=${this.label || nothing}
|
|
199
|
+
@keydown=${this._onKeydown}
|
|
200
|
+
>
|
|
201
|
+
${repeat(items, (item) => item, (item, index) => html `
|
|
202
|
+
<button
|
|
203
|
+
type="button"
|
|
204
|
+
class="tab"
|
|
205
|
+
role="tab"
|
|
206
|
+
id=${this._tabButtonId(index)}
|
|
207
|
+
aria-selected=${item.selected ? "true" : "false"}
|
|
208
|
+
aria-controls=${item.id}
|
|
209
|
+
tabindex=${index === current ? 0 : -1}
|
|
210
|
+
@click=${() => this._select(index)}
|
|
211
|
+
>
|
|
212
|
+
${item.label}
|
|
213
|
+
</button>
|
|
214
|
+
`)}
|
|
215
|
+
</div>
|
|
216
|
+
<div class="panels"><slot @slotchange=${this._handleSlotChange}></slot></div>
|
|
217
|
+
`;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
__decorate([
|
|
221
|
+
property()
|
|
222
|
+
], TabBar.prototype, "label", void 0);
|
|
223
|
+
__decorate([
|
|
224
|
+
queryAssignedElements({ selector: "tab-item" })
|
|
225
|
+
], TabBar.prototype, "_tabItems", void 0);
|
|
226
|
+
__decorate([
|
|
227
|
+
state()
|
|
228
|
+
], TabBar.prototype, "_version", void 0);
|
|
229
|
+
TabBar = __decorate([
|
|
230
|
+
customElement("tab-bar")
|
|
231
|
+
], TabBar);
|
|
232
|
+
export { TabBar };
|
|
233
|
+
//# sourceMappingURL=tab-bar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab-bar.js","sourceRoot":"","sources":["../src/tab-bar.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, nothing } from \"lit\";\nimport { customElement, property, queryAssignedElements, state } from \"lit/decorators.js\";\nimport { repeat } from \"lit/directives/repeat.js\";\nimport { tokens } from \"./tokens.js\";\nimport { TabItem } from \"./tab-item.js\";\n\nlet instanceCount = 0;\n\n/** Fired when the active tab changes in response to user interaction. */\nexport interface TabChangeDetail {\n /** The newly active `tab-item`'s `value` (falling back to its `label`). */\n value: string;\n /** Index of the newly active `tab-item` among its siblings. */\n index: number;\n}\n\n/**\n * WAI-ARIA tabs pattern (automatic activation, roving tabindex) driving a\n * strip of declarative `tab-item` children. `tab-bar` renders the `role=\"tab\"`\n * button strip itself, reading `label`/`value`/`selected` off each slotted\n * `tab-item`; each `tab-item` owns its own visibility via its reflected\n * `selected` attribute.\n *\n * The active tab's underline uses `--ui-primary`; a `--ui-border` line spans\n * the full strip beneath every tab, standing in for the inactive state since\n * this design system has no dedicated secondary accent color.\n *\n * @element tab-bar\n * @slot - `tab-item` elements.\n * @fires change - The active tab changed via click or keyboard; detail: `TabChangeDetail`.\n */\n@customElement(\"tab-bar\")\nexport class TabBar extends LitElement {\n /** Accessible name for the tablist (e.g. \"Editor mode\"). */\n @property() label = \"\";\n\n @queryAssignedElements({ selector: \"tab-item\" })\n private readonly _tabItems!: TabItem[];\n\n @state() private _version = 0;\n private readonly _uid = `tab-bar-${++instanceCount}`;\n private _observer?: MutationObserver;\n\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n }\n .tablist {\n display: flex;\n gap: 1rem;\n border-bottom: 1px solid var(--ui-border, #e2e8f0);\n }\n .tab {\n appearance: none;\n background: none;\n border: none;\n border-bottom: 2px solid transparent;\n margin-bottom: -1px;\n min-height: 2rem;\n padding: 0.5rem 0.25rem;\n font-family: var(\n --ui-font,\n ui-sans-serif,\n system-ui,\n sans-serif,\n \"Apple Color Emoji\",\n \"Segoe UI Emoji\",\n \"Segoe UI Symbol\",\n \"Noto Color Emoji\"\n );\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-weight: var(--ui-font-weight-medium, 500);\n line-height: var(--ui-line-height-tight, 1.25);\n color: var(--ui-text-muted, #64748b);\n cursor: pointer;\n }\n .tab:hover {\n color: var(--ui-text, #0f172a);\n }\n .tab[aria-selected=\"true\"] {\n color: var(--ui-text, #0f172a);\n font-weight: var(--ui-font-weight-semibold, 600);\n border-bottom-color: var(--ui-primary, #4f46e5);\n }\n .tab:focus-visible {\n outline: none;\n border-radius: var(--ui-radius-sm, 0.25rem);\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n .panels {\n margin-top: 1rem;\n }\n @media (forced-colors: active) {\n .tab[aria-selected=\"true\"] {\n border-bottom-color: Highlight;\n }\n .tab:focus-visible {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n }\n `,\n ];\n\n override connectedCallback(): void {\n super.connectedCallback();\n this._observer ??= new MutationObserver(() => {\n this._version++;\n this._ensureSelection();\n });\n this._observer.observe(this, {\n attributes: true,\n attributeFilter: [\"label\", \"value\", \"selected\"],\n childList: true,\n subtree: true,\n });\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this._observer?.disconnect();\n this._observer = undefined;\n }\n\n protected override updated(): void {\n this._tabItems.forEach((item, index) => {\n item.setAttribute(\"aria-labelledby\", this._tabButtonId(index));\n });\n }\n\n private _handleSlotChange(): void {\n this._version++;\n this._ensureSelection();\n }\n\n /** Defaults the first tab-item to selected if none was marked so by the consumer. */\n private _ensureSelection(): void {\n const items = this._tabItems;\n if (items.length > 0 && !items.some((item) => item.selected)) {\n items[0].selected = true;\n }\n }\n\n private _currentIndex(): number {\n const index = this._tabItems.findIndex((item) => item.selected);\n return index === -1 ? 0 : index;\n }\n\n private _tabButtonId(index: number): string {\n return `${this._uid}-tab-${index}`;\n }\n\n private _select(index: number, options?: { focus?: boolean }): void {\n const items = this._tabItems;\n const target = items[index];\n if (!target) return;\n const changed = !target.selected;\n for (const item of items) item.selected = item === target;\n this._version++;\n if (options?.focus) this._focusButton(index);\n if (changed) {\n this.dispatchEvent(\n new CustomEvent<TabChangeDetail>(\"change\", {\n detail: { value: target.value || target.label, index },\n bubbles: true,\n composed: true,\n }),\n );\n }\n }\n\n private _focusButton(index: number): void {\n void this.updateComplete.then(() => {\n this.shadowRoot?.querySelectorAll<HTMLButtonElement>(\".tab\")[index]?.focus();\n });\n }\n\n /** Arrow/Home/End roving-tabindex navigation with automatic activation. */\n private _onKeydown(event: KeyboardEvent): void {\n const items = this._tabItems;\n if (items.length === 0) return;\n const current = this._currentIndex();\n let next: number;\n switch (event.key) {\n case \"ArrowRight\":\n case \"ArrowDown\":\n next = (current + 1) % items.length;\n break;\n case \"ArrowLeft\":\n case \"ArrowUp\":\n next = (current - 1 + items.length) % items.length;\n break;\n case \"Home\":\n next = 0;\n break;\n case \"End\":\n next = items.length - 1;\n break;\n default:\n return;\n }\n event.preventDefault();\n this._select(next, { focus: true });\n }\n\n override render() {\n const items = this._tabItems ?? [];\n const current = this._currentIndex();\n return html`\n <div\n class=\"tablist\"\n role=\"tablist\"\n aria-label=${this.label || nothing}\n @keydown=${this._onKeydown}\n >\n ${repeat(\n items,\n (item) => item,\n (item, index) => html`\n <button\n type=\"button\"\n class=\"tab\"\n role=\"tab\"\n id=${this._tabButtonId(index)}\n aria-selected=${item.selected ? \"true\" : \"false\"}\n aria-controls=${item.id}\n tabindex=${index === current ? 0 : -1}\n @click=${() => this._select(index)}\n >\n ${item.label}\n </button>\n `,\n )}\n </div>\n <div class=\"panels\"><slot @slotchange=${this._handleSlotChange}></slot></div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"tab-bar\": TabBar;\n }\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
/**
|
|
3
|
+
* A single labeled panel inside a `tab-bar`. Renders its default slot as an
|
|
4
|
+
* ARIA `tabpanel`, shown or hidden based on `selected` — `tab-bar` reads
|
|
5
|
+
* `label`/`value` to build its tab strip and toggles `selected` on the
|
|
6
|
+
* active panel.
|
|
7
|
+
*
|
|
8
|
+
* @element tab-item
|
|
9
|
+
* @slot - Panel content, shown only while `selected`.
|
|
10
|
+
*/
|
|
11
|
+
export declare class TabItem extends LitElement {
|
|
12
|
+
/** Text shown in the tab-bar's tab button for this panel. */
|
|
13
|
+
label: string;
|
|
14
|
+
/** Stable identifier reported in `tab-bar`'s `change` event; defaults to `label`. */
|
|
15
|
+
value: string;
|
|
16
|
+
/** Whether this panel is the active one; `tab-bar` owns this. */
|
|
17
|
+
selected: boolean;
|
|
18
|
+
static styles: import("lit").CSSResult[];
|
|
19
|
+
connectedCallback(): void;
|
|
20
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
21
|
+
}
|
|
22
|
+
declare global {
|
|
23
|
+
interface HTMLElementTagNameMap {
|
|
24
|
+
"tab-item": TabItem;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=tab-item.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab-item.d.ts","sourceRoot":"","sources":["../src/tab-item.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAM5C;;;;;;;;GAQG;AACH,qBACa,OAAQ,SAAQ,UAAU;IACrC,6DAA6D;IACjD,KAAK,SAAM;IAEvB,qFAAqF;IACzE,KAAK,SAAM;IAEvB,iEAAiE;IACrB,QAAQ,UAAS;IAE7D,OAAgB,MAAM,4BAUpB;IAEO,iBAAiB,IAAI,IAAI,CAOjC;IAEQ,MAAM,yCAEd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,UAAU,EAAE,OAAO,CAAC;KACrB;CACF"}
|
package/dist/tab-item.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { LitElement, css, html } from "lit";
|
|
8
|
+
import { customElement, property } from "lit/decorators.js";
|
|
9
|
+
import { tokens } from "./tokens.js";
|
|
10
|
+
let instanceCount = 0;
|
|
11
|
+
/**
|
|
12
|
+
* A single labeled panel inside a `tab-bar`. Renders its default slot as an
|
|
13
|
+
* ARIA `tabpanel`, shown or hidden based on `selected` — `tab-bar` reads
|
|
14
|
+
* `label`/`value` to build its tab strip and toggles `selected` on the
|
|
15
|
+
* active panel.
|
|
16
|
+
*
|
|
17
|
+
* @element tab-item
|
|
18
|
+
* @slot - Panel content, shown only while `selected`.
|
|
19
|
+
*/
|
|
20
|
+
let TabItem = class TabItem extends LitElement {
|
|
21
|
+
constructor() {
|
|
22
|
+
super(...arguments);
|
|
23
|
+
/** Text shown in the tab-bar's tab button for this panel. */
|
|
24
|
+
this.label = "";
|
|
25
|
+
/** Stable identifier reported in `tab-bar`'s `change` event; defaults to `label`. */
|
|
26
|
+
this.value = "";
|
|
27
|
+
/** Whether this panel is the active one; `tab-bar` owns this. */
|
|
28
|
+
this.selected = false;
|
|
29
|
+
}
|
|
30
|
+
static { this.styles = [
|
|
31
|
+
tokens,
|
|
32
|
+
css `
|
|
33
|
+
:host {
|
|
34
|
+
display: block;
|
|
35
|
+
}
|
|
36
|
+
:host(:not([selected])) {
|
|
37
|
+
display: none;
|
|
38
|
+
}
|
|
39
|
+
`,
|
|
40
|
+
]; }
|
|
41
|
+
connectedCallback() {
|
|
42
|
+
super.connectedCallback();
|
|
43
|
+
// Custom element constructors must not gain attributes (throws when
|
|
44
|
+
// created via document.createElement), so this is set on connect instead.
|
|
45
|
+
if (!this.id)
|
|
46
|
+
this.id = `tab-item-${++instanceCount}`;
|
|
47
|
+
this.setAttribute("role", "tabpanel");
|
|
48
|
+
this.setAttribute("tabindex", "0");
|
|
49
|
+
}
|
|
50
|
+
render() {
|
|
51
|
+
return html `<slot></slot>`;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
__decorate([
|
|
55
|
+
property()
|
|
56
|
+
], TabItem.prototype, "label", void 0);
|
|
57
|
+
__decorate([
|
|
58
|
+
property()
|
|
59
|
+
], TabItem.prototype, "value", void 0);
|
|
60
|
+
__decorate([
|
|
61
|
+
property({ type: Boolean, reflect: true })
|
|
62
|
+
], TabItem.prototype, "selected", void 0);
|
|
63
|
+
TabItem = __decorate([
|
|
64
|
+
customElement("tab-item")
|
|
65
|
+
], TabItem);
|
|
66
|
+
export { TabItem };
|
|
67
|
+
//# sourceMappingURL=tab-item.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab-item.js","sourceRoot":"","sources":["../src/tab-item.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,IAAI,aAAa,GAAG,CAAC,CAAC;AAEtB;;;;;;;;GAQG;AAEI,IAAM,OAAO,GAAb,MAAM,OAAQ,SAAQ,UAAU;IAAhC;;QACL,6DAA6D;QACjD,UAAK,GAAG,EAAE,CAAC;QAEvB,qFAAqF;QACzE,UAAK,GAAG,EAAE,CAAC;QAEvB,iEAAiE;QACrB,aAAQ,GAAG,KAAK,CAAC;IA0B/D,CAAC;aAxBiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;KAOF;KACF,AAVqB,CAUpB;IAEO,iBAAiB;QACxB,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,oEAAoE;QACpE,0EAA0E;QAC1E,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,GAAG,YAAY,EAAE,aAAa,EAAE,CAAC;QACtD,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QACtC,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IACrC,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA,eAAe,CAAC;IAC7B,CAAC;CACF,CAAA;AAhCa;IAAX,QAAQ,EAAE;sCAAY;AAGX;IAAX,QAAQ,EAAE;sCAAY;AAGqB;IAA3C,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;yCAAkB;AARlD,OAAO;IADnB,aAAa,CAAC,UAAU,CAAC;GACb,OAAO,CAkCnB","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { tokens } from \"./tokens.js\";\n\nlet instanceCount = 0;\n\n/**\n * A single labeled panel inside a `tab-bar`. Renders its default slot as an\n * ARIA `tabpanel`, shown or hidden based on `selected` — `tab-bar` reads\n * `label`/`value` to build its tab strip and toggles `selected` on the\n * active panel.\n *\n * @element tab-item\n * @slot - Panel content, shown only while `selected`.\n */\n@customElement(\"tab-item\")\nexport class TabItem extends LitElement {\n /** Text shown in the tab-bar's tab button for this panel. */\n @property() label = \"\";\n\n /** Stable identifier reported in `tab-bar`'s `change` event; defaults to `label`. */\n @property() value = \"\";\n\n /** Whether this panel is the active one; `tab-bar` owns this. */\n @property({ type: Boolean, reflect: true }) selected = false;\n\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n }\n :host(:not([selected])) {\n display: none;\n }\n `,\n ];\n\n override connectedCallback(): void {\n super.connectedCallback();\n // Custom element constructors must not gain attributes (throws when\n // created via document.createElement), so this is set on connect instead.\n if (!this.id) this.id = `tab-item-${++instanceCount}`;\n this.setAttribute(\"role\", \"tabpanel\");\n this.setAttribute(\"tabindex\", \"0\");\n }\n\n override render() {\n return html`<slot></slot>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"tab-item\": TabItem;\n }\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Result of splitting a document into its optional front matter and body. */
|
|
2
|
+
export interface FrontMatterResult {
|
|
3
|
+
/** Parsed front matter key-value pairs, or `null` if none was found. */
|
|
4
|
+
data: Record<string, unknown> | null;
|
|
5
|
+
/** The document text with the front matter block (if any) removed. */
|
|
6
|
+
body: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Splits a leading `---`-delimited YAML front matter block off a document.
|
|
10
|
+
*
|
|
11
|
+
* Only treats the block as front matter if it parses as a non-empty plain
|
|
12
|
+
* object — this avoids misreading a markdown horizontal rule (also `---`) as
|
|
13
|
+
* front matter, and tolerates malformed YAML typed mid-edit by falling back
|
|
14
|
+
* to treating the whole document as plain body text.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseFrontMatter(text: string): FrontMatterResult;
|
|
17
|
+
/** Stringifies a parsed front matter value for key-value table display. */
|
|
18
|
+
export declare function formatFrontMatterValue(value: unknown): string;
|
|
19
|
+
//# sourceMappingURL=front-matter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"front-matter.d.ts","sourceRoot":"","sources":["../../src/utils/front-matter.ts"],"names":[],"mappings":"AAEA,8EAA8E;AAC9E,MAAM,WAAW,iBAAiB;IAChC,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACrC,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;CACd;AAID;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,CAkBhE;AAED,2EAA2E;AAC3E,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAI7D"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { parse } from "yaml";
|
|
2
|
+
const FRONT_MATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
3
|
+
/**
|
|
4
|
+
* Splits a leading `---`-delimited YAML front matter block off a document.
|
|
5
|
+
*
|
|
6
|
+
* Only treats the block as front matter if it parses as a non-empty plain
|
|
7
|
+
* object — this avoids misreading a markdown horizontal rule (also `---`) as
|
|
8
|
+
* front matter, and tolerates malformed YAML typed mid-edit by falling back
|
|
9
|
+
* to treating the whole document as plain body text.
|
|
10
|
+
*/
|
|
11
|
+
export function parseFrontMatter(text) {
|
|
12
|
+
const match = text.match(FRONT_MATTER_PATTERN);
|
|
13
|
+
if (!match)
|
|
14
|
+
return { data: null, body: text };
|
|
15
|
+
try {
|
|
16
|
+
const parsed = parse(match[1]);
|
|
17
|
+
if (parsed === null ||
|
|
18
|
+
typeof parsed !== "object" ||
|
|
19
|
+
Array.isArray(parsed) ||
|
|
20
|
+
Object.keys(parsed).length === 0) {
|
|
21
|
+
return { data: null, body: text };
|
|
22
|
+
}
|
|
23
|
+
return { data: parsed, body: text.slice(match[0].length) };
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return { data: null, body: text };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Stringifies a parsed front matter value for key-value table display. */
|
|
30
|
+
export function formatFrontMatterValue(value) {
|
|
31
|
+
if (Array.isArray(value))
|
|
32
|
+
return value.map(formatFrontMatterValue).join(", ");
|
|
33
|
+
if (value !== null && typeof value === "object")
|
|
34
|
+
return JSON.stringify(value);
|
|
35
|
+
return String(value);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=front-matter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"front-matter.js","sourceRoot":"","sources":["../../src/utils/front-matter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAU7B,MAAM,oBAAoB,GAAG,mCAAmC,CAAC;AAEjE;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;IAC/C,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAE9C,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACxC,IACE,MAAM,KAAK,IAAI;YACf,OAAO,MAAM,KAAK,QAAQ;YAC1B,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YACrB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,EAChC,CAAC;YACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACpC,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,MAAiC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;IACxF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACpC,CAAC;AACH,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,sBAAsB,CAAC,KAAc;IACnD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9E,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC9E,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC","sourcesContent":["import { parse } from \"yaml\";\n\n/** Result of splitting a document into its optional front matter and body. */\nexport interface FrontMatterResult {\n /** Parsed front matter key-value pairs, or `null` if none was found. */\n data: Record<string, unknown> | null;\n /** The document text with the front matter block (if any) removed. */\n body: string;\n}\n\nconst FRONT_MATTER_PATTERN = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?/;\n\n/**\n * Splits a leading `---`-delimited YAML front matter block off a document.\n *\n * Only treats the block as front matter if it parses as a non-empty plain\n * object — this avoids misreading a markdown horizontal rule (also `---`) as\n * front matter, and tolerates malformed YAML typed mid-edit by falling back\n * to treating the whole document as plain body text.\n */\nexport function parseFrontMatter(text: string): FrontMatterResult {\n const match = text.match(FRONT_MATTER_PATTERN);\n if (!match) return { data: null, body: text };\n\n try {\n const parsed: unknown = parse(match[1]);\n if (\n parsed === null ||\n typeof parsed !== \"object\" ||\n Array.isArray(parsed) ||\n Object.keys(parsed).length === 0\n ) {\n return { data: null, body: text };\n }\n return { data: parsed as Record<string, unknown>, body: text.slice(match[0].length) };\n } catch {\n return { data: null, body: text };\n }\n}\n\n/** Stringifies a parsed front matter value for key-value table display. */\nexport function formatFrontMatterValue(value: unknown): string {\n if (Array.isArray(value)) return value.map(formatFrontMatterValue).join(\", \");\n if (value !== null && typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n"]}
|
package/docs/design-language.md
CHANGED
|
@@ -33,6 +33,11 @@ defines the details used when creating or reviewing components.
|
|
|
33
33
|
- White map rings, image-overlay controls, avatar foregrounds, and celebratory
|
|
34
34
|
confetti may remain literal when their contrast is intentionally independent
|
|
35
35
|
of the surrounding theme.
|
|
36
|
+
- There is no dedicated secondary accent color. `tab-bar`'s inactive-tab
|
|
37
|
+
indicator reuses `--ui-border` (a shared line beneath the whole strip, with
|
|
38
|
+
the active tab's `--ui-primary` line drawn on top of it) rather than
|
|
39
|
+
introducing a new token — the same precedent as `ui-button`'s `secondary`
|
|
40
|
+
variant, which is realized via `--ui-border`/`--ui-text-muted`, not a hue.
|
|
36
41
|
|
|
37
42
|
## Typography
|
|
38
43
|
|
|
@@ -125,6 +130,10 @@ literal values are migrated onto them.
|
|
|
125
130
|
- Modal/centered overlays expose a name, `role="dialog"`, `aria-modal`, initial
|
|
126
131
|
focus, Escape behavior, focus containment, and focus restoration.
|
|
127
132
|
- Collapsible controls expose `aria-expanded` and `aria-controls`.
|
|
133
|
+
- Tab strips (`tab-bar`/`tab-item`) follow the WAI-ARIA tabs pattern:
|
|
134
|
+
`role="tablist"/"tab"/"tabpanel"`, `aria-selected`, roving tabindex, and
|
|
135
|
+
automatic activation (arrow keys both move focus and select; Home/End jump
|
|
136
|
+
to the first/last tab).
|
|
128
137
|
- Charts expose a concise accessible data summary.
|
|
129
138
|
- Decorative icons/keycaps inside already-labelled controls are hidden from
|
|
130
139
|
assistive technology; standalone equivalents expose their own name.
|
|
@@ -135,7 +144,8 @@ literal values are migrated onto them.
|
|
|
135
144
|
- Metadata-only tags may omit standalone playground sections when their full
|
|
136
145
|
behavior is demonstrated through a parent:
|
|
137
146
|
`calendar-entry`, `gallery-item`, `gallery-item-variant`, `kanban-card`,
|
|
138
|
-
`kanban-column`,
|
|
147
|
+
`kanban-column`, `tab-item` (shown through `tab-bar`), and `timeline-entry`
|
|
148
|
+
(shown through `timeline-container`).
|
|
139
149
|
- Styleless inline formatters may omit empty `static styles`/token imports:
|
|
140
150
|
`distance-value`, `live-timer`, `relative-time`, and `roman-numeral`.
|
|
141
151
|
- Domain visuals may deliberately diverge in geometry and data color, but their
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# `<markdown-editor>`
|
|
2
|
+
|
|
3
|
+
GitHub-style markdown editor: a "Write" tab holding a plain textarea and a
|
|
4
|
+
"Preview" tab rendering the markdown body (via `markdown-view`). Leading
|
|
5
|
+
YAML front matter (a `---`-delimited block) is detected, parsed, and shown
|
|
6
|
+
as a key-value table above the rendered body rather than as raw text.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```js
|
|
11
|
+
import "@f-ewald/components/markdown-editor.js";
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```html
|
|
17
|
+
<markdown-editor></markdown-editor>
|
|
18
|
+
<script type="module">
|
|
19
|
+
const el = document.querySelector("markdown-editor");
|
|
20
|
+
el.value = `---
|
|
21
|
+
title: Weekly status
|
|
22
|
+
author: Ada Lovelace
|
|
23
|
+
tags: [engineering, updates]
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
# Weekly status
|
|
27
|
+
|
|
28
|
+
Some **markdown** content here.`;
|
|
29
|
+
el.addEventListener("input", (e) => console.log(e.detail.value));
|
|
30
|
+
</script>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Attributes / properties
|
|
34
|
+
|
|
35
|
+
| Property | Attribute | Type | Default | Description |
|
|
36
|
+
| --- | --- | --- | --- | --- |
|
|
37
|
+
| `value` | `value` | `string` | `""` | Full raw document text, including any front matter block. |
|
|
38
|
+
| `rows` | `rows` | `number` | `12` | Visible row count for the Write tab's textarea. |
|
|
39
|
+
| `placeholder` | `placeholder` | `string` | `""` | Placeholder text shown when the Write tab is empty. |
|
|
40
|
+
|
|
41
|
+
## Events
|
|
42
|
+
|
|
43
|
+
| Event | Description |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| `input` | Fires on every keystroke in the Write tab; detail: { value: string }. |
|
|
46
|
+
| `change` | Native change semantics (on blur, if the value changed); detail: { value: string }. |
|
|
47
|
+
|
|
48
|
+
## Slots
|
|
49
|
+
|
|
50
|
+
_None._
|
|
51
|
+
|
|
52
|
+
## CSS custom properties
|
|
53
|
+
|
|
54
|
+
| Custom property |
|
|
55
|
+
| --- |
|
|
56
|
+
| `--ui-font` |
|
|
57
|
+
| `--ui-font-size-sm` |
|
|
58
|
+
| `--ui-font-weight-medium` |
|
|
59
|
+
| `--ui-line-height-normal` |
|
|
60
|
+
| `--ui-text` |
|
|
61
|
+
| `--ui-text-muted` |
|
package/docs/tab-bar.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# `<tab-bar>`
|
|
2
|
+
|
|
3
|
+
WAI-ARIA tabs pattern (automatic activation, roving tabindex) driving a
|
|
4
|
+
strip of declarative `tab-item` children. `tab-bar` renders the `role="tab"`
|
|
5
|
+
button strip itself, reading `label`/`value`/`selected` off each slotted
|
|
6
|
+
`tab-item`; each `tab-item` owns its own visibility via its reflected
|
|
7
|
+
`selected` attribute.
|
|
8
|
+
|
|
9
|
+
The active tab's underline uses `--ui-primary`; a `--ui-border` line spans
|
|
10
|
+
the full strip beneath every tab, standing in for the inactive state since
|
|
11
|
+
this design system has no dedicated secondary accent color.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import "@f-ewald/components/tab-bar.js";
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<tab-bar label="Project sections">
|
|
23
|
+
<tab-item label="Overview" value="overview" selected>Overview content</tab-item>
|
|
24
|
+
<tab-item label="Activity" value="activity">Activity content</tab-item>
|
|
25
|
+
<tab-item label="Settings" value="settings">Settings content</tab-item>
|
|
26
|
+
</tab-bar>
|
|
27
|
+
<script type="module">
|
|
28
|
+
document.querySelector("tab-bar").addEventListener("change", (e) => console.log(e.detail.value));
|
|
29
|
+
</script>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Attributes / properties
|
|
33
|
+
|
|
34
|
+
| Property | Attribute | Type | Default | Description |
|
|
35
|
+
| --- | --- | --- | --- | --- |
|
|
36
|
+
| `label` | `label` | `string` | `""` | Accessible name for the tablist (e.g. "Editor mode"). |
|
|
37
|
+
|
|
38
|
+
## Events
|
|
39
|
+
|
|
40
|
+
| Event | Description |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| `change` | The active tab changed via click or keyboard; detail: `TabChangeDetail`. |
|
|
43
|
+
|
|
44
|
+
## Slots
|
|
45
|
+
|
|
46
|
+
| Slot | Description |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `(default)` | `tab-item` elements. |
|
|
49
|
+
|
|
50
|
+
## CSS custom properties
|
|
51
|
+
|
|
52
|
+
| Custom property |
|
|
53
|
+
| --- |
|
|
54
|
+
| `--ui-border` |
|
|
55
|
+
| `--ui-focus-ring` |
|
|
56
|
+
| `--ui-font` |
|
|
57
|
+
| `--ui-font-size-sm` |
|
|
58
|
+
| `--ui-font-weight-medium` |
|
|
59
|
+
| `--ui-font-weight-semibold` |
|
|
60
|
+
| `--ui-line-height-tight` |
|
|
61
|
+
| `--ui-primary` |
|
|
62
|
+
| `--ui-radius-sm` |
|
|
63
|
+
| `--ui-text` |
|
|
64
|
+
| `--ui-text-muted` |
|
package/docs/tab-item.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# `<tab-item>`
|
|
2
|
+
|
|
3
|
+
A single labeled panel inside a `tab-bar`. Renders its default slot as an
|
|
4
|
+
ARIA `tabpanel`, shown or hidden based on `selected` — `tab-bar` reads
|
|
5
|
+
`label`/`value` to build its tab strip and toggles `selected` on the
|
|
6
|
+
active panel.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```js
|
|
11
|
+
import "@f-ewald/components/tab-item.js";
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```html
|
|
17
|
+
<tab-item></tab-item>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Attributes / properties
|
|
21
|
+
|
|
22
|
+
| Property | Attribute | Type | Default | Description |
|
|
23
|
+
| --- | --- | --- | --- | --- |
|
|
24
|
+
| `label` | `label` | `string` | `""` | Text shown in the tab-bar's tab button for this panel. |
|
|
25
|
+
| `value` | `value` | `string` | `""` | Stable identifier reported in `tab-bar`'s `change` event; defaults to `label`. |
|
|
26
|
+
| `selected` | `selected` | `boolean` | `false` | Whether this panel is the active one; `tab-bar` owns this. |
|
|
27
|
+
|
|
28
|
+
## Events
|
|
29
|
+
|
|
30
|
+
_None._
|
|
31
|
+
|
|
32
|
+
## Slots
|
|
33
|
+
|
|
34
|
+
| Slot | Description |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `(default)` | Panel content, shown only while `selected`. |
|
|
37
|
+
|
|
38
|
+
## CSS custom properties
|
|
39
|
+
|
|
40
|
+
_None._
|