@microsoft/webui-framework 0.0.7 → 0.0.9

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.
@@ -1,3 +1,5 @@
1
+ /** Convert camelCase to kebab-case for attribute reflection. */
2
+ export declare function toKebabCase(str: string): string;
1
3
  export declare function getObservableNames(ctor: Function): Set<string>;
2
4
  /**
3
5
  * Marks a property as observable. When the value changes the decorator will:
@@ -9,9 +9,74 @@
9
9
  // ---------------------------------------------------------------------------
10
10
  // Internal helpers
11
11
  // ---------------------------------------------------------------------------
12
+ /**
13
+ * Map of camelCase property names to their HTML attribute names.
14
+ *
15
+ * Covers two categories of irregular mappings:
16
+ *
17
+ * 1. Multi-word ARIA attributes — concatenated lowercase after `aria-`
18
+ * (e.g., `ariaDescribedBy` → `aria-describedby`), per the ARIAMixin spec.
19
+ * 2. HTML global/element attributes — concatenated lowercase attribute names
20
+ * with camelCase property counterparts (e.g., `readOnly` → `readonly`).
21
+ */
22
+ const propertyToAttribute = Object.assign(Object.create(null), {
23
+ // --- ARIA (ARIAMixin) ---
24
+ ariaActiveDescendant: 'aria-activedescendant',
25
+ ariaAutoComplete: 'aria-autocomplete',
26
+ ariaBrailleLabel: 'aria-braillelabel',
27
+ ariaBrailleRoleDescription: 'aria-brailleroledescription',
28
+ ariaColCount: 'aria-colcount',
29
+ ariaColIndex: 'aria-colindex',
30
+ ariaColIndexText: 'aria-colindextext',
31
+ ariaColSpan: 'aria-colspan',
32
+ ariaDescribedBy: 'aria-describedby',
33
+ ariaDropEffect: 'aria-dropeffect',
34
+ ariaErrorMessage: 'aria-errormessage',
35
+ ariaFlowTo: 'aria-flowto',
36
+ ariaHasPopup: 'aria-haspopup',
37
+ ariaKeyShortcuts: 'aria-keyshortcuts',
38
+ ariaLabelledBy: 'aria-labelledby',
39
+ ariaMultiLine: 'aria-multiline',
40
+ ariaMultiSelectable: 'aria-multiselectable',
41
+ ariaPosInSet: 'aria-posinset',
42
+ ariaReadOnly: 'aria-readonly',
43
+ ariaRoleDescription: 'aria-roledescription',
44
+ ariaRowCount: 'aria-rowcount',
45
+ ariaRowIndex: 'aria-rowindex',
46
+ ariaRowIndexText: 'aria-rowindextext',
47
+ ariaRowSpan: 'aria-rowspan',
48
+ ariaSetSize: 'aria-setsize',
49
+ ariaValueMax: 'aria-valuemax',
50
+ ariaValueMin: 'aria-valuemin',
51
+ ariaValueNow: 'aria-valuenow',
52
+ ariaValueText: 'aria-valuetext',
53
+ // --- HTML global/element attributes ---
54
+ accessKey: 'accesskey',
55
+ autoCapitalize: 'autocapitalize',
56
+ contentEditable: 'contenteditable',
57
+ crossOrigin: 'crossorigin',
58
+ dirName: 'dirname',
59
+ fetchPriority: 'fetchpriority',
60
+ formAction: 'formaction',
61
+ formEnctype: 'formenctype',
62
+ formMethod: 'formmethod',
63
+ formNoValidate: 'formnovalidate',
64
+ formTarget: 'formtarget',
65
+ inputMode: 'inputmode',
66
+ isMap: 'ismap',
67
+ maxLength: 'maxlength',
68
+ minLength: 'minlength',
69
+ noModule: 'nomodule',
70
+ noValidate: 'novalidate',
71
+ readOnly: 'readonly',
72
+ referrerPolicy: 'referrerpolicy',
73
+ tabIndex: 'tabindex',
74
+ useMap: 'usemap',
75
+ });
12
76
  /** Convert camelCase to kebab-case for attribute reflection. */
