Skip to content

Commit

Permalink
fix: support spread event handlers on native tag (#2231)
Browse files Browse the repository at this point in the history
  • Loading branch information
DylanPiercey committed May 16, 2024
1 parent 9d94b0d commit 2200c45
Show file tree
Hide file tree
Showing 26 changed files with 500 additions and 61 deletions.
4 changes: 2 additions & 2 deletions .sizes.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
{
"name": "*",
"total": {
"min": 13393,
"brotli": 5228
"min": 13478,
"brotli": 5270
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-tags/src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export enum AccessorChar {
LoopScopeArray = "!",
LoopScopeMap = "(",
LoopValue = ")",
PreviousAttributes = "~",
EventAttributes = "~",
}

export enum NodeType {
Expand Down
1 change: 1 addition & 0 deletions packages/runtime-tags/src/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export {
html,
attr,
attrs,
attrsEvents,
classAttr,
styleAttr,
props,
Expand Down
66 changes: 34 additions & 32 deletions packages/runtime-tags/src/dom/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
import { getAbortSignal } from "./abort-signal";
import { on } from "./event";

const eventHandlerReg = /^on[A-Z-]/;

export function isDocumentFragment(node: Node): node is DocumentFragment {
return node.nodeType === NodeType.DocumentFragment;
}
Expand Down Expand Up @@ -49,48 +51,48 @@ export function attrs(
elementAccessor: Accessor,
nextAttrs: Record<string, unknown>,
) {
const prevAttrs: typeof nextAttrs =
scope[elementAccessor + AccessorChar.PreviousAttributes] || {};
const element = scope[elementAccessor] as Element;
let events: undefined | Record<string, unknown>;

if (prevAttrs) {
for (const name in prevAttrs) {
if (!(nextAttrs && name in nextAttrs)) {
element.removeAttribute(name);
}
for (const { name } of element.attributes) {
if (!(nextAttrs && name in nextAttrs)) {
element.removeAttribute(name);
}
}

// https://jsperf.com/object-keys-vs-for-in-with-closure/194
for (const name in nextAttrs) {
const value = nextAttrs[name];
if (value !== prevAttrs[name]) {
switch (name) {
case "class":
classAttr(element, value);
break;
case "style":
styleAttr(element, value);
break;
case "renderBody":
break;
default:
if (/^on[A-Z-]/.test(name)) {
on(
element,
(name[2] === "-"
? name.slice(3)
: name.slice(2).toLowerCase()) as any,
value as any,
);
} else {
attr(element, name, value);
}
break;
}
switch (name) {
case "class":
classAttr(element, value);
break;
case "style":
styleAttr(element, value);
break;
case "renderBody":
break;
default:
if (eventHandlerReg.test(name)) {
(events ??= {})[
name[2] === "-" ? name.slice(3) : name.slice(2).toLowerCase()
] = value;
} else {
attr(element, name, value);
}
break;
}
}

scope[elementAccessor + AccessorChar.PreviousAttributes] = nextAttrs;
scope[elementAccessor + AccessorChar.EventAttributes] = events;
}

export function attrsEvents(scope: Scope, elementAccessor: Accessor) {
const element = scope[elementAccessor] as Element;
const events = scope[elementAccessor + AccessorChar.EventAttributes];
for (const name in events) {
on(element, name as any, events[name] as any);
}
}

const doc = document;
Expand Down
25 changes: 22 additions & 3 deletions packages/runtime-tags/src/html/attrs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { classValue, isVoid, styleValue } from "../common/helpers";
import { AccessorChar, type Accessor } from "../common/types";
import { ensureScopeWithId } from "./writer";

export function classAttr(val: unknown) {
return stringAttr("class", classValue(val));
Expand All @@ -12,8 +14,13 @@ export function attr(name: string, val: unknown) {
return isVoid(val) ? "" : nonVoidAttr(name, val);
}

export function attrs(data: Record<string, unknown>) {
export function attrs(
data: Record<string, unknown>,
elementAccessor?: Accessor,
scopeId?: number,
) {
let result = "";
let events: undefined | Record<string, unknown>;

for (const name in data) {
const val = data[name];
Expand All @@ -29,13 +36,25 @@ export function attrs(data: Record<string, unknown>) {
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
// Avoids outputting attribute names that would be invalid or
// be an event handler / renderBody.
if (!(isVoid(val) || /^on[A-Z-]|^renderBody$|[\s/>"'=]/.test(name))) {
result += nonVoidAttr(name, val);
if (!isVoid(val)) {
if (/^on[A-Z-]/.test(name)) {
(events ??= {})[
name[2] === "-" ? name.slice(3) : name.slice(2).toLowerCase()
] = val;
} else if (!/^renderBody$|[\s/>"'=]/.test(name)) {
result += nonVoidAttr(name, val);
}
}
break;
}
}

if (events && elementAccessor) {
ensureScopeWithId(scopeId!)[
(elementAccessor + AccessorChar.EventAttributes) as any
] = events;
}

return result;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Render {}
```html
<button>
0
</button>
```


# Render
container.querySelector("button").click()

```html
<button>
1
</button>
```


# Render
container.querySelector("button").click()

```html
<button>
2
</button>
```


# Render
container.querySelector("button").click()

```html
<button>
3
</button>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Render {}
```html
<button>
0
</button>
```

# Mutations
```
inserted button0
```


# Render
container.querySelector("button").click()

```html
<button>
1
</button>
```

# Mutations
```
button0/#text0: "0" => "1"
```


# Render
container.querySelector("button").click()

```html
<button>
2
</button>
```

# Mutations
```
button0/#text0: "1" => "2"
```


# Render
container.querySelector("button").click()

```html
<button>
3
</button>
```

# Mutations
```
button0/#text0: "2" => "3"
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { attrs as _attrs2, attrsEvents as _attrsEvents, conditional as _conditional, register as _register, queueEffect as _queueEffect, value as _value, createRenderer as _createRenderer, createTemplate as _createTemplate } from "@marko/runtime-tags/debug/dom";
const _dynamicTagName = /* @__PURE__ */_conditional("#text/1");
const _attrs_effect = _register("packages/translator-tags/src/__tests__/fixtures/body-content-new/components/FancyButton.marko_0_attrs", _scope => _attrsEvents(_scope, "#button/0"));
const _attrs = /* @__PURE__ */_value("attrs", (_scope, attrs) => {
_attrs2(_scope, "#button/0", attrs);
_queueEffect(_scope, _attrs_effect);
});
const _renderBody = /* @__PURE__ */_value("renderBody", (_scope, renderBody) => _dynamicTagName(_scope, renderBody), void 0, _dynamicTagName);
const _destructure2 = (_scope, _destructure, _clean) => {
let renderBody, attrs;
if (!_clean) ({
renderBody,
...attrs
} = _destructure);
_renderBody(_scope, renderBody, _clean);
_attrs(_scope, attrs, _clean);
};
const _input = /* @__PURE__ */_value("input", (_scope, input) => _destructure2(_scope, input), void 0, _destructure2);
export const _args_ = (_scope, _destructure3, _clean) => {
let input;
if (!_clean) [input] = _destructure3;
_input(_scope, input, _clean);
};
export const _template_ = "<button><!></button>";
export const _walks_ = /* get, next(1), replace, out(1) */" D%l";
export const _setup_ = function () {};
export default /* @__PURE__ */_createTemplate( /* @__PURE__ */_createRenderer(_template_, _walks_, _setup_, void 0, void 0, _args_), "packages/translator-tags/src/__tests__/fixtures/body-content-new/components/FancyButton.marko");
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { init } from "@marko/runtime-tags/debug/dom";
import "./template.marko";
import "./components/FancyButton.marko";
init();
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { queueSource as _queueSource, data as _data, register as _register, bindRenderer as _bindRenderer, inChild as _inChild, dynamicClosure as _dynamicClosure, registerSubscriber as _registerSubscriber, createRenderer as _createRenderer, dynamicSubscribers as _dynamicSubscribers, value as _value, createTemplate as _createTemplate } from "@marko/runtime-tags/debug/dom";
import { _setup_ as _FancyButton, _args_ as _FancyButton_args, _template_ as _FancyButton_template, _walks_ as _FancyButton_walks } from "./components/FancyButton.marko";
const _onClick = _register("packages/translator-tags/src/__tests__/fixtures/body-content-new/template.marko_0/onClick", _scope => {
const {
clickCount
} = _scope;
return function () {
_queueSource(_scope, _clickCount, clickCount + 1);
};
});
const _clickCount$FancyButtonBody = _registerSubscriber("packages/translator-tags/src/__tests__/fixtures/body-content-new/template.marko_1_clickCount/subscriber", /* @__PURE__ */_dynamicClosure("clickCount", (_scope, clickCount) => _data(_scope["#text/0"], clickCount)));
const _FancyButtonBody = _register("packages/translator-tags/src/__tests__/fixtures/body-content-new/template.marko_1_renderer", /* @__PURE__ */_createRenderer(" ", /* get */" ", void 0, [_clickCount$FancyButtonBody]));
const _clickCount = /* @__PURE__ */_value("clickCount", (_scope, clickCount) => _FancyButton_args(_scope["#childScope/0"], [{
onClick: _onClick(_scope),
renderBody: /* @__PURE__ */_bindRenderer(_scope, _FancyButtonBody)
}]), _dynamicSubscribers("clickCount"), _inChild("#childScope/0", _FancyButton_args));
const _setup = _scope => {
_FancyButton(_scope["#childScope/0"]);
_clickCount(_scope, 0);
};
export const _template_ = `${_FancyButton_template}`;
export const _walks_ = /* beginChild, _FancyButton_walks, endChild */`/${_FancyButton_walks}&`;
export const _setup_ = _setup;
export default /* @__PURE__ */_createTemplate( /* @__PURE__ */_createRenderer(_template_, _walks_, _setup_), "packages/translator-tags/src/__tests__/fixtures/body-content-new/template.marko");
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { attrs as _attrs2, write as _write, dynamicTagInput as _dynamicTagInput, markResumeControlEnd as _markResumeControlEnd, markResumeNode as _markResumeNode, writeEffect as _writeEffect, writeScope as _writeScope, nextScopeId as _nextScopeId, createRenderer as _createRenderer, createTemplate as _createTemplate } from "@marko/runtime-tags/debug/html";
const _renderer = /* @__PURE__ */_createRenderer((input, _tagVar) => {
const _scope0_id = _nextScopeId();
const {
renderBody,
...attrs
} = input;
_write(`<button${_attrs2(attrs, "#button/0", _scope0_id)}>`);
const _dynamicScope = _dynamicTagInput(renderBody, {});
_write(`${_markResumeControlEnd(_scope0_id, "#text/1")}</button>${_markResumeNode(_scope0_id, "#button/0")}`);
_writeEffect(_scope0_id, "packages/translator-tags/src/__tests__/fixtures/body-content-new/components/FancyButton.marko_0_attrs");
_writeScope(_scope0_id, {
"attrs": attrs,
"#text/1!": _dynamicScope,
"#text/1(": renderBody
});
});
export default /* @__PURE__ */_createTemplate(_renderer, "packages/translator-tags/src/__tests__/fixtures/body-content-new/components/FancyButton.marko");
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { escapeXML as _escapeXML, markResumeNode as _markResumeNode, write as _write, ensureScopeWithId as _ensureScopeWithId, writeEffect as _writeEffect, writeScope as _writeScope, nextScopeId as _nextScopeId, register as _register, createRenderer as _createRenderer, peekNextScope as _peekNextScope, createTemplate as _createTemplate } from "@marko/runtime-tags/debug/html";
import _FancyButton from "./components/FancyButton.marko";
const _renderer = /* @__PURE__ */_createRenderer((input, _tagVar) => {
const _scope0_id = _nextScopeId();
const clickCount = 0;
const _childScope = _peekNextScope();
_FancyButton._({
onClick: _register(function () {
clickCount++;
}, "packages/translator-tags/src/__tests__/fixtures/body-content-new/template.marko_0/onClick", _scope0_id),
renderBody: /* @__PURE__ */_createRenderer(() => {
const _scope1_id = _nextScopeId();
_write(`${_escapeXML(clickCount)}${_markResumeNode(_scope1_id, "#text/0")}`);
_writeEffect(_scope1_id, "packages/translator-tags/src/__tests__/fixtures/body-content-new/template.marko_1_clickCount/subscriber");
_writeScope(_scope1_id, {
"_": _ensureScopeWithId(_scope0_id)
});
})
});
_writeScope(_scope0_id, {
"clickCount": clickCount,
"#childScope/0": _childScope
});
});
export default /* @__PURE__ */_createTemplate(_renderer, "packages/translator-tags/src/__tests__/fixtures/body-content-new/template.marko");
Loading

0 comments on commit 2200c45

Please sign in to comment.