Compare commits
2
Commits
84e58b1798
...
0ddb481159
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ddb481159 | ||
|
|
3441b96362 |
@@ -341,7 +341,7 @@
|
|||||||
},
|
},
|
||||||
"packages/csr": {
|
"packages/csr": {
|
||||||
"name": "@wrnexus/csr",
|
"name": "@wrnexus/csr",
|
||||||
"version": "0.8.26",
|
"version": "0.8.27",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
},
|
},
|
||||||
@@ -661,7 +661,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@wrnexus/ui",
|
"name": "@wrnexus/ui",
|
||||||
"version": "0.8.22",
|
"version": "0.8.23",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
"packages/ui/components/TogglePassword.wrn": "6bd4d9fa82b0c8e80178430d5a42b1b3ec9725fb62be158585f68af9338c901a",
|
"packages/ui/components/TogglePassword.wrn": "6bd4d9fa82b0c8e80178430d5a42b1b3ec9725fb62be158585f68af9338c901a",
|
||||||
"packages/ui/components/TreeView.wrn": "f54d8495730203b923629bcfb167d5ca41236af0101ea2723d33354413555b4b",
|
"packages/ui/components/TreeView.wrn": "f54d8495730203b923629bcfb167d5ca41236af0101ea2723d33354413555b4b",
|
||||||
"packages/ui/components/Typography.wrn": "9676af57f2a937b21f45512e0a290b5bee4a16267fbf404850297d29645d88b5",
|
"packages/ui/components/Typography.wrn": "9676af57f2a937b21f45512e0a290b5bee4a16267fbf404850297d29645d88b5",
|
||||||
"packages/ui/components/WysiwygEditor.wrn": "3f9d1765184581e3c1f335769a9f19969917450258bc95ba076980d99f5610b5",
|
"packages/ui/components/WysiwygEditor.wrn": "a858ebd4e6e2672463ba57d4d049176ca1cc98dac32ab3df3638d6a9cdcd0f2d",
|
||||||
"packages/ui/components/alert.wrn": "d42287e41b918b1e19962e9462ef203002eb5480acac984f115b711e9dbe663d",
|
"packages/ui/components/alert.wrn": "d42287e41b918b1e19962e9462ef203002eb5480acac984f115b711e9dbe663d",
|
||||||
"packages/ui/components/avatar.wrn": "8bc705e459b13c7f063c57e1506ce540e50fa8ef9020ae85b6e8b6a3579fc5dc",
|
"packages/ui/components/avatar.wrn": "8bc705e459b13c7f063c57e1506ce540e50fa8ef9020ae85b6e8b6a3579fc5dc",
|
||||||
"packages/ui/components/badge.wrn": "e44e33633fb34e897696cd9290f210108e35a3e2dc3b2a4a367411f45c69a1e1",
|
"packages/ui/components/badge.wrn": "e44e33633fb34e897696cd9290f210108e35a3e2dc3b2a4a367411f45c69a1e1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/csr",
|
"name": "@wrnexus/csr",
|
||||||
"version": "0.8.26",
|
"version": "0.8.27",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -2724,9 +2724,17 @@ export const REACTIVE_RUNTIME = String.raw`
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
node.addEventListener(outName, domListener);
|
/*
|
||||||
|
* focus and blur do not bubble, so a bubble-phase listener on the
|
||||||
|
* component root never hears its own input or button take focus --
|
||||||
|
* the binding silently did nothing. Capture reaches the descendant.
|
||||||
|
* The same flag must be passed to removeEventListener or the
|
||||||
|
* listener outlives the component.
|
||||||
|
*/
|
||||||
|
var capture = outName === "focus" || outName === "blur";
|
||||||
|
node.addEventListener(outName, domListener, capture);
|
||||||
cleanupCallbacks.push(function () {
|
cleanupCallbacks.push(function () {
|
||||||
node.removeEventListener(outName, domListener);
|
node.removeEventListener(outName, domListener, capture);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1781,3 +1781,57 @@ test("a class binding inside data-for follows state the row never mentions", ()
|
|||||||
expect(items()[0]?.classList.contains("is-active")).toBe(false);
|
expect(items()[0]?.classList.contains("is-active")).toBe(false);
|
||||||
expect(items()[1]?.classList.contains("is-active")).toBe(true);
|
expect(items()[1]?.classList.contains("is-active")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("a @focus binding on a component tag fires for a focusable element inside it", () => {
|
||||||
|
/*
|
||||||
|
* focus and blur do NOT bubble. The runtime bound every output-named DOM
|
||||||
|
* fallback listener in the bubble phase, so a parent writing @focus on a
|
||||||
|
* component tag never heard the component's own input or button take focus:
|
||||||
|
* the event fired on the descendant and stopped there. Nothing errored --
|
||||||
|
* the binding simply did nothing, which is why the library still declares
|
||||||
|
* focus/blur outputs on components that could never deliver them
|
||||||
|
* (button, TextLink, WysiwygEditor).
|
||||||
|
*
|
||||||
|
* The ui ratchet for "outputs nothing emits" excludes natively-named
|
||||||
|
* outputs on the stated grounds that a native event reaches the root
|
||||||
|
* anyway. That reasoning holds for click and change, which bubble, and
|
||||||
|
* fails for focus and blur, which do not.
|
||||||
|
*/
|
||||||
|
const win = mount(
|
||||||
|
`<div data-scope="seen: ''">` +
|
||||||
|
`<span id="out">{seen}</span>` +
|
||||||
|
`<div data-scope="n: 0" data-wrn-hydration="Field:x">` +
|
||||||
|
`<div data-wrn-events="focus" data-wrn-out-focus="seen = 'focused'">` +
|
||||||
|
`<button id="inner">press</button>` +
|
||||||
|
`</div></div></div>`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const inner = win.document.querySelector("#inner") as unknown as {
|
||||||
|
dispatchEvent: (event: unknown) => boolean;
|
||||||
|
};
|
||||||
|
// A real focus event: fires on the descendant, does not bubble.
|
||||||
|
const FocusEventCtor = (win as unknown as { Event: typeof Event }).Event;
|
||||||
|
inner.dispatchEvent(new FocusEventCtor("focus", { bubbles: false }));
|
||||||
|
|
||||||
|
expect(win.document.querySelector("#out")?.textContent).toBe("focused");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a @change binding still fires for a bubbling event inside a component", () => {
|
||||||
|
// The companion case: change DOES bubble, and must keep working unchanged.
|
||||||
|
const win = mount(
|
||||||
|
`<div data-scope="seen: ''">` +
|
||||||
|
`<span id="out">{seen}</span>` +
|
||||||
|
`<div data-scope="n: 0" data-wrn-hydration="Field:y">` +
|
||||||
|
`<div data-wrn-events="change" data-wrn-out-change="seen = 'changed'">` +
|
||||||
|
`<input id="inner" />` +
|
||||||
|
`</div></div></div>`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const inner = win.document.querySelector("#inner") as unknown as {
|
||||||
|
dispatchEvent: (event: unknown) => boolean;
|
||||||
|
};
|
||||||
|
const EventCtor = (win as unknown as { Event: typeof Event }).Event;
|
||||||
|
inner.dispatchEvent(new EventCtor("change", { bubbles: true }));
|
||||||
|
|
||||||
|
expect(win.document.querySelector("#out")?.textContent).toBe("changed");
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
component WysiwygEditor {
|
component WysiwygEditor {
|
||||||
|
// A chrome shell around a slotted editor, not an editor itself: it renders a
|
||||||
|
// heading, a description, an optional item strip and a slot. It therefore
|
||||||
|
// emits none of these outputs on its own -- they forward whatever the slotted
|
||||||
|
// control raises. Slot a textarea or input and a parent @change or @input
|
||||||
|
// hears it, because those events bubble to this root; @focus and @blur arrive
|
||||||
|
// too, since the runtime binds the non-bubbling pair in the capture phase.
|
||||||
|
// Slot nothing interactive and there is nothing to hear.
|
||||||
outputs {
|
outputs {
|
||||||
input(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
|
input(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
|
||||||
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
|
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/ui",
|
"name": "@wrnexus/ui",
|
||||||
"version": "0.8.22",
|
"version": "0.8.23",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -3320,6 +3320,14 @@ test("no component gains an output that nothing ever emits", () => {
|
|||||||
* assumed: invokeComponentOutput falls back to dispatchComponentEvent when no
|
* assumed: invokeComponentOutput falls back to dispatchComponentEvent when no
|
||||||
* handler is registered, and a parent @click on a component tag is also bound
|
* handler is registered, and a parent @click on a component tag is also bound
|
||||||
* as an ordinary DOM listener, so a natively-named output does arrive.
|
* as an ordinary DOM listener, so a natively-named output does arrive.
|
||||||
|
*
|
||||||
|
* That reasoning held only for events that bubble. focus and blur do not, so
|
||||||
|
* the bubble-phase fallback never heard a descendant take focus and every
|
||||||
|
* focus/blur output here was undeliverable -- silently, since a missing
|
||||||
|
* output raises nothing. The runtime now binds those two in the capture
|
||||||
|
* phase, which is what makes their exclusion honest rather than convenient.
|
||||||
|
* See "a @focus binding on a component tag fires for a focusable element
|
||||||
|
* inside it" in packages/csr/test/reactive.test.ts.
|
||||||
*/
|
*/
|
||||||
const native = new Set([
|
const native = new Set([
|
||||||
"click",
|
"click",
|
||||||
|
|||||||
Reference in New Issue
Block a user