feat(runtime): lower generated runtime ids
req: wire/001 req: ts/001
This commit is contained in:
Vendored
+66
@@ -0,0 +1,66 @@
|
||||
// req: ts/001
|
||||
export type ResourceKind = "slot" | "atom" | "handle" | "form";
|
||||
|
||||
export interface ResourceId {
|
||||
kind: ResourceKind;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export type ScopeKey =
|
||||
| { kind: "key"; value: string }
|
||||
| { kind: "field"; value: string };
|
||||
|
||||
export interface ResourceRef {
|
||||
resource: ResourceId;
|
||||
scope: ScopeKey | null;
|
||||
}
|
||||
|
||||
export type Payload =
|
||||
| { kind: "text"; value: string }
|
||||
| { kind: "html"; value: string };
|
||||
|
||||
export type ScrollBehavior =
|
||||
| "preserve"
|
||||
| "top"
|
||||
| { kind: "element"; target: ResourceRef };
|
||||
|
||||
export type Effect =
|
||||
| { kind: "put"; target: ResourceRef; payload: Payload }
|
||||
| { kind: "insert"; target: ResourceRef; key: string; payload: Payload }
|
||||
| { kind: "prepend"; target: ResourceRef; key: string; payload: Payload }
|
||||
| { kind: "remove"; target: ResourceRef; key: string | null }
|
||||
| { kind: "move"; target: ResourceRef; key: string; before: string | null }
|
||||
| { kind: "focus"; target: ResourceRef }
|
||||
| { kind: "navigate"; url: string; mode: "push" | "replace" | "redirect"; scroll: ScrollBehavior; title: string | null }
|
||||
| { kind: "emit"; name: string; payload: string };
|
||||
|
||||
export interface EffectBatch {
|
||||
abiVersion: number;
|
||||
fingerprint: bigint;
|
||||
ops: Effect[];
|
||||
}
|
||||
|
||||
export interface AtomSnapshot {
|
||||
id: number;
|
||||
bytes: Uint8Array;
|
||||
}
|
||||
|
||||
export interface SlhxRuntime {
|
||||
readonly runtimeAbiVersion: number;
|
||||
roots(): Element[];
|
||||
rootOf(node: Element | null): Element | null;
|
||||
applyHtml(html: string, root?: ParentNode | null, title?: string | null): boolean;
|
||||
applyBatch(buffer: ArrayBuffer, root?: ParentNode | null): void;
|
||||
decodeBatch(buffer: ArrayBuffer): EffectBatch;
|
||||
atomValue(root: Element | ParentNode | null | undefined, id: number): Uint8Array | undefined;
|
||||
decodeAtomState(encoded: string): AtomSnapshot[];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
slhx: SlhxRuntime;
|
||||
}
|
||||
}
|
||||
|
||||
declare const slhx: SlhxRuntime;
|
||||
export default slhx;
|
||||
+16
-1
@@ -341,7 +341,9 @@
|
||||
const scope = root || document;
|
||||
const doc = new DOMParser().parseFromString(html, "text/html");
|
||||
const template = doc.querySelector("template[data-slhx]");
|
||||
if (!replaceSlot(scope, doc, "content", template ? template.innerHTML : html)) {
|
||||
const lowered = replaceLoweredSlots(scope, doc);
|
||||
const named = replaceSlot(scope, doc, "content", lowered ? undefined : (template ? template.innerHTML : html));
|
||||
if (!lowered && !named) {
|
||||
emit(scope, "slhx:missing-content-slot", null);
|
||||
return false;
|
||||
}
|
||||
@@ -351,6 +353,19 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
function replaceLoweredSlots(scope, doc) {
|
||||
let changed = false;
|
||||
doc.querySelectorAll("[data-sid], [data-slot-id]").forEach((source) => {
|
||||
const id = source.getAttribute("data-sid") || source.getAttribute("data-slot-id");
|
||||
const target = scope.querySelector(`[data-sid="${cssEscape(id)}"], [data-slot-id="${cssEscape(id)}"]`);
|
||||
if (target) {
|
||||
target.innerHTML = source.innerHTML;
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
return changed;
|
||||
}
|
||||
|
||||
function replaceSlot(scope, doc, name, fallback) {
|
||||
const target = scope.querySelector(`[data-slhx-slot="${name}"], [data-slot="${name}"]`);
|
||||
if (!target) return false;
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub const RUNTIME_ABI_VERSION: u32 = 1;
|
||||
pub const RUNTIME_JS: &str = include_str!("../runtime/slhx.js");
|
||||
pub const RUNTIME_D_TS: &str = include_str!("../runtime/slhx.d.ts");
|
||||
|
||||
@@ -40,6 +40,16 @@ fn runtime_exposes_page_swap_hooks() {
|
||||
assert!(source.contains("location.href = href"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_page_swaps_lowered_slot_ids() {
|
||||
// req: wire/001
|
||||
let source = slhx_js::RUNTIME_JS;
|
||||
|
||||
assert!(source.contains("function replaceLoweredSlots(scope, doc)"));
|
||||
assert!(source.contains("doc.querySelectorAll(\"[data-sid], [data-slot-id]\")"));
|
||||
assert!(source.contains("target.innerHTML = source.innerHTML"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_keeps_form_field_targets_separate_from_error_targets() {
|
||||
let source = slhx_js::RUNTIME_JS;
|
||||
@@ -58,3 +68,15 @@ fn runtime_preflights_batches_before_applying_ops() {
|
||||
assert!(source.contains("function canApplyOp(scope, op)"));
|
||||
assert!(source.contains("for (const op of batch.ops) applyOp(scope, op)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_ships_typescript_definitions() {
|
||||
let source = slhx_js::RUNTIME_D_TS;
|
||||
|
||||
assert!(source.contains("req: ts/001"));
|
||||
assert!(source.contains("export interface EffectBatch"));
|
||||
assert!(source.contains("fingerprint: bigint"));
|
||||
assert!(source.contains("export type Effect ="));
|
||||
assert!(source.contains("decodeBatch(buffer: ArrayBuffer): EffectBatch"));
|
||||
assert!(source.contains("interface Window"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user