Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/uibench/main.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// @ts-check
/// <reference types='./uibench.d.ts' />

import { html } from 'dhtml'
import { createRoot, keyed } from 'dhtml/client'
import { html, keyed } from 'dhtml'
import { createRoot } from 'dhtml/client'

/** @param {string} text */
function tableCell(text) {
Expand Down
2 changes: 1 addition & 1 deletion src/client.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { invalidate, keyed, onMount, onUnmount } from './client/controller.ts'
export { invalidate, onMount, onUnmount } from './client/controller.ts'
export { attr_directive as attr, on_directive as on, type Directive } from './client/parts.ts'
export { createRoot, hydrate, type Root } from './client/root.ts'
15 changes: 1 addition & 14 deletions src/client/controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { Displayable, Renderable } from '../index.ts'
import { assert, is_renderable } from '../shared.ts'
import { assert, is_renderable, type Key } from '../shared.ts'
import { type Cleanup } from './util.ts'

export type Key = string | number | bigint | boolean | symbol | object | null

export interface Controller {
_mount_callbacks: (() => Cleanup)[]
_unmount_callbacks: Cleanup[]
Expand Down Expand Up @@ -47,14 +45,3 @@ export function onMount(renderable: Renderable, callback: () => Cleanup): void {
export function onUnmount(renderable: Renderable, callback: () => void): void {
onMount(renderable, () => callback)
}

export function keyed<T extends Displayable & object>(displayable: T, key: Key): T {
assert(!keys.has(displayable), 'renderable already has a key')
keys.set(displayable, key)
return displayable
}

export function get_key(displayable: unknown): unknown {
// the cast is fine because getting any non-object will return null
return keys.get(displayable as object) ?? displayable
}
6 changes: 4 additions & 2 deletions src/client/parts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import {
assert,
is_html,
is_iterable,
is_keyed,
is_renderable,
single_part_template,
type Displayable,
type Key,
type Renderable,
} from '../shared.ts'
import {
Expand All @@ -15,7 +17,7 @@ import {
PART_PROPERTY,
type CompiledTemplate,
} from './compiler.ts'
import { controllers, get_controller, get_key, type Key } from './controller.ts'
import { controllers, get_controller } from './controller.ts'
import { create_span_after, delete_contents, extract_contents, insert_node, type Span } from './span.ts'
import type { Cleanup } from './util.ts'

Expand Down Expand Up @@ -117,7 +119,7 @@ export function create_child_part(
let i = 0
let end = span._start
for (const item of value) {
const key = get_key(item) as Key
const key = is_keyed(item) ? item._key : (item as Key)
if (entries.length <= i) {
const span = create_span_after(end)
entries[i] = { _span: span, _part: create_child_part(span), _key: key }
Expand Down
5 changes: 3 additions & 2 deletions src/client/root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import {
assert,
is_html,
is_iterable,
is_keyed,
is_renderable,
single_part_template,
type Displayable,
type Key,
type Renderable,
} from '../shared.ts'
import {
Expand All @@ -16,7 +18,6 @@ import {
PART_PROPERTY,
type CompiledTemplate,
} from './compiler.ts'
import { get_key, type Key } from './controller.ts'
import {
create_attribute_part,
create_child_part,
Expand Down Expand Up @@ -95,7 +96,7 @@ function hydrate_child_part(span: Span, value: unknown) {
let end = span._start

for (const item of value) {
const key = get_key(item) as Key
const key = is_keyed(item) ? item._key : (item as Key)

const start = end.nextSibling
assert(start && is_comment(start) && start.data === '?[')
Expand Down
4 changes: 2 additions & 2 deletions src/client/tests/hydration.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { html, type Displayable, type Renderable } from 'dhtml'
import { attr, hydrate, invalidate, keyed, onMount, type Directive, type Root } from 'dhtml/client'
import { html, keyed, type Displayable, type Renderable } from 'dhtml'
import { attr, hydrate, invalidate, onMount, type Directive, type Root } from 'dhtml/client'
import { renderToString } from 'dhtml/server'
import { assert, assert_deep_eq, assert_eq, test } from '../../../scripts/test/test.ts'

Expand Down
4 changes: 2 additions & 2 deletions src/client/tests/lists.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { html, type Displayable } from 'dhtml'
import { invalidate, keyed } from 'dhtml/client'
import { html, keyed, type Displayable } from 'dhtml'
import { invalidate } from 'dhtml/client'
import { assert, assert_eq, test } from '../../../scripts/test/test.ts'
import { setup } from './setup.ts'

Expand Down
18 changes: 2 additions & 16 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,6 @@
import { html_tag, is_html } from './shared.ts'
export { html, keyed, type Displayable, type HTML, type Renderable } from './shared.ts'

export interface HTML {
[html_tag]: true
/* @internal */ _statics: TemplateStringsArray
/* @internal */ _dynamics: unknown[]
}

export function html(statics: TemplateStringsArray, ...dynamics: unknown[]): HTML {
return {
[html_tag]: true,
_dynamics: dynamics,
_statics: statics,
}
}
import { is_html } from './shared.ts'

if (__DEV__) {
type JsonML = string | readonly [tag: string, attrs?: Record<string, any>, ...children: JsonML[]]
Expand All @@ -38,5 +26,3 @@ if (__DEV__) {
},
})
}

export type { Displayable, Renderable } from './shared.ts'
38 changes: 35 additions & 3 deletions src/shared.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { html, type HTML } from './index.ts'
export * as lexer from './shared/lexer.ts'

/** @internal */
Expand All @@ -10,7 +9,7 @@ export interface ToString {
toString(): string
}

export type Displayable = null | undefined | ToString | Node | Renderable | Iterable<Displayable> | HTML
export type Displayable = null | undefined | ToString | Node | Renderable | Iterable<Displayable> | HTML | Keyed
export interface Renderable {
render(): Displayable
}
Expand All @@ -32,11 +31,44 @@ export function assert(value: unknown, message?: string): asserts value {
}
}

export const html_tag: unique symbol = Symbol()
export interface HTML {
[html_tag]: true
/* @internal */ _statics: TemplateStringsArray
/* @internal */ _dynamics: unknown[]
}

const html_tag: unique symbol = Symbol()
export function html(statics: TemplateStringsArray, ...dynamics: unknown[]): HTML {
return {
[html_tag]: true,
_dynamics: dynamics,
_statics: statics,
}
}

export function is_html(value: unknown): value is HTML {
return typeof value === 'object' && value !== null && html_tag in value
}

export function single_part_template(part: Displayable): HTML {
return html`${part}`
}

export type Key = string | number | bigint | boolean | symbol | object | null
export interface Keyed extends Renderable {
[keyed_tag]: true
/** @internal */ _key: Key
}

const keyed_tag: unique symbol = Symbol()
export function keyed<T extends Displayable & object>(displayable: T, key: Key): Keyed {
return {
[keyed_tag]: true,
_key: key,
render: () => displayable,
}
}

export function is_keyed(value: any): value is Keyed {
return typeof value === 'object' && value !== null && keyed_tag in value
}