fluentui-webcomponents 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/AGENTS.md +212 -0
  2. package/README.md +99 -0
  3. package/components/avatar/fluent-avatar.css +481 -0
  4. package/components/avatar/fluent-avatar.js +80 -0
  5. package/components/badge/fluent-badge.css +289 -0
  6. package/components/badge/fluent-badge.js +20 -0
  7. package/components/breadcrumb/fluent-breadcrumb.css +29 -0
  8. package/components/breadcrumb/fluent-breadcrumb.js +33 -0
  9. package/components/breadcrumb-item/fluent-breadcrumb-item.css +70 -0
  10. package/components/breadcrumb-item/fluent-breadcrumb-item.js +77 -0
  11. package/components/button/fluent-button.css +265 -0
  12. package/components/button/fluent-button.js +326 -0
  13. package/components/card/fluent-card.css +85 -0
  14. package/components/card/fluent-card.js +21 -0
  15. package/components/checkbox/fluent-checkbox.css +171 -0
  16. package/components/checkbox/fluent-checkbox.js +294 -0
  17. package/components/dialog/fluent-dialog.css +82 -0
  18. package/components/dialog/fluent-dialog.js +137 -0
  19. package/components/divider/fluent-divider.css +124 -0
  20. package/components/divider/fluent-divider.js +14 -0
  21. package/components/image/fluent-image.css +73 -0
  22. package/components/image/fluent-image.js +36 -0
  23. package/components/label/fluent-label.css +49 -0
  24. package/components/label/fluent-label.js +61 -0
  25. package/components/link/fluent-link.css +72 -0
  26. package/components/link/fluent-link.js +109 -0
  27. package/components/menu/fluent-menu.css +57 -0
  28. package/components/menu/fluent-menu.js +202 -0
  29. package/components/menu-item/fluent-menu-item.css +152 -0
  30. package/components/menu-item/fluent-menu-item.js +177 -0
  31. package/components/popover/fluent-popover.css +95 -0
  32. package/components/popover/fluent-popover.js +93 -0
  33. package/components/radio/fluent-radio.css +123 -0
  34. package/components/radio/fluent-radio.js +257 -0
  35. package/components/select/fluent-select.css +194 -0
  36. package/components/select/fluent-select.js +245 -0
  37. package/components/slider/fluent-slider.css +199 -0
  38. package/components/slider/fluent-slider.js +438 -0
  39. package/components/spinner/fluent-spinner.css +160 -0
  40. package/components/spinner/fluent-spinner.js +30 -0
  41. package/components/switch/fluent-switch.css +154 -0
  42. package/components/switch/fluent-switch.js +260 -0
  43. package/components/text/fluent-text.css +128 -0
  44. package/components/text/fluent-text.js +21 -0
  45. package/components/text-input/fluent-text-input.css +227 -0
  46. package/components/text-input/fluent-text-input.js +298 -0
  47. package/components/textarea/fluent-textarea.css +227 -0
  48. package/components/textarea/fluent-textarea.js +400 -0
  49. package/components/tooltip/fluent-tooltip.css +65 -0
  50. package/components/tooltip/fluent-tooltip.js +102 -0
  51. package/components/tree/fluent-tree.css +16 -0
  52. package/components/tree/fluent-tree.js +167 -0
  53. package/components/tree-item/fluent-tree-item.css +147 -0
  54. package/components/tree-item/fluent-tree-item.js +163 -0
  55. package/core/fluent-element.js +34 -0
  56. package/gallery.html +492 -0
  57. package/package.json +19 -0
  58. package/theme/theme-picker.js +38 -0
  59. package/tokens.css +724 -0