13
- function toKebabCase(str) {
14
- return str.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
77
+ export function toKebabCase(str) {
78
+ const mapped = propertyToAttribute[str];
79
+ return mapped ?? str.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
15
80
  }
16
81
  /**
17
82
  * Shared logic for installing a reactive getter/setter on a class prototype.
package/dist/element.d.ts CHANGED
@@ -19,7 +19,13 @@ export declare class WebUIElement extends HTMLElement {
19
19
  disconnectedCallback(): void;
20
20
  /** Dispatch a bubbling custom event. Uses composed:true when in shadow DOM. */
21
21
  $emit(name: string, detail?: unknown): boolean;
22
- /** Populate @observable properties from router state. */
22
+ /** Populate @observable properties from router state.
23
+ *
24
+ * Each property is set through its reactive setter, which coalesces
25
+ * updates into a single pending microtask. We then synchronously
26
+ * flush those pending path updates so the DOM is current before any
27
+ * view-transition snapshot captures it.
28
+ */
23
29
  setInitialState(state: Record<string, unknown>): void;
24
30
  /**
25
31
  * Apply SSR state from the global `window.__webui_state` object.
package/dist/element.js CHANGED
@@ -160,14 +160,18 @@ export class WebUIElement extends HTMLElement {
160
160
  root = this.shadowRoot;
161
161
  isSSR = true;
162
162
  }
163
- else if (this.childNodes.length > 0) {
164
- // SSR — element already has server-rendered children (light DOM).
165
- // Reuse existing DOM regardless of shadow preference.
163
+ else if (this.childNodes.length > 0 && !meta.sd) {
164
+ // SSR light-DOM — element already has server-rendered children.
165
+ // Only treat as SSR when the template does NOT explicitly declare
166
+ // shadow DOM (meta.sd). When meta.sd is set, existing children
167
+ // are slot content from an SPA partial, not SSR output.
166
168
  root = this;
167
169
  isSSR = true;
168
170
  }
169
171
  else if (wantShadow) {
170
- // Shadow DOM client-created
172
+ // Shadow DOM client-created (or SPA partial with slot content).
173
+ // Existing children are slot content — they stay in light DOM
174
+ // and project through the template's <slot>.
171
175
  root = this.attachShadow({ mode: 'open' });
172
176
  const fragment = this.$parseTemplate(meta);
173
177
  root.appendChild(fragment);
@@ -195,6 +199,13 @@ export class WebUIElement extends HTMLElement {
195
199
  this.$meta = meta;
196
200
  this.$hydrated = true;
197
201
  this.$ready = true;
202
+ // Client-created components: flush current attr/observable values
203
+ // into the freshly-wired template DOM. Call $updateInstance directly
204
+ // to avoid the $update() path-index build — it will be lazy-built
205
+ // on the first reactive change instead.
206
+ if (!isSSR) {
207
+ this.$updateInstance(this.$root);
208
+ }
198
209
  hydrationEnd();
199
210
  }
200
211
  disconnectedCallback() { }
@@ -207,7 +218,13 @@ export class WebUIElement extends HTMLElement {
207
218
  detail,
208
219
  }));
209
220
  }
210
- /** Populate @observable properties from router state. */
221
+ /** Populate @observable properties from router state.
222
+ *
223
+ * Each property is set through its reactive setter, which coalesces
224
+ * updates into a single pending microtask. We then synchronously
225
+ * flush those pending path updates so the DOM is current before any
226
+ * view-transition snapshot captures it.
227
+ */
211
228
  setInitialState(state) {
212
229
  const names = getObservableNames(this.constructor);
213
230
  for (const key of Object.keys(state)) {
@@ -215,6 +232,7 @@ export class WebUIElement extends HTMLElement {
215
232
  this[key] = state[key];
216
233
  }
217
234
  }
235
+ this.$flushUpdates();
218
236
  }
219
237
  /**
220
238
  * Apply SSR state from the global `window.__webui_state` object.
@@ -1059,6 +1077,13 @@ export class WebUIElement extends HTMLElement {
1059
1077
  case ATTR_KIND_COMPLEX: {
1060
1078
  const v = this.$resolveValue(b.path, b.scope);
1061
1079
  el[b.name] = v;
1080
+ // If the target is a WebUIElement, flush its pending updates
1081
+ // synchronously so child <for> loops re-render immediately.
1082
+ // Without this, the child's microtask-coalesced update runs
1083
+ // too late for view transitions that snapshot the DOM.
1084
+ const flush = el['$flushUpdates'];
1085
+ if (typeof flush === 'function')
1086
+ flush.call(el);
1062
1087
  break;
1063
1088
  }
1064
1089
  case ATTR_KIND_BOOLEAN: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/webui-framework",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "type": "module",
5
5
  "description": "WebUI Framework Next — Preact-inspired lightweight Web Component runtime with SSR hydration. 15KB minified, compiled-template path mapping, no hydration markers.",
6
6
  "license": "MIT",
@@ -19,7 +19,7 @@
19
19
  "@playwright/test": "^1.58.2",
20
20
  "@types/node": "^25.3.5",
21
21
  "typescript": "^5.9.3",
22
- "@microsoft/webui-test-support": "0.0.7"
22
+ "@microsoft/webui-test-support": "0.0.9"
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsc",