UNPKG

@omnicajs/vue-remote

Version:

Proxy renderer for Vue.js based on @remote-ui

704 lines (703 loc) 20.5 kB
"use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const vue = require("vue"); const dom = require("./dom-BVh86cof.cjs"); const rpc = require("@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 (dom.isSerializedComponent(node)) { const { properties } = node; for (const key of dom.keysOf(properties)) { const prop = properties[key]; if (dom.isSerializedFragment(prop)) { properties[key] = deserialize(prop, deserializer); } } } return deserializer(node); } const isReceivedFragment = (value) => { return dom.isSerializedFragment(value) && "version" in value; }; const createContext = () => { const root = { id: dom.ROOT_ID, kind: dom.KIND_ROOT, children: [], version: 0 }; const all = /* @__PURE__ */ new Map([[dom.ROOT_ID, root]]); const context = { get root() { return root; }, state: "unmounted", get(id) { return all.get(id) ?? null; } }; dom.addMethod(context, "attach", (node) => attach(all, node)); dom.addMethod(context, "detach", (node) => detach(all, node)); return context; }; function attach(nodes, child) { nodes.set(child.id, child); if (child.kind === dom.KIND_COMPONENT) { const { properties } = child; dom.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 === dom.KIND_COMPONENT) { const { properties } = child; dom.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) => dom.addMethod(context, "mount", (children) => { context.root.children = children.map((c) => { const node = deserialize(c, addVersion); rpc.retain(node); context.attach(node); return node; }); awaitUpdate(context.root, updater).then(() => { context.state = "mounted"; emitter.emit("mount"); }); }); const addInsertMethod = (context, updater) => dom.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); rpc.retain(received); context.attach(received); } insert(parent.children, received, after); enqueueUpdate(parent, updater); }); const addRemoveMethod = (context, updater) => dom.addMethod(context, "removeChild", (id, index) => { const node = context.get(id); const [removed] = node.children.splice(index, 1); context.detach(removed); awaitUpdate(node, updater).then(() => rpc.release(removed)); }); const addUpdatePropertiesMethod = (context, updater) => dom.addMethod(context, "updateProperties", (id, newProperties) => { const component = context.get(id); const oldProperties = { ...component.properties }; rpc.retain(newProperties); dom.keysOf(newProperties).forEach((key) => { const newProp = newProperties[key]; const oldProp = oldProperties[key]; if (isReceivedFragment(oldProp)) { context.detach(oldProp); } if (dom.isSerializedFragment(newProp)) { context.attach(deserialize(newProp, addVersion)); } }); Object.assign(component.properties, newProperties); awaitUpdate(component, updater).then(() => { dom.keysOf(newProperties).forEach((k) => rpc.release(oldProperties[k])); }); }); const addUpdateTextMethod = (context, updater) => dom.addMethod(context, "updateText", (id, text) => { const node = context.get(id); node.text = text; enqueueUpdate(node, updater); }); const addInvokeMethod = (context, invoker) => { dom.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: dom.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 === dom.KIND_COMMENT; const isSlot = (node) => { return "type" in node && node.type === dom.REMOTE_SLOT; }; const isText = (node) => node.kind === dom.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) && dom.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 dom.isDOMTag(node.type) ? vue.h(node.type, props, children.map((child) => render(child, provider))) : vue.h(provider.get(node.type), { ...props, key: node.id }, toSlots( children, (child) => render(child, provider) )); } return isComment(node) ? vue.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: dom.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 = vue.ref(node.text); return { id: node.id, kind: dom.KIND_COMMENT, text, update() { const n = receiver.tree.get(node); if (n) { text.value = n.text; } }, release: receiver.tree.updatable(node, (n) => { text.value = n.text; }) }; } function useComponent(receiver, node) { const _ref = vue.ref(null); const properties = vue.ref(node.properties); const children = useChildren(receiver, node); const load = (node2) => { properties.value = node2.properties; children.load(node2); }; const release = 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: dom.KIND_COMPONENT, type: node.type, ref: _ref, properties, children: children.ref, update: () => { const n = receiver.tree.get(node); if (n) { load(n); } }, release }; } function useText(receiver, node) { const text = vue.ref(node.text); return { id: node.id, kind: dom.KIND_TEXT, text, update() { const c = receiver.tree.get(node); if (c) { text.value = c.text; } }, release: receiver.tree.updatable(node, (n) => { text.value = n.text; }) }; } function useChild(receiver, child) { switch (child.kind) { case dom.KIND_COMMENT: return useComment(receiver, child); case dom.KIND_COMPONENT: return useComponent(receiver, child); case dom.KIND_TEXT: return useText(receiver, child); } } function useChildren(receiver, node) { const ref2 = vue.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 (!dom.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__ */ vue.defineComponent({ name: "HostedTree", props: { provider: { type: Object, required: true }, receiver: { type: Object, required: true } }, setup(props, { expose }) { const tree = vue.shallowRef(useReceived(props.receiver)); vue.watch(() => props.receiver, () => { tree.value.release(); tree.value = useReceived(props.receiver); }); expose({ forceUpdate: () => tree.value.update() }); vue.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; } }; }; exports.ACTION_INSERT_CHILD = dom.ACTION_INSERT_CHILD; exports.ACTION_MOUNT = dom.ACTION_MOUNT; exports.ACTION_REMOVE_CHILD = dom.ACTION_REMOVE_CHILD; exports.ACTION_UPDATE_PROPERTIES = dom.ACTION_UPDATE_PROPERTIES; exports.ACTION_UPDATE_TEXT = dom.ACTION_UPDATE_TEXT; exports.KIND_COMMENT = dom.KIND_COMMENT; exports.KIND_COMPONENT = dom.KIND_COMPONENT; exports.KIND_FRAGMENT = dom.KIND_FRAGMENT; exports.KIND_ROOT = dom.KIND_ROOT; exports.KIND_TEXT = dom.KIND_TEXT; exports.ROOT_ID = dom.ROOT_ID; exports.HostedTree = HostedTree; exports.createProvider = createProvider; exports.createReceiver = createReceiver;