package/AGENTS.md ADDED
@@ -0,0 +1,212 @@
1
+ # Fluent UI Web Components — Agent Instructions
2
+
3
+ **These instructions are the source of truth.** All contributions must follow these principles.
4
+
5
+ ## Critical Rules (never violate)
6
+
7
+ 1. **Zero external dependencies.** No `@microsoft/fast-element`, no `@fluentui/tokens`, no Lit, no frameworks. Vanilla `HTMLElement` only.
8
+ 2. **No build tools.** Plain `.js` + `.css` files. Native ES modules (`import`/`export`). No bundler, no TypeScript compiler, no CSS preprocessor.
9
+ 3. **Design tokens are CSS custom properties.** All colors, spacing, typography, radii, shadows, durations, and curves are defined in `tokens.css` and referenced via `var(--tokenName)`. Never hardcode a value.
10
+ 4. **`color-mix(in srgb, ...)` for palette derivation.** The 16-stop brand/neutral palettes derive from a single `--accent-base` / `--neutral-base` CSS variable using the `@microsoft/fast-colors` ColorPalette algorithm (trim-and-rescale with RGB interpolation). No JS color math, no lookup tables.
11
+ 5. **Component CSS imports tokens.** Every `fluent-xxx.css` starts with `@import url('../../tokens.css');`. Browsers deduplicate by URL.
12
+
13
+ ## Component Pattern
14
+
15
+ ### JS Template
16
+
17
+ ```js
18
+ import { FluentElement } from '../../core/fluent-element.js';
19
+
20
+ const stylesUrl = new URL('./fluent-xxx.css', import.meta.url).href;
21
+
22
+ class FluentXxx extends FluentElement {
23
+ static stylesUrl = stylesUrl;
24
+ static template = `
25
+ <div class="root">
26
+ <!-- shadow DOM content here -->
27
+ <slot></slot>
28
+ </div>
29
+ `;
30
+
31
+ static get observedAttributes() {
32
+ return ['disabled', 'appearance', 'size', 'shape'];
33
+ }
34
+ }
35
+
36
+ customElements.define('fluent-xxx', FluentXxx);
37
+ ```
38
+
39
+ ### CSS Template
40
+
41
+ ```css
42
+ @import url('../../tokens.css');
43
+
44
+ :host {
45
+ display: inline-flex; /* host stays in layout tree for pointer events */
46
+ }
47
+
48
+ .root {
49
+ /* ALL visual styles go here — colors, spacing, borders, shadows, fonts */
50
+ background: var(--colorBrandBackground);
51
+ padding: 0 var(--spacingHorizontalM);
52
+ border-radius: var(--borderRadiusMedium);
53
+ }
54
+
55
+ /* State via host attributes targeting .root */
56
+ :host([disabled]) .root { opacity: 0.5; cursor: not-allowed; }
57
+ :host([appearance='primary']) .root { background: var(--colorBrandBackground); }
58
+
59
+ /* Pseudo-classes on .root */
60
+ .root:hover { background: var(--colorBrandBackgroundHover); }
61
+ .root:focus-visible { outline: 2px solid var(--colorStrokeFocus2); }
62
+ ```
63
+
64
+ ### Key Patterns
65
+
66
+ | Concern | Where | Why |
67
+ |---|---|---|
68
+ | Visual styles | `.root` div | Isolated from page CSS — page's `* { padding: 0 }` can't reach it |
69
+ | Layout / display | `:host` | Host must stay in layout tree for pointer events, `getBoundingClientRect()`, and CSS anchor positioning |
70
+ | State selectors | `:host([attr]) .root` | Host carries the attribute, `.root` receives the visual change |
71
+ | Slotted content | `::slotted(...)` | Stays as-is, no `.root` prefix needed |
72
+
73
+ ### `FluentElement` Base Class
74
+
75
+ ```js
76
+ // core/fluent-element.js — ~30 lines
77
+ class FluentElement extends HTMLElement {
78
+ static stylesUrl = '';
79
+ static template = '';
80
+
81
+ constructor() {
82
+ super();
83
+ this._root = this.attachShadow({ mode: 'open' });
84
+ }
85
+
86
+ connectedCallback() {
87
+ if (this.constructor.stylesUrl) {
88
+ const link = document.createElement('link');
89
+ link.rel = 'stylesheet';
90
+ link.href = this.constructor.stylesUrl;
91
+ this._root.appendChild(link);
92
+ }
93
+ const tmpl = document.createElement('template');
94
+ tmpl.innerHTML = this.constructor.template;
95
+ this._root.appendChild(tmpl.content.cloneNode(true));
96
+ }
97
+
98
+ static get observedAttributes() { return []; }
99
+ attributeChangedCallback(name, oldVal, newVal) {
100
+ if (oldVal !== newVal) this.changed(name, oldVal, newVal);
101
+ }
102
+ changed() {}
103
+ }
104
+ ```
105
+
106
+ ## Design Tokens
107
+
108
+ ### Palette Derivation
109
+
110
+ ```
111
+ --accent-base (#0f6cbd)
112
+ → trim endpoints (ColorPalette: clipLight=0.185, clipDark=0.16, RGB interp)
113
+ → 16-stop ramp from trimDark through baseColor to trimLight
114
+ → --brand-10 … --brand-160
115
+
116
+ --neutral-base (#808080)
117
+ → same algorithm, trim endpoints
118
+ → --neutral-10 … --neutral-160
119
+
120
+ Dark mode: clipLight=0 → trimLight = pure white (maximum contrast text)
121
+ ```
122
+
123
+ Semantic tokens alias these stops:
124
+ ```
125
+ --colorBrandBackground → --brand-80
126
+ --colorBrandBackgroundHover → --brand-70
127
+ --colorBrandBackgroundPressed → --brand-40
128
+ ```
129
+
130
+ ### Runtime Accent Change
131
+
132
+ ```js
133
+ document.documentElement.style.setProperty('--accent-base', '#e3008c');
134
+ ```
135
+
136
+ One line. Entire palette and all dependent tokens update instantly. Zero JS math.
137
+
138
+ ### Theme Switching
139
+
140
+ ```css
141
+ :root.dark { --accent-base: #479ef5; --neutral-base: #b0b0b0; }
142
+ body.dark { /* overrides for dark theme tokens */ }
143
+ ```
144
+
145
+ ## Form Association
146
+
147
+ ```js
148
+ class FluentCheckbox extends FluentElement {
149
+ static formAssociated = true;
150
+
151
+ constructor() {
152
+ super();
153
+ this._internals = this.attachInternals();
154
+ }
155
+
156
+ // Never pass empty second arg to setValidity when flags are truthy
157
+ _setValidity() {
158
+ const msg = this.validationMessage;
159
+ this._internals.setValidity(
160
+ { valueMissing: this.required && !this._checked },
161
+ msg || undefined
162
+ );
163
+ }
164
+ }
165
+ ```
166
+
167
+ ## Component-Specific Exception Rules
168
+
169
+ ### Dialog
170
+ - Host uses `:host { display: none; }` / `:host([open]) { display: block; }` for show/hide
171
+ - `trigger` and `close-on` attributes for declarative open/close wiring
172
+ - Uses native `<dialog>` element inside shadow DOM
173
+
174
+ ### Tooltip / Popover
175
+ - Uses Popover API: `popover="manual"`, `showPopover()` / `hidePopover()`
176
+ - `inset-area` / `position-area` MUST stay on `:host`, not on `.root`
177
+ - JS sets `anchor-name` on the anchor element and `position-anchor` on the tooltip host
178
+
179
+ ### Slider
180
+ - CSS custom properties (`--slider-thumb`, `--slider-progress`, `--step-rate`) set on `.root` via inline style to override CSS defaults
181
+ - Pointer event handlers bound once in constructor (`this._boundMove = this._handlePointerMove.bind(this)`) — never use `.bind()` in addEventListener arguments
182
+
183
+ ## File Structure
184
+
185
+ ```
186
+ fluentui-webcomponents/
187
+ ├── tokens.css # All design tokens
188
+ ├── gallery.html # Single-page component showcase
189
+ ├── core/
190
+ │ └── fluent-element.js # HTMLElement base class
191
+ ├── components/
192
+ │ ├── button/ fluent-button.js + .css
193
+ │ ├── badge/ fluent-badge.js + .css
194
+ │ ├── checkbox/ fluent-checkbox.js + .css
195
+ │ ├── ... (26 components total)
196
+ │ └── tree/ fluent-tree.js + .css
197
+ └── theme/
198
+ └── theme-picker.js # Accent color + theme switcher
199
+ ```
200
+
201
+ ## Quality Checklist
202
+
203
+ - [ ] Zero console errors in gallery
204
+ - [ ] Component has both `.js` and `.css` files
205
+ - [ ] CSS starts with `@import url('../../tokens.css');`
206
+ - [ ] Template wrapped in `<div class="root">...</div>`
207
+ - [ ] Visual styles on `.root`, not on `:host`
208
+ - [ ] No hardcoded colors/spacing/typography — all `var(--tokenName)`
209
+ - [ ] `:host` has a display mode (not `display: contents`)
210
+ - [ ] Form components use `static formAssociated = true` + `attachInternals()`
211
+ - [ ] `setValidity()` never passes empty second arg with truthy flags
212
+ - [ ] Event handlers (if any) bound once in constructor, not each call
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # fluentui-webcomponents
2
+
3
+ Vanilla Custom Elements rewrite of `@fluentui/web-components` — zero dependencies, zero build tools.
4
+
5
+ ## Quick Start
6
+
7
+ ```html
8
+ <script type="module" src="core/fluent-element.js"></script>
9
+ <script type="module" src="components/button/fluent-button.js"></script>
10
+ <script type="module" src="components/badge/fluent-badge.js"></script>
11
+
12
+ <fluent-button appearance="primary">Click me</fluent-button>
13
+ <fluent-badge color="danger">New</fluent-badge>
14
+ ```
15
+
16
+ CSS design tokens are defined in `tokens.css`. Each component's CSS imports it via `@import` — no `<link>` needed in your page.
17
+
18
+ ## Gallery
19
+
20
+ ```bash
21
+ npx serve .
22
+ ```
23
+
24
+ Open `http://localhost:3000/gallery.html` — a single-page showcase of all 26 components with a theme picker.
25
+
26
+ ## Customization
27
+
28
+ **Accent color** (instant, no JS math):
29
+ ```css
30
+ :root { --accent-base: #e3008c; }
31
+ ```
32
+
33
+ The entire 16-stop brand palette derives from this single CSS variable via `color-mix(in oklch, ...)`. All component colors update automatically.
34
+
35
+ **Dark theme**:
36
+ ```html
37
+ <body class="dark">
38
+ ```
39
+
40
+ **Theme picker** (optional):
41
+ ```html
42
+ <script type="module" src="theme/theme-picker.js"></script>
43
+ <fluent-theme-picker></fluent-theme-picker>
44
+ ```
45
+
46
+ ## Components
47
+
48
+ | Component | Tag |
49
+ |---|---|
50
+ | Button | `<fluent-button>` |
51
+ | Badge | `<fluent-badge>` |
52
+ | Link | `<fluent-link>` |
53
+ | Divider | `<fluent-divider>` |
54
+ | Spinner | `<fluent-spinner>` |
55
+ | Avatar | `<fluent-avatar>` |
56
+ | Checkbox | `<fluent-checkbox>` |
57
+ | Radio | `<fluent-radio>` |
58
+ | Switch | `<fluent-switch>` |
59
+ | Slider | `<fluent-slider>` |
60
+ | TextInput | `<fluent-text-input>` |
61
+ | Textarea | `<fluent-textarea>` |
62
+ | Select | `<fluent-select>` |
63
+ | Card | `<fluent-card>` |
64
+ | Label | `<fluent-label>` |
65
+ | Text | `<fluent-text>` |
66
+ | Image | `<fluent-image>` |
67
+ | Dialog | `<fluent-dialog trigger="#btn" close-on="#cancel">` |
68
+ | Tooltip | `<fluent-tooltip anchor="btn-id">` |
69
+ | Menu | `<fluent-menu>` |
70
+ | Breadcrumb | `<fluent-breadcrumb>` |
71
+ | Tree | `<fluent-tree>` |
72
+
73
+ ## File Structure
74
+
75
+ ```
76
+ fluentui-webcomponents/
77
+ ├── tokens.css # All design tokens (colors, spacing, type, shadows...)
78
+ ├── gallery.html # Component showcase
79
+ ├── core/
80
+ │ └── fluent-element.js # Base class (~30 lines)
81
+ ├── components/
82
+ │ ├── button/
83
+ │ │ ├── fluent-button.js
84
+ │ │ └── fluent-button.css # @import url('../../tokens.css');
85
+ │ ├── badge/
86
+ │ ├── ...
87
+ │ └── tree/
88
+ └── theme/
89
+ └── theme-picker.js # Optional accent/theme switcher
90
+ ```
91
+
92
+ Each component is self-contained: one `.js` file (class + `customElements.define`) and one `.css` file. No bundler, no framework, no external dependencies.
93
+
94
+ ## Architecture
95
+
96
+ - **`FluentElement`** — base class extends `HTMLElement`, attaches shadow DOM, injects `<link rel="stylesheet">` for the component's CSS
97
+ - **CSS-only state** — `:host([disabled])`, `:host([appearance="primary"])`, etc. Zero JS for visual updates
98
+ - **Form association** — native `this.attachInternals()` + `setFormValue` + `setValidity`
99
+ - **Design tokens** — `color-mix(in oklch, var(--accent-base), ...)` palette, CSS `var()` throughout