Compare commits
9 Commits
4b5eccad24
...
ebe7700cee
Author | SHA1 | Date |
---|---|---|
Tony Garnock-Jones | ebe7700cee | |
Tony Garnock-Jones | f4f2ad0783 | |
Tony Garnock-Jones | c1cdf3660f | |
Tony Garnock-Jones | 9d8e7f5ccd | |
Tony Garnock-Jones | bf9d10813e | |
Tony Garnock-Jones | 80250fdac9 | |
Tony Garnock-Jones | 305c0c26ee | |
Tony Garnock-Jones | 782f24687f | |
Tony Garnock-Jones | 6d904d276e |
|
@ -296,14 +296,19 @@ ${joinItems(sa.captureBinders.map(binderTypeGuard(t)), '\n')}
|
|||
})`;
|
||||
|
||||
if (s.isDynamic) {
|
||||
return wrap(t`__SYNDICATE__.Turn.active.assertDataflow(() => ({
|
||||
target: currentSyndicateTarget,
|
||||
assertion: ${assertion},
|
||||
}));`);
|
||||
if (s.test === void 0) {
|
||||
return wrap(t`__SYNDICATE__.Turn.active.assertDataflow(() => ({ target: currentSyndicateTarget, assertion: ${assertion} }));`);
|
||||
} else {
|
||||
return wrap(
|
||||
t`__SYNDICATE__.Turn.active.replace(currentSyndicateTarget, void 0, ${assertion});`
|
||||
);
|
||||
return wrap(t`__SYNDICATE__.Turn.active.assertDataflow(() => (${walk(s.test)})
|
||||
? ({ target: currentSyndicateTarget, assertion: ${assertion} })
|
||||
: ({ target: void 0, assertion: void 0 }));`);
|
||||
}
|
||||
} else {
|
||||
if (s.test === void 0) {
|
||||
return wrap(t`__SYNDICATE__.Turn.active.replace(currentSyndicateTarget, void 0, ${assertion});`);
|
||||
} else {
|
||||
return wrap(t`__SYNDICATE__.Turn.active.replace(currentSyndicateTarget, void 0, (${walk(s.test)}) ? ${assertion} : void 0);`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -75,6 +75,7 @@ export interface PseudoEventEndpointStatement extends GenericEventEndpointStatem
|
|||
export interface AssertionEventEndpointStatement extends GenericEventEndpointStatement {
|
||||
triggerType: 'asserted' | 'retracted' | 'message';
|
||||
pattern: ValuePattern;
|
||||
test?: Expr,
|
||||
}
|
||||
|
||||
export type EventHandlerEndpointStatement =
|
||||
|
@ -325,7 +326,11 @@ export class SyndicateParser {
|
|||
atomString('message'))),
|
||||
option(map(kw('snapshot'), _ => o.isDynamic = false)),
|
||||
bind(o as AssertionEventEndpointStatement, 'pattern',
|
||||
this.valuePattern(1, atom('=>'))),
|
||||
this.valuePattern(1, atom('=>'),
|
||||
seq(atom('when'), group('(', discard)))),
|
||||
option(seq(atom('when'), group(
|
||||
'(', bind(o as AssertionEventEndpointStatement, 'test',
|
||||
this.expr())))),
|
||||
this.mandatoryIfNotTerminal(
|
||||
o, seq(atom('=>'), this.statement(o.body))))));
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@ const currentSyndicateTarget = Syndicate.Dataspace.global;
|
|||
|
||||
(() => {
|
||||
async function translateScripts() {
|
||||
if (SchemaReady) await SchemaReady;
|
||||
|
||||
const syndicateScripts =
|
||||
Array.from(document.getElementsByTagName('script'))
|
||||
|
|
|
@ -187,7 +187,7 @@ __SYNDICATE__.Turn.active.facet(() => {
|
|||
}
|
||||
}
|
||||
}),
|
||||
}),
|
||||
})
|
||||
}));
|
||||
});`));
|
||||
|
||||
|
@ -206,8 +206,57 @@ __SYNDICATE__.Turn.active.facet(() => {
|
|||
}
|
||||
}
|
||||
}),
|
||||
}),
|
||||
})
|
||||
}));
|
||||
});`));
|
||||
|
||||
});
|
||||
|
||||
describe('on', () => {
|
||||
it('message with guard', () => expectCodeEqual(`
|
||||
on message S.Focus(entity) when (isLast.value) => {
|
||||
text.node.focus();
|
||||
}`, `__SYNDICATE__.Turn.active.assertDataflow(() => (isLast.value) ?
|
||||
({
|
||||
target: currentSyndicateTarget,
|
||||
assertion: __SYNDICATE__.Observe({
|
||||
pattern: __SYNDICATE__.QuasiValue.finish((__SYNDICATE__.QuasiValue.ctor(S.Focus, (__SYNDICATE__.QuasiValue.lit(__SYNDICATE__.fromJS(entity)))))),
|
||||
observer: __SYNDICATE__.Turn.ref({
|
||||
message: (__vs) => {
|
||||
if (Array.isArray(__vs)) {
|
||||
|
||||
|
||||
text.node.focus();
|
||||
|
||||
}
|
||||
}
|
||||
}),
|
||||
})
|
||||
}) :
|
||||
({
|
||||
target: void 0,
|
||||
assertion: void 0
|
||||
}));`));
|
||||
|
||||
it('asserted with guard', () => expectCodeEqual(`on asserted P when (someTest) => x;`, `
|
||||
__SYNDICATE__.Turn.active.assertDataflow(() => (someTest) ?
|
||||
({
|
||||
target: currentSyndicateTarget,
|
||||
assertion: __SYNDICATE__.Observe({
|
||||
pattern: __SYNDICATE__.QuasiValue.finish((__SYNDICATE__.QuasiValue.lit(__SYNDICATE__.fromJS(P)))),
|
||||
observer: __SYNDICATE__.Turn.ref({
|
||||
assert: (__vs, __handle) => {
|
||||
if (Array.isArray(__vs)) {
|
||||
|
||||
x;
|
||||
}
|
||||
}
|
||||
}),
|
||||
})
|
||||
}) :
|
||||
({
|
||||
target: void 0,
|
||||
assertion: void 0
|
||||
}));`));
|
||||
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/// SPDX-FileCopyrightText: Copyright © 2016-2024 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
import { IdentityMap, KeyedDictionary, stringify } from '@preserves/core';
|
||||
import { IdentityMap, KeyedDictionary, stringify, strip } from '@preserves/core';
|
||||
import { Index, IndexObserver } from './skeleton.js';
|
||||
import { Actor, AnyValue, Assertion, DetailedAction, Entity, Facet, Handle, LocalAction, Ref, Turn } from './actor.js';
|
||||
import { Observe, toObserve } from '../gen/dataspace.js';
|
||||
|
@ -79,7 +79,7 @@ export class Dataspace implements Partial<Entity> {
|
|||
readonly options: DataspaceOptions;
|
||||
readonly index = new Index();
|
||||
readonly handleMap = new IdentityMap<Handle, Assertion>();
|
||||
readonly observerMap = new IdentityMap<Ref, DataspaceObserver>();
|
||||
readonly observerMap = new KeyedDictionary<Ref, Observe, DataspaceObserver>();
|
||||
readonly data = this;
|
||||
|
||||
constructor(options?: DataspaceOptions) {
|
||||
|
@ -90,12 +90,11 @@ export class Dataspace implements Partial<Entity> {
|
|||
const is_new = this.index.addAssertion(v, Turn.active);
|
||||
this.options.tracer?.('+', v, this, is_new);
|
||||
if (is_new) {
|
||||
const o = toObserve(v);
|
||||
const o = toObserve(strip(v));
|
||||
if (o !== void 0) {
|
||||
const target = o.observer;
|
||||
const observer = new DataspaceObserver(target);
|
||||
this.observerMap.set(target, observer);
|
||||
this.index.addObserver(o.pattern, observer, Turn.active);
|
||||
const io = new DataspaceObserver(o.observer);
|
||||
this.observerMap.set(o, io);
|
||||
this.index.addObserver(o.pattern, io, Turn.active);
|
||||
}
|
||||
if (this.options.dumpIndex ?? false) this.index.dump();
|
||||
}
|
||||
|
@ -109,12 +108,12 @@ export class Dataspace implements Partial<Entity> {
|
|||
const is_last = this.index.removeAssertion(v, Turn.active);
|
||||
this.options.tracer?.('-', v, this, is_last);
|
||||
if (is_last) {
|
||||
const o = toObserve(v);
|
||||
const o = toObserve(strip(v));
|
||||
if (o !== void 0) {
|
||||
const io = this.observerMap.get(o.observer);
|
||||
const io = this.observerMap.get(o);
|
||||
if (io !== void 0) {
|
||||
this.index.removeObserver(o.pattern, io, Turn.active);
|
||||
this.observerMap.delete(o.observer);
|
||||
this.observerMap.delete(o);
|
||||
}
|
||||
}
|
||||
if (this.options.dumpIndex ?? false) this.index.dump();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/// SPDX-FileCopyrightText: Copyright © 2016-2024 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
import { canonicalString, KeyedDictionary, is, Record, RecordConstructorInfo, Value, _iterMap, DictionaryMap, Dictionary, EncodableDictionary } from '@preserves/core';
|
||||
import { canonicalString, KeyedDictionary, is, Record, RecordConstructorInfo, Value, _iterMap, DictionaryMap, Dictionary, EncodableDictionary, unannotate } from '@preserves/core';
|
||||
import { AnyValue, Ref } from './actor.js';
|
||||
import * as P from '../gen/dataspacePatterns.js';
|
||||
|
||||
|
@ -10,6 +10,7 @@ export type Path = Array<AnyValue>;
|
|||
export type Shape = string;
|
||||
|
||||
export function classOfValue(v: any): Shape | null {
|
||||
v = unannotate(v);
|
||||
if (Record.isRecord(v)) {
|
||||
return constructorInfoSignature(Record.constructorInfo(v));
|
||||
} else if (Array.isArray(v)) {
|
||||
|
@ -38,6 +39,7 @@ export function constructorInfoSignature(ci: RecordConstructorInfo<Value>): stri
|
|||
}
|
||||
|
||||
export function step(v: AnyValue, index: AnyValue): AnyValue | undefined {
|
||||
v = unannotate(v);
|
||||
const vMap = Dictionary.asMap<Ref>(v);
|
||||
if (vMap) {
|
||||
return vMap.get(index);
|
||||
|
|
|
@ -70,7 +70,22 @@ function nodeInserter(n: number): PlaceholderAction {
|
|||
switch (typeof f) {
|
||||
case 'string': newNode = document.createTextNode(f); break;
|
||||
case 'number': newNode = document.createTextNode('' + f); break;
|
||||
default: newNode = f; break;
|
||||
case 'object':
|
||||
if (f !== null && 'nodeType' in f) {
|
||||
newNode = f;
|
||||
break;
|
||||
}
|
||||
/* fall through */
|
||||
default: {
|
||||
let info;
|
||||
try {
|
||||
info = '' + f;
|
||||
} catch (_e) {
|
||||
info = (f as any).toString();
|
||||
}
|
||||
newNode = document.createTextNode(`<ERROR: invalid HtmlFragment: ${info}>`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
node.parentNode?.insertBefore(newNode, node);
|
||||
}
|
||||
|
|
|
@ -20,23 +20,28 @@ type Wrapped = {
|
|||
export type NodeGenerator = (t: HtmlTemplater) => ReturnType<HtmlTemplater>;
|
||||
|
||||
export class Widget implements EventTarget {
|
||||
readonly nodeGenerator: NodeGenerator;
|
||||
readonly facet: Facet;
|
||||
private _node: ChildNode | null = null;
|
||||
parentField: Dataflow.Field<ParentNode | null>;
|
||||
callbacks = new Map<string, Map<EventListenerOrEventListenerObject, Wrapped>>();
|
||||
|
||||
get node(): ChildNode {
|
||||
return this._node!;
|
||||
}
|
||||
|
||||
constructor (node: ChildNode);
|
||||
constructor (nodeGenerator: NodeGenerator);
|
||||
constructor (template: string | HTMLTemplateElement, data: object);
|
||||
constructor (arg0: NodeGenerator | string | HTMLTemplateElement, data?: object) {
|
||||
constructor (arg0: ChildNode | NodeGenerator | string | HTMLTemplateElement, data?: object) {
|
||||
let nodeGenerator: NodeGenerator;
|
||||
|
||||
if (data === void 0) {
|
||||
this.nodeGenerator = arg0 as NodeGenerator;
|
||||
if (typeof arg0 === 'function') {
|
||||
nodeGenerator = arg0 as NodeGenerator;
|
||||
} else {
|
||||
this.nodeGenerator = templateGenerator(arg0 as (string | HTMLTemplateElement), data);
|
||||
nodeGenerator = () => [arg0 as ChildNode];
|
||||
}
|
||||
} else {
|
||||
nodeGenerator = templateGenerator(arg0 as (string | HTMLTemplateElement), data);
|
||||
}
|
||||
|
||||
this.facet = Turn.activeFacet;
|
||||
|
@ -49,7 +54,7 @@ export class Widget implements EventTarget {
|
|||
|
||||
const thisTemplate = template();
|
||||
dataflow {
|
||||
const nodes = this.nodeGenerator(thisTemplate);
|
||||
const nodes = nodeGenerator(thisTemplate);
|
||||
if (nodes.length !== 1) {
|
||||
throw new Error(`@syndicate-lang/html2: Expected exactly one node from template`);
|
||||
}
|
||||
|
@ -59,24 +64,18 @@ export class Widget implements EventTarget {
|
|||
throw new Error(`@syndicate-lang/html2: Node generator is not stable`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
field parentField: ParentNode | null = this._node?.parentNode ?? null;
|
||||
this.parentField = parentField;
|
||||
|
||||
dataflow {
|
||||
const p = this.parentField.value;
|
||||
if (this.node.parentNode !== p) {
|
||||
if (p === null) {
|
||||
this.node.remove();
|
||||
get _nodeAsParent(): ParentNode | null {
|
||||
if (this._node && 'querySelector' in this._node) {
|
||||
return this._node as unknown as ParentNode;
|
||||
} else {
|
||||
p.appendChild(this.node);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
get parent(): ParentNode | null {
|
||||
return this.parentField.value;
|
||||
return this.node.parentNode;
|
||||
}
|
||||
|
||||
set parent(p: string | ParentNode | null) {
|
||||
|
@ -87,10 +86,34 @@ export class Widget implements EventTarget {
|
|||
if (typeof p === 'string') {
|
||||
p = wrt.querySelector(p);
|
||||
}
|
||||
this.parentField.value = p;
|
||||
|
||||
if (this.node.parentNode !== p) {
|
||||
if (p === null) {
|
||||
this.node.remove();
|
||||
} else {
|
||||
p.appendChild(this.node);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
querySelector(selector: string): Widget | null;
|
||||
querySelector<T extends Widget>(selector: string, ctor: { new(e: Element): T }): T | null;
|
||||
querySelector<T extends Widget>(selector: string, ctor?: { new(e: Element): T }): Widget | null {
|
||||
const e = this._nodeAsParent?.querySelector(selector);
|
||||
return e ? new (ctor ?? Widget)(e) : null;
|
||||
}
|
||||
|
||||
querySelectorAll(selector: string): Widget[];
|
||||
querySelectorAll<T extends Widget>(selector: string, ctor: { new(e: Element): T }): T[];
|
||||
querySelectorAll<T extends Widget>(selector: string, ctor?: { new(e: Element): T }): Widget[] {
|
||||
const es = this._nodeAsParent?.querySelectorAll(selector);
|
||||
const ws: Widget[] = [];
|
||||
if (es) es.forEach(e => ws.push(new (ctor ?? Widget)(e)));
|
||||
return ws;
|
||||
}
|
||||
|
||||
on(type: string, callback: EventListenerOrEventListenerObject): this {
|
||||
this.addEventListener(type, callback);
|
||||
return this;
|
||||
|
@ -173,20 +196,21 @@ export class ValueWidget extends Widget {
|
|||
this._valueAsNumber = valueAsNumber;
|
||||
|
||||
if ('value' in this.node) {
|
||||
const readValues = (n: any) => {
|
||||
this.suppressCycleWarning();
|
||||
this._value.value = n?.value ?? '';
|
||||
this._valueAsNumber.value = n?.valueAsNumber ?? NaN;
|
||||
};
|
||||
|
||||
this.on(triggerEvent, e => readValues(e.target));
|
||||
readValues(this.node);
|
||||
this.on(triggerEvent, () => this.readValues());
|
||||
this.readValues();
|
||||
|
||||
dataflow { this.valueAsNumber = this._valueAsNumber.value; }
|
||||
dataflow { this.value = this._value.value; }
|
||||
}
|
||||
}
|
||||
|
||||
readValues() {
|
||||
const n = this.node as any;
|
||||
this.suppressCycleWarning();
|
||||
this._value.value = n.value ?? '';
|
||||
this._valueAsNumber.value = n.valueAsNumber ?? NaN;
|
||||
}
|
||||
|
||||
get value(): string {
|
||||
return this._value.value;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue