Skip to content

Commit

Permalink
feat: add deep refs
Browse files Browse the repository at this point in the history
  • Loading branch information
arnoson committed Jun 29, 2023
1 parent bda73df commit f002cb6
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 14 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,21 @@ registerComponent('parent', options, ({ refs }) => {
</div>
```

### Deep/Nested Refs

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`.

```html
<div data-simple-component="parent">
<div data-simple-component="child">
<button data-ref="parent/button">I'm a ref of `parent`</button>
<button data-ref="button">I'm a ref of `child`</button>
</div>
</div>
```

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`.

### Events

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:
Expand Down
8 changes: 6 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@
<title>Document</title>
</head>
<body>
<div data-simple-component="counter">
<div data-simple-component="counter" id="counter">
<span data-ref="countDisplay">0</span>
<button data-ref="count">+</button>
<div data-simple-component="fu">
<div data-simple-component="bar">
<button data-ref="counter/count">+</button>
</div>
</div>
</div>

<div data-simple-component="todos" data-todos='["one", "two"]'></div>
Expand Down
21 changes: 17 additions & 4 deletions src/refs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,25 @@ import { walkComponent } from './walkComponent'

export const getRefs = (el: HTMLElement) => {
const refsAll: SimpleRefsAll = {}
const addRef = (name: string, el: HTMLElement) => {
refsAll[name] ??= []
refsAll[name].push(el)
}

walkComponent(el, el => {
const { ref } = el.dataset
if (ref) {
refsAll[ref] ??= []
refsAll[ref].push(el)
}
if (ref) addRef(ref, el)
})

const deepRefs = el.querySelectorAll<HTMLElement>('[data-ref*="/"]')
deepRefs.forEach(refEl => {
const [parent, name] = refEl.dataset.ref!.split('/')

const selector = parent.match(/^\((.*)\)$/)?.[1]
const parentSelector = selector ?? `[data-simple-component='${parent}']`

const parentEl = el.closest(parentSelector)
if (parentEl === el) addRef(name, refEl)
})

const refs: SimpleRefs = Object.fromEntries(
Expand Down
24 changes: 16 additions & 8 deletions tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,22 @@ it('provides a record of single refs', () => {
registerComponent('test', component)

document.body.innerHTML = `
<div data-simple-component="test">
<div data-ref="myRef"></div>
<div data-simple-component="test" id="my-test">
<div id="ref1" data-ref="myRef"></div>
<div data-simple-component="another-component">
<!-- This shouldn't show up in the refs because it belongs to another
component -->
<div data-ref="nestedRef"></div>
<div data-ref="otherComponentsRef"></div>
<div id="ref2" data-ref="test/deepRef"></div>
<div id="ref3" data-ref="(#my-test)/selectorDeepRef"></div>
</div>
</div>
`
mountComponents(document.body)
const myRef = document.querySelector(`[data-ref='myRef']`)
expect(component).toBeCalledWith(expect.objectContaining({ refs: { myRef } }))
const myRef = document.querySelector('#ref1')
const deepRef = document.querySelector('#ref2')
const selectorDeepRef = document.querySelector('#ref3')
expect(component).toBeCalledWith(
expect.objectContaining({ refs: { myRef, deepRef, selectorDeepRef } })
)
})

it('provides a record of groups of refs with the same name', () => {
Expand All @@ -96,13 +100,17 @@ it('provides a record of groups of refs with the same name', () => {
<div id="ref1" data-ref="myRef"></div>
<div id="ref2" data-ref="myRef"></div>
<div id="ref3" data-ref="myRef"></div>
<div data-simple-component="another-component">
<div id="ref4" data-ref="test/myRef"></div>
</div>
</div>
`
mountComponents(document.body)
const myRef = [
document.querySelector('#ref1'),
document.querySelector('#ref2'),
document.querySelector('#ref3')
document.querySelector('#ref3'),
document.querySelector('#ref4')
]
expect(component).toBeCalledWith(
expect.objectContaining({ refsAll: { myRef } })
Expand Down

0 comments on commit f002cb6

Please sign in to comment.