@sigil-dev/runtime 0.9.7 → 0.9.10
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/index.test.ts +22 -0
- package/index.ts +43 -10
- package/package.json +1 -1
package/index.test.ts
CHANGED
|
@@ -805,6 +805,28 @@ describe("setProp", () => {
|
|
|
805
805
|
setProp(input, "value", "hello");
|
|
806
806
|
expect(input.value).toBe("hello");
|
|
807
807
|
});
|
|
808
|
+
|
|
809
|
+
test("routes getter-only HTML properties through setAttribute", () => {
|
|
810
|
+
const input = claim([], "input") as HTMLInputElement;
|
|
811
|
+
expect(() => setProp(input, "list", "people")).not.toThrow();
|
|
812
|
+
expect(input.getAttribute("list")).toBe("people");
|
|
813
|
+
setProp(input, "list", null);
|
|
814
|
+
expect(input.hasAttribute("list")).toBe(false);
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
test("routes unknown HTML props through setAttribute", () => {
|
|
818
|
+
const div = claim([], "div");
|
|
819
|
+
setProp(div, "popovertarget", "menu");
|
|
820
|
+
expect(div.getAttribute("popovertarget")).toBe("menu");
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
test("keeps IDL semantics for writable props like checked", () => {
|
|
824
|
+
const input = claim([], "input") as HTMLInputElement;
|
|
825
|
+
input.type = "checkbox";
|
|
826
|
+
setProp(input, "checked", true);
|
|
827
|
+
expect(input.checked).toBe(true);
|
|
828
|
+
expect(input.hasAttribute("checked")).toBe(false);
|
|
829
|
+
});
|
|
808
830
|
});
|
|
809
831
|
|
|
810
832
|
describe("claimText", () => {
|
package/index.ts
CHANGED
|
@@ -629,11 +629,14 @@ export function claim(
|
|
|
629
629
|
}
|
|
630
630
|
|
|
631
631
|
/**
|
|
632
|
-
* Write a JSX prop to an element. On HTML elements this is
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
*
|
|
636
|
-
*
|
|
632
|
+
* Write a JSX prop to an element. On HTML elements this is `el[name] = value`
|
|
633
|
+
* (preserves IDL semantics for value/checked/etc) whenever the element has a
|
|
634
|
+
* writable property of that name. Names with no writable property go through
|
|
635
|
+
* setAttribute instead, like SSR does: some IDL attributes are getter-only
|
|
636
|
+
* (`input.list`, `input.form`, `button.form`), and assigning them throws and
|
|
637
|
+
* aborts the whole render; unknown names would otherwise land as a bare JS
|
|
638
|
+
* expando the DOM never sees. On SVG-namespace elements everything is
|
|
639
|
+
* attribute-routed — IDL property assignment throws there too (`svg.width`).
|
|
637
640
|
*/
|
|
638
641
|
const SVG_ATTR_FIX: Record<string, string> = {
|
|
639
642
|
className: "class",
|
|
@@ -643,13 +646,43 @@ const SVG_ATTR_FIX: Record<string, string> = {
|
|
|
643
646
|
// Never attribute-routed, even inside SVG.
|
|
644
647
|
const SVG_PROP_ONLY = new Set(["textContent", "innerHTML", "outerHTML"]);
|
|
645
648
|
|
|
649
|
+
// Per-prototype cache of "can `el[name] = v` be assigned?".
|
|
650
|
+
const writableCache = new WeakMap<object, Map<string, boolean>>();
|
|
651
|
+
|
|
652
|
+
function isWritableProp(el: Element, name: string): boolean {
|
|
653
|
+
const proto = Object.getPrototypeOf(el) as object;
|
|
654
|
+
let cache = writableCache.get(proto);
|
|
655
|
+
if (!cache) writableCache.set(proto, (cache = new Map()));
|
|
656
|
+
let writable = cache.get(name);
|
|
657
|
+
if (writable === undefined) {
|
|
658
|
+
writable = false;
|
|
659
|
+
for (let o: object | null = proto; o; o = Object.getPrototypeOf(o)) {
|
|
660
|
+
const d = Object.getOwnPropertyDescriptor(o, name);
|
|
661
|
+
if (d) {
|
|
662
|
+
writable = !!(d.set || d.writable);
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
cache.set(name, writable);
|
|
667
|
+
}
|
|
668
|
+
// Own properties (expandos set earlier) are always assignable.
|
|
669
|
+
return writable || Object.hasOwn(el, name);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function setAttr(el: Element, attr: string, value: unknown): void {
|
|
673
|
+
if (value == null || value === false) el.removeAttribute(attr);
|
|
674
|
+
else el.setAttribute(attr, value === true ? "" : String(value));
|
|
675
|
+
}
|
|
676
|
+
|
|
646
677
|
export function setProp(el: Element, name: string, value: unknown): void {
|
|
647
|
-
if (el.namespaceURI === SVG_NS
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
else el
|
|
651
|
-
} else {
|
|
678
|
+
if (el.namespaceURI === SVG_NS) {
|
|
679
|
+
if (SVG_PROP_ONLY.has(name))
|
|
680
|
+
(el as unknown as Record<string, unknown>)[name] = value;
|
|
681
|
+
else setAttr(el, SVG_ATTR_FIX[name] ?? name, value);
|
|
682
|
+
} else if (isWritableProp(el, name)) {
|
|
652
683
|
(el as unknown as Record<string, unknown>)[name] = value;
|
|
684
|
+
} else {
|
|
685
|
+
setAttr(el, name, value);
|
|
653
686
|
}
|
|
654
687
|
}
|
|
655
688
|
|