Skip to content

Commit f002cb6

Browse files
author
arnoson
committed
feat: add deep refs
1 parent bda73df commit f002cb6

File tree

4 files changed

+54
-14
lines changed

4 files changed

+54
-14
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,21 @@ registerComponent('parent', options, ({ refs }) => {
284284
</div>
285285
```
286286

287+
### Deep/Nested Refs
288+
289+
Sometimes you may want to associate a ref that is nested inside another component with the parent component instead. You can do so by providing a path with the parent components name: `parent/ref`.
290+
291+
```html
292+
<div data-simple-component="parent">
293+
<div data-simple-component="child">
294+
<button data-ref="parent/button">I'm a ref of `parent`</button>
295+
<button data-ref="button">I'm a ref of `child`</button>
296+
</div>
297+
</div>
298+
```
299+
300+
Referencing the parent component by it's name as above is the most common scenario, but in some rare cases you may want to target a specific html element. You can do this by using a CSS selector surrounded by parentheses: `(#my-component)/ref` or `(div.some-class)/ref`.
301+
287302
### Events
288303

289304
Components try to stay as close to native APIs as possible. Therefore events are just [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent), but they can be fully typed:

index.html

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@
77
<title>Document</title>
88
</head>
99
<body>
10-
<div data-simple-component="counter">
10+
<div data-simple-component="counter" id="counter">
1111
<span data-ref="countDisplay">0</span>
12-
<button data-ref="count">+</button>
12+
<div data-simple-component="fu">
13+
<div data-simple-component="bar">
14+
<button data-ref="counter/count">+</button>
15+
</div>
16+
</div>
1317
</div>
1418

1519
<div data-simple-component="todos" data-todos='["one", "two"]'></div>

src/refs.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,25 @@ import { walkComponent } from './walkComponent'
33

44
export const getRefs = (el: HTMLElement) => {
55
const refsAll: SimpleRefsAll = {}
6+
const addRef = (name: string, el: HTMLElement) => {
7+
refsAll[name] ??= []
8+
refsAll[name].push(el)
9+
}
10+
611
walkComponent(el, el => {
712
const { ref } = el.dataset
8-
if (ref) {
9-
refsAll[ref] ??= []
10-
refsAll[ref].push(el)
11-
}
13+
if (ref) addRef(ref, el)
14+
})
15+
16+
const deepRefs = el.querySelectorAll<HTMLElement>('[data-ref*="/"]')
17+
deepRefs.forEach(refEl => {
18+
const [parent, name] = refEl.dataset.ref!.split('/')
19+
20+
const selector = parent.match(/^\((.*)\)$/)?.[1]
21+
const parentSelector = selector ?? `[data-simple-component='${parent}']`
22+
23+
const parentEl = el.closest(parentSelector)
24+
if (parentEl === el) addRef(name, refEl)
1225
})
1326

1427
const refs: SimpleRefs = Object.fromEntries(

tests/index.test.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,22 @@ it('provides a record of single refs', () => {
7373
registerComponent('test', component)
7474

7575
document.body.innerHTML = `
76-
<div data-simple-component="test">
77-
<div data-ref="myRef"></div>
76+
<div data-simple-component="test" id="my-test">
77+
<div id="ref1" data-ref="myRef"></div>
7878
<div data-simple-component="another-component">
79-
<!-- This shouldn't show up in the refs because it belongs to another
80-
component -->
81-
<div data-ref="nestedRef"></div>
79+
<div data-ref="otherComponentsRef"></div>
80+
<div id="ref2" data-ref="test/deepRef"></div>
81+
<div id="ref3" data-ref="(#my-test)/selectorDeepRef"></div>
8282
</div>
8383
</div>
8484
`
8585
mountComponents(document.body)
86-
const myRef = document.querySelector(`[data-ref='myRef']`)
87-
expect(component).toBeCalledWith(expect.objectContaining({ refs: { myRef } }))
86+
const myRef = document.querySelector('#ref1')
87+
const deepRef = document.querySelector('#ref2')
88+
const selectorDeepRef = document.querySelector('#ref3')
89+
expect(component).toBeCalledWith(
90+
expect.objectContaining({ refs: { myRef, deepRef, selectorDeepRef } })
91+
)
8892
})
8993

9094
it('provides a record of groups of refs with the same name', () => {
@@ -96,13 +100,17 @@ it('provides a record of groups of refs with the same name', () => {
96100
<div id="ref1" data-ref="myRef"></div>
97101
<div id="ref2" data-ref="myRef"></div>
98102
<div id="ref3" data-ref="myRef"></div>
103+
<div data-simple-component="another-component">
104+
<div id="ref4" data-ref="test/myRef"></div>
105+
</div>
99106
</div>
100107
`
101108
mountComponents(document.body)
102109
const myRef = [
103110
document.querySelector('#ref1'),
104111
document.querySelector('#ref2'),
105-
document.querySelector('#ref3')
112+
document.querySelector('#ref3'),
113+
document.querySelector('#ref4')
106114
]
107115
expect(component).toBeCalledWith(
108116
expect.objectContaining({ refsAll: { myRef } })

0 commit comments

Comments
 (0)