Skip to content

Commit

Permalink
Add lightweight h utility to construct UIs
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmyersdev committed Apr 7, 2024
1 parent 192d50d commit 296644f
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 34 deletions.
74 changes: 40 additions & 34 deletions src/editor/extensions/search.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,59 @@
import { SearchQuery, findNext, findPrevious, getSearchQuery, search as searchExtension, searchKeymap, setSearchQuery } from '@codemirror/search'
import { keymap, runScopeHandlers } from '@codemirror/view'
import { createRoot, createSignal } from 'solid-js'
import { createElement, h } from '/src/helpers/h'

export const search = () => {
return [
searchExtension({
top: true,
createPanel: (view) => {
return createRoot((dispose) => {
const [query, setQuery] = createSignal(getSearchQuery(view.state))
let el: HTMLInputElement | undefined
let query = getSearchQuery(view.state)

const handleKeyDown = (event: KeyboardEvent) => {
if (runScopeHandlers(view, event, 'search-panel')) return event.preventDefault()
const handleKeyDown = (event: KeyboardEvent) => {
if (runScopeHandlers(view, event, 'search-panel')) return event.preventDefault()

if (event.code === 'Enter') {
event.preventDefault()
if (event.code === 'Enter') {
event.preventDefault()

if (event.shiftKey) {
findPrevious(view)
} else {
findNext(view)
}
if (event.shiftKey) {
findPrevious(view)
} else {
findNext(view)
}
}
}

const updateSearch = (event: InputEvent) => {
// @ts-expect-error "value" is not a recognized property of EventTarget.
const { value } = event.target
const updateSearch = (event: Event) => {
// @ts-expect-error "value" is not a recognized property of EventTarget.
const { value } = event.target

setQuery(new SearchQuery({ search: value }))
view.dispatch({ effects: setSearchQuery.of(query()) })
}
query = new SearchQuery({ search: value })
view.dispatch({ effects: setSearchQuery.of(query) })
}

return {
destroy: () => {
dispose()
},
dom: (
<div class='ink-mde-search-panel' onKeyDown={handleKeyDown}>
<input attr:main-field='true' class='ink-mde-search-input' onInput={updateSearch} onKeyDown={handleKeyDown} ref={el} type="text" value={query().search} />
</div>
) as HTMLElement,
mount: () => {
el?.focus()
},
top: true,
}
})
const dom = createElement(
h('div', { class: 'ink-mde-search-panel' }, [
h('input', {
class: 'ink-mde-search-input',
'main-field': 'true',
type: 'text',
value: query.search,
}),
]),
)

return {
dom,
mount: () => {
const input = dom.querySelector('input')!

input.addEventListener('input', updateSearch)
input.addEventListener('keydown', handleKeyDown)

input.focus()
},
top: true,
}
},
}),
keymap.of(searchKeymap),
Expand Down
85 changes: 85 additions & 0 deletions src/helpers/h.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
export type H<Tag extends HTag = HTag> = HTMLElementTagNameMap[Tag]
export type HProps<_Tag extends HTag = HTag> = Record<string, any>
export type HTag = keyof HTMLElementTagNameMap

export type HFn = {
<Tag extends HTag>(tag: Tag): HNode<Tag>,
<Tag extends HTag>(tag: Tag, options: HProps): HNode<Tag>,
<Tag extends HTag>(tag: Tag, children: HNode[]): HNode<Tag>,
<Tag extends HTag>(tag: Tag, options: HProps, children: HNode[]): HNode<Tag>,
}

export type HNamedFn = {
<Tag extends HTag>(): HNode<Tag>,
<Tag extends HTag>(options: HProps): HNode<Tag>,
<Tag extends HTag>(children: HNode[]): HNode<Tag>,
<Tag extends HTag>(options: HProps, children: HNode[]): HNode<Tag>,
}

export type HNode<Tag extends HTag = HTag> = {
tag: Tag,
options: HProps<Tag>,
children: HNode[],
}

const isVoidTag = (tag: string): boolean => {
return voidTags.includes(tag)
}

const voidTags = Object.freeze([
'area',
'base',
'br',
'col',
'embed',
'hr',
'img',
'input',
'link',
'meta',
'param',
'source',
'track',
'wbr',
])

export const createElement = <Tag extends HTag>(hNode: HNode<Tag>): H<Tag> => {
const element = document.createElement(hNode.tag)

for (const [key, value] of Object.entries(hNode.options)) {
element.setAttribute(key, value)
}

if (!isVoidTag(hNode.tag)) {
for (const child of hNode.children) {
element.append(createElement(child))
}
}