Skip to content

Commit 5166022

Browse files
committed
🩹 anchor html events first render
1 parent 8777337 commit 5166022

File tree

6 files changed

+82
-32
lines changed

6 files changed

+82
-32
lines changed

client/anchorableNode.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ import router from './router'
22

33
export function anchorableElement(element) {
44
const links = element.querySelectorAll('a[href^="/"]:not([target])')
5+
if (element.dataset.nullstack) return
6+
element.dataset.nullstack = true
57
for (const link of links) {
6-
link.onclick = (event) => {
7-
if (event.ctrlKey || event.shiftKey) return
8-
event.preventDefault()
9-
router.url = link.getAttribute('href')
10-
}
8+
link.addEventListener('click', (event) => {
9+
if (!event.ctrlKey && !event.shiftKey && !event.altKey) {
10+
event.preventDefault()
11+
router.url = link.getAttribute('href')
12+
}
13+
})
1114
}
1215
}

client/render.js

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,62 @@
1-
import {isFalse, isText} from '../shared/nodes';
2-
import {anchorableElement} from './anchorableNode';
1+
import { isFalse, isText } from '../shared/nodes';
2+
import { anchorableElement } from './anchorableNode';
33

44
export default function render(node, options) {
55

6-
if(isFalse(node) || node.type === 'head') {
6+
if (isFalse(node) || node.type === 'head') {
77
return document.createComment("");
88
}
99

10-
if(isText(node)) {
10+
if (isText(node)) {
1111
return document.createTextNode(node);
1212
}
1313

1414
const svg = (options && options.svg) || node.type === 'svg';
1515

1616
let element;
17-
if(svg) {
17+
if (svg) {
1818
element = document.createElementNS("http://www.w3.org/2000/svg", node.type);
1919
} else {
2020
element = document.createElement(node.type);
2121
}
2222

23-
if(node.instance) {
23+
if (node.instance) {
2424
node.instance._self.element = element;
2525
}
2626

27-
for(let name in node.attributes) {
28-
if(name === 'html') {
27+
for (let name in node.attributes) {
28+
if (name === 'html') {
2929
element.innerHTML = node.attributes[name];
3030
anchorableElement(element);
31-
} else if(name.startsWith('on')) {
31+
} else if (name.startsWith('on')) {
3232
const eventName = name.replace('on', '');
3333
const key = '_event.' + eventName;
3434
node[key] = (event) => {
35-
if(node.attributes.default !== true) {
35+
if (node.attributes.default !== true) {
3636
event.preventDefault();
3737
}
38-
node.attributes[name]({...node.attributes, event});
38+
node.attributes[name]({ ...node.attributes, event });
3939
};
4040
element.addEventListener(eventName, node[key]);
4141
} else {
42-
const type = typeof(node.attributes[name]);
43-
if(type !== 'object' && type !== 'function') {
44-
if(name != 'value' && node.attributes[name] === true) {
42+
const type = typeof (node.attributes[name]);
43+
if (type !== 'object' && type !== 'function') {
44+
if (name != 'value' && node.attributes[name] === true) {
4545
element.setAttribute(name, '');
46-
} else if(name == 'value' || (node.attributes[name] !== false && node.attributes[name] !== null && node.attributes[name] !== undefined)) {
46+
} else if (name == 'value' || (node.attributes[name] !== false && node.attributes[name] !== null && node.attributes[name] !== undefined)) {
4747
element.setAttribute(name, node.attributes[name]);
4848
}
4949
}
5050
}
5151
}
5252

53-
if(!node.attributes.html) {
54-
for(let i = 0; i < node.children.length; i++) {
55-
const child = render(node.children[i], {svg});
53+
if (!node.attributes.html) {
54+
for (let i = 0; i < node.children.length; i++) {
55+
const child = render(node.children[i], { svg });
5656
element.appendChild(child);
5757
}
58-
59-
if(node.type == 'select') {
58+
59+
if (node.type == 'select') {
6060
element.value = node.attributes.value;
6161
}
6262
}

client/rerender.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ export default function rerender(selector, current, next) {
6060
if (name === 'html') {
6161
if (next.attributes[name] !== current.attributes[name]) {
6262
selector.innerHTML = next.attributes[name];
63-
anchorableElement(selector);
6463
}
64+
anchorableElement(selector);
6565
} else if (name === 'checked') {
6666
if (next.attributes[name] !== selector.value) {
6767
selector.checked = next.attributes[name];

plugins/anchorable.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ function transform({ node, router }) {
1313
const originalEvent = node.attributes.onclick
1414
node.attributes.default = true
1515
node.attributes.onclick = ({ event }) => {
16-
if (event.ctrlKey || event.shiftKey) return
17-
event.preventDefault()
18-
router.url = node.attributes.href
16+
if (!event.ctrlKey && !event.shiftKey && !event.altKey) {
17+
event.preventDefault()
18+
router.url = node.attributes.href
19+
}
1920
if (originalEvent) {
2021
setTimeout(() => {
2122
originalEvent({ ...node.attributes, event })

tests/src/AnchorModifiers.njs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,23 @@ class AnchorModifiers extends Nullstack {
66
<a href="/anchor-modifiers?source=html">html</a>
77
`
88

9-
render() {
9+
hydrate(context) {
10+
context.self.element.querySelector('a').addEventListener('click', () => {
11+
context.clickedHTML = true
12+
})
13+
}
14+
15+
clickJSX(context) {
16+
context.clickedJSX = true
17+
}
18+
19+
render({ clickedJSX, clickedHTML }) {
1020
return (
11-
<div>
21+
<div data-clicked-jsx={clickedJSX} data-clicked-html={clickedHTML}>
1222
<div html={this.html} />
13-
<a href="/anchor-modifiers?source=jsx">jsx</a>
23+
<a href="/anchor-modifiers?source=jsx" onclick={this.clickJSX}>
24+
jsx
25+
</a>
1426
</div>
1527
)
1628
}

tests/src/AnchorModifiers.test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ describe('AnchorModifiers jsx', () => {
2020
expect(url).toEqual('http://localhost:6969/anchor-modifiers');
2121
});
2222

23+
test('Clicking html link with alt downloads the link', async () => {
24+
await page.keyboard.down('Alt');
25+
await page.click('[href="/anchor-modifiers?source=html"]');
26+
await page.keyboard.up('Alt');
27+
const url = await page.url()
28+
expect(url).toEqual('http://localhost:6969/anchor-modifiers');
29+
});
30+
2331
test('Clicking jsx link with shift opens in new window', async () => {
2432
await page.keyboard.down('Shift');
2533
await page.click('[href="/anchor-modifiers?source=jsx"]');
@@ -36,4 +44,30 @@ describe('AnchorModifiers jsx', () => {
3644
expect(url).toEqual('http://localhost:6969/anchor-modifiers');
3745
});
3846

47+
test('Clicking jsx link with alt downloads the link', async () => {
48+
await page.keyboard.down('Alt');
49+
await page.click('[href="/anchor-modifiers?source=jsx"]');
50+
await page.keyboard.up('Alt');
51+
const url = await page.url()
52+
expect(url).toEqual('http://localhost:6969/anchor-modifiers');
53+
});
54+
55+
test('Clicking html link with modifier runs the original event', async () => {
56+
await page.keyboard.down('Shift');
57+
await page.click('[href="/anchor-modifiers?source=html"]');
58+
await page.keyboard.up('Shift');
59+
await page.waitForSelector('[data-clicked-html]');
60+
const element = await page.$('[data-clicked-html]');
61+
expect(element).toBeTruthy();
62+
});
63+
64+
test('Clicking jsx link with modifier runs the original event', async () => {
65+
await page.keyboard.down('Shift');
66+
await page.click('[href="/anchor-modifiers?source=jsx"]');
67+
await page.keyboard.up('Shift');
68+
await page.waitForSelector('[data-clicked-jsx]');
69+
const element = await page.$('[data-clicked-jsx]');
70+
expect(element).toBeTruthy();
71+
});
72+
3973
});

0 commit comments

Comments
 (0)