@omnicajs/vue-remote
Version:
Proxy renderer for Vue.js based on @remote-ui
705 lines (704 loc) • 20.3 kB
JavaScript
import { h, createCommentVNode, shallowRef, ref, defineComponent, watch, onUnmounted } from "vue";
import { i as isSerializedComponent, k as keysOf, a as isSerializedFragment, b as addMethod, K as KIND_COMPONENT, R as ROOT_ID, c as KIND_ROOT, d as createChannel, e as KIND_TEXT, f as KIND_COMMENT, g as REMOTE_SLOT, h as isDOMTag, j as isFunction } from "./dom-Duh2pvkf.js";
import { l, A, m, n, o, p } from "./dom-Duh2pvkf.js";
import { retain, release } from "@remote-ui/rpc";
const addVersion = (value) => {
value.version = 0;
return value;
};
function deserialize(node, deserializer) {
if ("children" in node) {
node.children.forEach((child) => deserialize(child, deserializer));
}
if (isSerializedComponent(node)) {
const { properties } = node;
for (const key of keysOf(properties)) {
const prop = properties[key];
if (isSerializedFragment(prop)) {
properties[key] = deserialize(prop, deserializer);
}
}
}
return deserializer(node);
}
const isReceivedFragment = (value) => {
return isSerializedFragment(value) && "version" in value;
};
const createContext = () => {
const root = {
id: ROOT_ID,
kind: KIND_ROOT,
children: [],
version: 0
};
const all = /* @__PURE__ */ new Map([[ROOT_ID, root]]);
const context = {
get root() {
return root;
},
state: "unmounted",
get(id) {
return all.get(id) ?? null;
}
};
addMethod(context, "attach", (node) => attach(all, node));
addMethod(context, "detach", (node) => detach(all, node));
return context;
};
function attach(nodes, child) {
nodes.set(child.id, child);
if (child.kind === KIND_COMPONENT) {
const { properties } = child;
keysOf(properties).forEach((key) => {
const prop = properties[key];
if (isReceivedFragment(prop)) {
attach(nodes, prop);
}
});
}
if ("children" in child) {
child.children.forEach((c) => attach(nodes, c));
}
}
function detach(nodes, child) {
nodes.delete(child.id);
if (child.kind === KIND_COMPONENT) {
const { properties } = child;
keysOf(properties).forEach((key) => {
const prop = properties[key];
if (isReceivedFragment(prop)) {
detach(nodes, prop);
}
});
}
if ("children" in child) {
child.children.forEach((c) => detach(nodes, c));
}
}
const createEmitter = () => {
const handlers = /* @__PURE__ */ new Map();
const emit = (event) => {
const eventHandlers = handlers.get(event);
if (eventHandlers) {
for (const handle of eventHandlers) {
handle();
}
}
};
const on = (event, handler) => {
let eventHandlers = handlers.get(event);
if (eventHandlers == null) {
eventHandlers = /* @__PURE__ */ new Set();
handlers.set(event, eventHandlers);
}
eventHandlers.add(handler);
return () => {
const eventHandlers2 = handlers.get(event);
if (eventHandlers2) {
eventHandlers2.delete(handler);
if (eventHandlers2.size === 0) {
handlers.delete(event);
}
}
};
};
return { emit, on };
};
const createInvoker = () => {
const handlers = /* @__PURE__ */ new Map();
const invoke = (id, method, payload, resolve, reject) => {
const handler = handlers.get(id);
if (handler) {
try {
resolve(handler(method, payload));
} catch (e) {
reject(e instanceof Error ? e.message : e);
}
} else {
reject(`No handler for node [ID=${id}]`);
}
};
const register = (id, handler) => {
handlers.set(id, handler);
return () => {
if (handlers.has(id)) {
handlers.delete(id);
}
};
};
return { invoke, register };
};
const createUpdater = () => {
const handlers = /* @__PURE__ */ new Map();
const updates = /* @__PURE__ */ new Set();
let timeout = null;
const flush = () => timeout ?? Promise.resolve();
const enqueueUpdate2 = (received) => {
timeout = timeout ?? new Promise((resolve) => {
setTimeout(() => {
const awaiting = [...updates];
timeout = null;
updates.clear();
for (const received2 of awaiting) {
call(handlers, received2);
}
resolve();
}, 0);
});
updates.add(received);
return timeout;
};
const register = ({ id }, handler) => {
add(handlers, id, handler);
return () => remove$1(handlers, id, handler);
};
return {
enqueueUpdate: enqueueUpdate2,
register,
flush
};
};
function add(all, id, handler) {
let handlers = all.get(id);
if (handlers == null) {
handlers = /* @__PURE__ */ new Set();
all.set(id, handlers);
}
handlers.add(handler);
}
function call(all, received) {
const handlers = all.get(received.id);
if (handlers) {
for (const handler of handlers) {
handler(received);
}
}
}
function remove$1(all, id, subscriber) {
const handlers = all.get(id);
if (handlers) {
handlers.delete(subscriber);
if (handlers.size === 0) {
all.delete(id);
}
}
}
const insert = (target, el, after) => {
if (after === target.length) {
target.push(el);
} else {
target.splice(after, 0, el);
}
};
const remove = (target, criteria) => {
const at = target.findIndex(criteria);
return at > 0 ? target.splice(at, 1)[0] : void 0;
};
const awaitUpdate = (node, updater) => {
node.version += 1;
return updater.enqueueUpdate(node);
};
const enqueueUpdate = (node, updater) => {
awaitUpdate(node, updater);
};
const addMountMethod = (context, updater, emitter) => addMethod(context, "mount", (children) => {
context.root.children = children.map((c) => {
const node = deserialize(c, addVersion);
retain(node);
context.attach(node);
return node;
});
awaitUpdate(context.root, updater).then(() => {
context.state = "mounted";
emitter.emit("mount");
});
});
const addInsertMethod = (context, updater) => addMethod(context, "insertChild", (parentId, after, child, oldParentId) => {
const parent = context.get(parentId);
const oldParent = parentId === oldParentId ? parent : oldParentId !== false ? context.get(oldParentId) : null;
let received;
if (oldParent) {
received = remove(oldParent.children, (c) => c.id === child.id);
if (parentId !== oldParentId) {
enqueueUpdate(oldParent, updater);
}
} else {
received = deserialize(child, addVersion);
retain(received);
context.attach(received);
}
insert(parent.children, received, after);
enqueueUpdate(parent, updater);
});
const addRemoveMethod = (context, updater) => addMethod(context, "removeChild", (id, index) => {
const node = context.get(id);
const [removed] = node.children.splice(index, 1);
context.detach(removed);
awaitUpdate(node, updater).then(() => release(removed));
});
const addUpdatePropertiesMethod = (context, updater) => addMethod(context, "updateProperties", (id, newProperties) => {
const component = context.get(id);
const oldProperties = { ...component.properties };
retain(newProperties);
keysOf(newProperties).forEach((key) => {
const newProp = newProperties[key];
const oldProp = oldProperties[key];
if (isReceivedFragment(oldProp)) {
context.detach(oldProp);
}
if (isSerializedFragment(newProp)) {
context.attach(deserialize(newProp, addVersion));
}
});
Object.assign(component.properties, newProperties);
awaitUpdate(component, updater).then(() => {
keysOf(newProperties).forEach((k) => release(oldProperties[k]));
});
});
const addUpdateTextMethod = (context, updater) => addMethod(context, "updateText", (id, text) => {
const node = context.get(id);
node.text = text;
enqueueUpdate(node, updater);
});
const addInvokeMethod = (context, invoker) => {
addMethod(context, "invoke", (id, method, payload, resolve, reject) => {
invoker.invoke(id, method, payload, resolve, reject);
});
};
function createReceiver() {
const context = createContext();
const emitter = createEmitter();
const invoker = createInvoker();
const updater = createUpdater();
addMountMethod(context, updater, emitter);
addInsertMethod(context, updater);
addUpdatePropertiesMethod(context, updater);
addUpdateTextMethod(context, updater);
addRemoveMethod(context, updater);
addInvokeMethod(context, invoker);
return {
get state() {
return context.state;
},
receive: createChannel(context),
tree: {
get root() {
return context.root;
},
get({ id }) {
return context.get(id);
},
invokable({ id }, handler) {
return invoker.register(id, handler);
},
updatable({ id }, handler) {
return updater.register({ id }, handler);
}
},
on: (event, handler) => emitter.on(event, handler),
flush: () => updater.flush()
};
}
const isComment = (node) => node.kind === KIND_COMMENT;
const isSlot = (node) => {
return "type" in node && node.type === REMOTE_SLOT;
};
const isText = (node) => node.kind === KIND_TEXT;
const serializeTarget = (target) => {
switch (true) {
case target instanceof HTMLInputElement:
case target instanceof HTMLSelectElement:
case target instanceof HTMLTextAreaElement:
return {
value: target.value,
...target instanceof HTMLInputElement && { checked: target.checked },
...target instanceof HTMLSelectElement && {
selectedIndex: target.selectedIndex,
selectedOptions: [...target.selectedOptions].map((option) => ({
value: option.value,
text: option.text,
selected: option.selected
}))
}
};
case target instanceof HTMLElement:
return {};
}
return {};
};
const serializeBaseEvent = (event) => {
return {
type: event.type,
target: event.target ? serializeTarget(event.target) : null,
currentTarget: event.currentTarget ? serializeTarget(event.currentTarget) : null,
bubbles: event.bubbles,
cancelable: event.cancelable,
composed: event.composed,
defaultPrevented: event.defaultPrevented,
eventPhase: event.eventPhase,
isTrusted: event.isTrusted
};
};
const serializeFiles = (files) => {
return [...files].map((file) => ({
lastModified: file.lastModified,
name: file.name,
webkitRelativePath: file.webkitRelativePath,
size: file.size,
type: file.type
}));
};
const serializeDataTransfer = (dataTransfer) => {
return {
dropEffect: dataTransfer.dropEffect,
effectAllowed: dataTransfer.effectAllowed,
types: dataTransfer.types,
files: serializeFiles(dataTransfer.files)
};
};
const serializeDragEvent = (event) => {
return {
...serializeMouseEvent(event),
dataTransfer: event.dataTransfer ? serializeDataTransfer(event.dataTransfer) : null
};
};
const serializeInputEvent = (event) => {
return {
...serializeBaseEvent(event),
data: event.data
};
};
const serializeFocusEvent = (event) => {
return {
...serializeBaseEvent(event),
relatedTarget: event.relatedTarget ? { tagName: event.relatedTarget.tagName } : null
};
};
const serializeKeyboardEvent = (event) => {
return {
...serializeBaseEvent(event),
key: event.key,
code: event.code,
altKey: event.altKey,
ctrlKey: event.ctrlKey,
shiftKey: event.shiftKey,
metaKey: event.metaKey
};
};
const serializeMouseEvent = (event) => ({
...serializeBaseEvent(event),
clientX: event.clientX,
clientY: event.clientY,
button: event.button
});
const serializePointerEvent = (event) => ({
...serializeMouseEvent(event),
height: event.height,
isPrimary: event.isPrimary,
pointerId: event.pointerId,
pointerType: event.pointerType,
pressure: event.pressure,
tangentialPressure: event.tangentialPressure,
tiltX: event.tiltX,
tiltY: event.tiltY,
twist: event.twist,
width: event.width
});
const serializeTouchList = (touchList) => [...touchList].map((touch) => ({
clientX: touch.clientX,
clientY: touch.clientY,
force: touch.force,
identifier: touch.identifier,
pageX: touch.pageX,
pageY: touch.pageY,
radiusX: touch.radiusX,
radiusY: touch.radiusY,
rotationAngle: touch.rotationAngle,
screenX: touch.screenX,
screenY: touch.screenY
}));
const serializeTouchEvent = (event) => ({
...serializeBaseEvent(event),
altKey: event.altKey,
changedTouches: serializeTouchList(event.changedTouches),
ctrlKey: event.ctrlKey,
metaKey: event.metaKey,
shiftKey: event.shiftKey,
targetTouches: serializeTouchList(event.targetTouches),
touches: serializeTouchList(event.touches)
});
const serializeWheelEvent = (event) => ({
...serializeMouseEvent(event),
deltaMode: event.deltaMode,
deltaX: event.deltaX,
deltaY: event.deltaY,
deltaZ: event.deltaZ,
DOM_DELTA_PIXEL: event.DOM_DELTA_PIXEL,
DOM_DELTA_LINE: event.DOM_DELTA_LINE,
DOM_DELTA_PAGE: event.DOM_DELTA_PAGE
});
const serializeEvent = (event) => {
if (event instanceof InputEvent) {
return serializeInputEvent(event);
}
if (event instanceof DragEvent) {
return serializeDragEvent(event);
}
if (event instanceof FocusEvent) {
return serializeFocusEvent(event);
}
if (event instanceof KeyboardEvent) {
return serializeKeyboardEvent(event);
}
if (event instanceof PointerEvent) {
return serializePointerEvent(event);
}
if (event instanceof WheelEvent) {
return serializeWheelEvent(event);
}
if (event instanceof MouseEvent) {
return serializeMouseEvent(event);
}
if (typeof window !== "undefined" && window.TouchEvent && event instanceof TouchEvent) {
return serializeTouchEvent(event);
}
return serializeBaseEvent(event);
};
const toSlots = (children, render2) => {
const defaultSlot = [];
const slots = {};
children.forEach((child) => {
if (isSlot(child)) {
const slotName = child.properties.value.name;
slots[slotName] = [
...slots[slotName] ?? [],
...child.children.value
];
} else if (!isText(child) || child.text.value.length > 0) {
defaultSlot.push(child);
}
});
return {
...Object.keys(slots).reduce((named, slotName) => {
return { ...named, [slotName]: () => slots[slotName].map(render2) };
}, {}),
default: () => defaultSlot.map(render2)
};
};
const isJavaScriptSchema = (value) => {
const normalized = value.trim().toLowerCase();
return normalized.startsWith("javascript:") || decodeURIComponent(normalized).startsWith("javascript:");
};
const process = (properties) => {
if (properties === void 0) {
return void 0;
}
const result = {};
for (const key in properties) {
const v = properties[key];
if (typeof v === "string" && isJavaScriptSchema(v)) {
result[key] = "javascript:void(0);";
continue;
}
result[key] = /^on[A-Z]/.test(key) && isFunction(v) ? (...args) => v(...args.map((arg) => arg instanceof Event ? serializeEvent(arg) : arg)) : v;
}
return result;
};
const render = (node, provider) => {
if ("type" in node) {
if (isSlot(node)) {
console.error("Found an orphan remote slot", node);
return null;
}
const props = { ...process(node.properties.value), ref: node.ref };
const children = node.children.value;
return isDOMTag(node.type) ? h(node.type, props, children.map((child) => render(child, provider))) : h(provider.get(node.type), { ...props, key: node.id }, toSlots(
children,
(child) => render(child, provider)
));
}
return isComment(node) ? createCommentVNode(node.text.value) : isText(node) && node.text.value.length > 0 ? node.text.value : null;
};
function useReceived(receiver) {
const root = receiver.tree.root;
const children = useChildren(receiver, root);
return {
id: root.id,
kind: KIND_ROOT,
children: children.ref,
update() {
children.load(root, true);
},
release: fuse([
receiver.tree.updatable(root, children.load),
() => children.ref.value.forEach((c) => c.release())
])
};
}
function useComment(receiver, node) {
const text = ref(node.text);
return {
id: node.id,
kind: KIND_COMMENT,
text,
update() {
const n2 = receiver.tree.get(node);
if (n2) {
text.value = n2.text;
}
},
release: receiver.tree.updatable(node, (n2) => {
text.value = n2.text;
})
};
}
function useComponent(receiver, node) {
const _ref = ref(null);
const properties = ref(node.properties);
const children = useChildren(receiver, node);
const load = (node2) => {
properties.value = node2.properties;
children.load(node2);
};
const release2 = fuse([
receiver.tree.updatable(node, load),
receiver.tree.invokable(node, useInvokeHandler(node, _ref)),
() => children.ref.value.forEach((c) => c.release())
]);
return {
id: node.id,
kind: KIND_COMPONENT,
type: node.type,
ref: _ref,
properties,
children: children.ref,
update: () => {
const n2 = receiver.tree.get(node);
if (n2) {
load(n2);
}
},
release: release2
};
}
function useText(receiver, node) {
const text = ref(node.text);
return {
id: node.id,
kind: KIND_TEXT,
text,
update() {
const c = receiver.tree.get(node);
if (c) {
text.value = c.text;
}
},
release: receiver.tree.updatable(node, (n2) => {
text.value = n2.text;
})
};
}
function useChild(receiver, child) {
switch (child.kind) {
case KIND_COMMENT:
return useComment(receiver, child);
case KIND_COMPONENT:
return useComponent(receiver, child);
case KIND_TEXT:
return useText(receiver, child);
}
}
function useChildren(receiver, node) {
const ref2 = shallowRef(node.children.map((c) => useChild(receiver, c)));
const load = (node2, force = false) => {
ref2.value.forEach((_old) => {
if (!node2.children.some((_new) => _new.id === _old.id)) {
_old.release();
}
});
ref2.value = node2.children.map((_new) => {
const foundOld = ref2.value.find((_old) => _old.id === _new.id);
if (force && foundOld) {
foundOld.update();
}
return foundOld ?? useChild(receiver, _new);
});
};
return { ref: ref2, load };
}
function useInvokeHandler(node, ref2) {
return (method, payload) => {
const el = ref2.value;
if (el == null) {
throw new Error(`${print(node)} not mounted to host environment yet`);
}
if (!(method in el)) {
throw new Error(`${print(node)} doesn't support method ${method}`);
}
const callable = el[method];
if (!isFunction(callable)) {
throw new Error(`${print(node)} doesn't support method ${method}`);
}
return callable.call(el, ...payload);
};
}
function print(node) {
const { id, kind } = node;
return "type" in node ? `Node [ID=${id}, KIND=${kind}, TYPE=${node.type}]` : `Node [ID=${id}, KIND=${kind}]`;
}
function fuse(handlers) {
return () => handlers.forEach((fn) => fn());
}
const HostedTree = /* @__PURE__ */ defineComponent({
name: "HostedTree",
props: {
provider: {
type: Object,
required: true
},
receiver: {
type: Object,
required: true
}
},
setup(props, { expose }) {
const tree = shallowRef(useReceived(props.receiver));
watch(() => props.receiver, () => {
tree.value.release();
tree.value = useReceived(props.receiver);
});
expose({
forceUpdate: () => tree.value.update()
});
onUnmounted(tree.value.release);
return () => tree.value.children.value.filter((child) => !isText(child) || child.text.value.length > 0).map((root) => render(root, props.provider));
}
});
const createProvider = (components = {}) => {
const registry = new Map(Object.entries(components));
return {
get(type) {
const value = registry.get(type);
if (value == null) {
throw new Error(`Unknown component: ${type}`);
}
return value;
}
};
};
export {
l as ACTION_INSERT_CHILD,
A as ACTION_MOUNT,
m as ACTION_REMOVE_CHILD,
n as ACTION_UPDATE_PROPERTIES,
o as ACTION_UPDATE_TEXT,
HostedTree,
KIND_COMMENT,
KIND_COMPONENT,
p as KIND_FRAGMENT,
KIND_ROOT,
KIND_TEXT,
ROOT_ID,
createProvider,
createReceiver
};