Skip to content

Commit

Permalink
Updates $.live and $.bind to accept an element input and skip the…
Browse files Browse the repository at this point in the history
… `querySelector` call.

This makes them a little more flexible for situations where the element was already found in the DOM.
  • Loading branch information
dgp1130 committed Aug 26, 2023
1 parent e58a083 commit a8d1bc5
Showing 1 changed file with 47 additions and 11 deletions.
58 changes: 47 additions & 11 deletions src/functional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,10 @@ function proxyProps(host: HTMLElement, elementState: ElementInternalState): {} {
}

type SignalsOf<Props extends {}> = { [Key in keyof Props]-?: Accessor<Props[Key]> };
type QueryOrElement<T extends string | Element> = T extends string
? QueriedElement<T>
: T;

class Component<
Host extends HTMLElement = HTMLElement,
Props extends {} = {},
Expand Down Expand Up @@ -367,14 +371,31 @@ class Component<
}

// TODO: How should `$.live('.foo', HTMLSpanElement)` work?
public live<Selector extends string, Result = QueriedElement<Selector>, Source extends HydrateSource = ElementSource>(
selector: Selector,
type: HydrateConverter<Source, Result, QueriedElement<Selector>>,
public live<
SelectorOrElement extends string | Element,
Result = QueryOrElement<SelectorOrElement>,
Source extends HydrateSource = ElementSource,
>(
selectorOrElement: SelectorOrElement,
type: HydrateConverter<Source, Result, QueryOrElement<SelectorOrElement>>,
source: Source = element as Source,
): Signal<Result> {
const initialValue = this.read(selector, type, source);
// Read the current content from the actual DOM element.
const initialValue = selectorOrElement instanceof Element
? readEl(
selectorOrElement,
type as HydrateConverter<Source, Result, Element>,
source,
)
: this.read(
selectorOrElement,
type as HydrateConverter<Source, Result, Element>,
source,
);

// Create a two-way live binding to the content.
const [ accessor, setter ] = createSignal(initialValue);
this.bind(selector, accessor, source);
this.bind(selectorOrElement, accessor, source);

return [ accessor, setter ];
}
Expand All @@ -387,10 +408,7 @@ class Component<
source: Source = element as Source,
): Result {
const el = queryAsserted(this.host, selector);
const content = getSource(el, source);
const coerce = getCoercer(type);
const value = coerce(content);
return value;
return readEl(el, type, source);
}

// TODO: Consider adding `$.hydrateChildren()`.
Expand All @@ -409,11 +427,13 @@ class Component<

// TODO: Require `T` be a stringifiable primitive.
public bind<T>(
selector: string,
selectorOrElement: string | Element,
signal: Accessor<T> | Promise<Accessor<T>>,
source: HydrateSource = element,
): void {
const el = this.query(selector);
const el = selectorOrElement instanceof Element
? selectorOrElement
: this.query(selectorOrElement);
const setDom = getSetter(source);

const bindValue = (accessor: Accessor<T>): void => {
Expand Down Expand Up @@ -514,6 +534,7 @@ class Component<
this.host.dispatchEvent(event);
}

// TODO: Could this optimize to `$.listen($.host)` with a filter? Maybe if `target` is a selector?
public listen(target: EventTarget, event: string, handler: (evt: Event) => void): void {
this.lifecycle(() => {
target.addEventListener(event, handler);
Expand Down Expand Up @@ -545,6 +566,21 @@ type OneOrMore<T> = [ T, ...T[] ];

type HydrateSetter<T> = (el: Element, value: T) => void;

function readEl<
El extends Element,
Result,
Source extends HydrateSource = ElementSource,
>(
el: El,
type: HydrateConverter<Source, Result, El>,
source: Source = element as Source,
): Result {
const content = getSource(el, source);
const coerce = getCoercer(type);
const value = coerce(content);
return value;
}

function getSetter(source: HydrateSource): HydrateSetter<unknown> {
switch (source.kind) {
case 'element': return (el, content) => {
Expand Down

0 comments on commit a8d1bc5

Please sign in to comment.