UNPKG

@lynx-js/web-core

Version:

This is an internal experimental package, do not use

415 lines 19.2 kB
import { wasmInstance } from '../../wasm.js'; import { AnimationOperation, LYNX_TAG_TO_HTML_TAG_MAP, LYNX_TIMING_FLAG_ATTRIBUTE, lynxDefaultDisplayLinearAttribute, lynxDefaultOverflowVisibleAttribute, lynxDisposedAttribute, lynxEntryNameAttribute, uniqueIdSymbol, } from '../../../constants.js'; import { __SwapElement, __AppendElement, __ElementIsEqual, __FirstElement, __GetChildren, __GetParent, __InsertElementBefore, __LastElement, __NextElement, __RemoveElement, __ReplaceElement, __ReplaceElements, __GetAttributes, __GetAttributeByName, __GetID, __SetID, __GetTag, __GetClasses, __SetClasses, __AddClass, __MarkTemplateElement, __MarkPartElement, __GetElementUniqueID, __GetTemplateParts, __UpdateListCallbacks, __InvokeUIMethod, __QuerySelector, __QuerySelectorAll, } from './pureElementPAPIs.js'; import { requestIdleCallbackImpl } from '../utils/requestIdleCallback.js'; const { MainThreadWasmContext, add_inline_style_raw_string_key, set_inline_styles_number_key, set_inline_styles_in_str, set_inline_styles_in_key_value_vec, } = wasmInstance; export function createElementAPI(rootDom, mtsBinding, config_enable_css_selector, config_default_display_linear, config_default_overflow_visible, transform_vw, transform_vh, transform_rem) { let wasmContext = new MainThreadWasmContext(rootDom, mtsBinding, config_enable_css_selector); let page = undefined; const timingFlags = []; let disposed = false; mtsBinding.wasmContext = wasmContext; mtsBinding.disposeWasmContext = () => { if (disposed) return; disposed = true; if (wasmContext) { wasmContext.free(); // @ts-expect-error It's better to throw an Error than triggering an use-after-free of rust struct wasmContext = null; } page = undefined; timingFlags.length = 0; }; const __SetCSSId = (elements, cssId, entryName) => { const uniqueIds = elements.map((element) => { return element[uniqueIdSymbol]; }); return wasmContext.set_css_id(new Uint32Array(uniqueIds), cssId ?? 0, entryName); }; const __AddEvent = (element, eventType, eventName, frameworkCrossThreadIdentifier) => { const uniqueId = element[uniqueIdSymbol]; if (typeof frameworkCrossThreadIdentifier === 'string') { wasmContext.add_cross_thread_event(uniqueId, eventType, eventName, frameworkCrossThreadIdentifier); } else if (frameworkCrossThreadIdentifier == null) { wasmContext.add_cross_thread_event(uniqueId, eventType, eventName, undefined); wasmContext.add_run_worklet_event(uniqueId, eventType, eventName, undefined); } else if (typeof frameworkCrossThreadIdentifier === 'object') { wasmContext.add_run_worklet_event(uniqueId, eventType, eventName, frameworkCrossThreadIdentifier); } if (eventName === 'uiappear' || eventName === 'uidisappear') { const element = wasmContext.get_dom_by_unique_id(uniqueId)?.deref(); if (element) { mtsBinding.markExposureRelatedElementByUniqueId(element, frameworkCrossThreadIdentifier != null); } } }; return { __CreateView(parentComponentUniqueId) { const dom = document.createElement('x-view'); dom[uniqueIdSymbol] = wasmContext.create_element_common(parentComponentUniqueId, dom, new WeakRef(dom)); return dom; }, __CreateText(parentComponentUniqueId) { const dom = document.createElement('x-text'); dom[uniqueIdSymbol] = wasmContext.create_element_common(parentComponentUniqueId, dom, new WeakRef(dom)); return dom; }, __CreateImage(parentComponentUniqueId) { const dom = document.createElement('x-image'); dom[uniqueIdSymbol] = wasmContext.create_element_common(parentComponentUniqueId, dom, new WeakRef(dom)); return dom; }, __CreateRawText(text) { const dom = document.createElement('raw-text'); dom.setAttribute('text', text); dom[uniqueIdSymbol] = wasmContext.create_element_common(-1, dom, new WeakRef(dom)); return dom; }, __CreateScrollView(parentComponentUniqueId) { const dom = document.createElement('scroll-view'); dom[uniqueIdSymbol] = wasmContext.create_element_common(parentComponentUniqueId, dom, new WeakRef(dom)); return dom; }, __CreateElement(tagName, parentComponentUniqueId) { const dom = document.createElement(LYNX_TAG_TO_HTML_TAG_MAP[tagName] ?? tagName); dom[uniqueIdSymbol] = wasmContext.create_element_common(parentComponentUniqueId, dom, new WeakRef(dom)); return dom; }, __CreateComponent(parentComponentUniqueId, componentID, cssID, entryName, name) { const dom = document.createElement('x-view'); dom[uniqueIdSymbol] = wasmContext.create_element_common(parentComponentUniqueId, dom, new WeakRef(dom), cssID, componentID); if (entryName) { dom.setAttribute(lynxEntryNameAttribute, entryName); } if (name) { dom.setAttribute('name', name); } return dom; }, __CreateWrapperElement(parentComponentUniqueId) { const dom = document.createElement('lynx-wrapper'); dom[uniqueIdSymbol] = wasmContext.create_element_common(parentComponentUniqueId, dom, new WeakRef(dom)); return dom; }, __CreateList(parentComponentUniqueId, componentAtIndex, enqueueComponent) { const dom = document.createElement('x-list'); dom.componentAtIndex = componentAtIndex; dom.enqueueComponent = enqueueComponent; dom[uniqueIdSymbol] = wasmContext.create_element_common(parentComponentUniqueId, dom, new WeakRef(dom)); return dom; }, __CreatePage(componentID, cssID) { if (page) return page; const dom = document.createElement('div'); dom[uniqueIdSymbol] = wasmContext.create_element_common(0, dom, new WeakRef(dom), cssID, componentID); if (config_default_overflow_visible) { dom.setAttribute(lynxDefaultOverflowVisibleAttribute, 'true'); } if (!config_default_display_linear) { dom.setAttribute(lynxDefaultDisplayLinearAttribute, 'false'); } dom.setAttribute('part', 'page'); page = dom; return dom; }, __SetClasses: config_enable_css_selector ? __SetClasses : ((element, classname) => { __SetClasses(element, classname); const uniqueId = element[uniqueIdSymbol]; wasmContext.update_css_og_style(uniqueId, element.getAttribute(lynxEntryNameAttribute)); }), __SetCSSId, __AddInlineStyle: (element, key, value) => { let valStr = null; if (value != null) { valStr = value.toString(); } if (typeof key === 'number') { return set_inline_styles_number_key(element, key, valStr); } else { return add_inline_style_raw_string_key(element, key.toString(), valStr); } }, __SetInlineStyles: (element, value) => { if (!value) { element.removeAttribute('style'); } else { if (typeof value === 'string') { if (!set_inline_styles_in_str(element, value, transform_vw, transform_vh, transform_rem)) { element.setAttribute('style', value); } } else if (!value) { element.removeAttribute('style'); } else { const vec = []; for (const [k, v] of Object.entries(value)) { if (v != null) { vec.push(k, v.toString()); } } set_inline_styles_in_key_value_vec(element, vec, transform_vw, transform_vh, transform_rem); } } }, __AddConfig: (element, type, value) => { const uniqueId = element[uniqueIdSymbol]; const config = wasmContext.get_config(uniqueId); // @ts-ignore config[type] = value; }, __UpdateComponentInfo: (element, componentInfo) => { const uniqueId = element[uniqueIdSymbol]; const { componentID, cssID, entry, name } = componentInfo; if (name) { element.setAttribute('name', name); } else { element.removeAttribute('name'); } wasmContext.update_component_id(uniqueId, componentID); if (cssID !== undefined) { __SetCSSId([element], cssID, entry); } }, __UpdateComponentID: (element, componentID) => { const uniqueId = element[uniqueIdSymbol]; wasmContext.update_component_id(uniqueId, componentID); }, __GetConfig: (element) => { const uniqueId = element[uniqueIdSymbol]; return wasmContext.get_config(uniqueId); }, __SetConfig: (element, config) => { const uniqueId = element[uniqueIdSymbol]; wasmContext.set_config(uniqueId, config); }, __GetElementConfig: (element) => { const uniqueId = element[uniqueIdSymbol]; return wasmContext.get_element_config(uniqueId); }, __GetComponentID: (element) => { const uniqueId = element[uniqueIdSymbol]; return wasmContext.get_component_id(uniqueId); }, __SetDataset: (element, dataset) => { const uniqueId = element[uniqueIdSymbol]; wasmContext.set_dataset(uniqueId, element, dataset); }, __AddDataset: (element, key, value) => { const uniqueId = element[uniqueIdSymbol]; if (value) { element.setAttribute(`data-${key}`, typeof value === 'object' ? JSON.stringify(value) : value.toString()); } else { element.removeAttribute(`data-${key}`); } wasmContext.add_dataset(uniqueId, key, value); }, __GetDataset: (element) => { const uniqueId = element[uniqueIdSymbol]; return Object.assign(Object.create(null), wasmContext.get_dataset(uniqueId)); }, __GetDataByKey: (element, key) => { const uniqueId = element[uniqueIdSymbol]; return wasmContext.get_data_by_key(uniqueId, key); }, __SetAttribute(element, name, value) { if (name === 'update-list-info') { const { insertAction, removeAction } = value; queueMicrotask(() => { const componentAtIndex = element.componentAtIndex; const enqueueComponent = element.enqueueComponent; const uniqueId = __GetElementUniqueID(element); removeAction.forEach((position, i) => { const removedEle = element.children[position - i]; if (removedEle) { const sign = __GetElementUniqueID(removedEle); enqueueComponent?.(element, uniqueId, sign); element.removeChild(removedEle); } }); for (const action of insertAction) { const childSign = componentAtIndex?.(element, uniqueId, action.position, 0, false); if (typeof childSign === 'number') { const childElement = wasmContext.get_dom_by_unique_id(childSign) ?.deref(); if (childElement) { const referenceNode = element.children[action.position]; if (referenceNode !== childElement) { element.insertBefore(childElement, referenceNode || null); } } } } }); } else { if (value == null) { element.removeAttribute(name); } else { element.setAttribute(name, value.toString()); } if (name === 'exposure-id') { if (value != null) { mtsBinding.markExposureRelatedElementByUniqueId(element, true); } else { mtsBinding.markExposureRelatedElementByUniqueId(element, false); } } else if (name === LYNX_TIMING_FLAG_ATTRIBUTE) { timingFlags.push(String(value)); } } }, __AddEvent, __GetEvent: (element, eventType, eventName) => { const uniqueId = element[uniqueIdSymbol]; return wasmContext.get_event(uniqueId, eventType, eventName); }, __GetEvents: (element) => { const uniqueId = element[uniqueIdSymbol]; return wasmContext.get_events(uniqueId); }, __SetEvents: (element, events) => { for (const event of events) { __AddEvent(element, event.type, event.name, event.function); } }, __GetPageElement: () => page, __AppendElement, __ElementIsEqual, __FirstElement, __GetChildren, __GetParent, __InsertElementBefore, __LastElement, __NextElement, __RemoveElement, __ReplaceElement, __GetAttributes, __GetAttributeByName, __ReplaceElements, __GetID, __SetID, __GetTag, __AddClass, __GetClasses, __MarkTemplateElement, __MarkPartElement, __GetTemplateParts, __GetElementUniqueID, __UpdateListCallbacks, __SwapElement, __ElementAnimate: (() => { const animationMap = new Map(); const mapTimingOptions = (options) => { if (!options) return undefined; const result = {}; if ('duration' in options) { result.duration = Number(options['duration']); } if ('delay' in options) result.delay = Number(options['delay']); if ('direction' in options) { result.direction = options['direction']; } if ('iterationCount' in options) { result.iterations = options['iterationCount'] === 'infinite' ? Infinity : Number(options['iterationCount']); } if ('fillMode' in options) { result.fill = options['fillMode']; } if ('timingFunction' in options) { result.easing = options['timingFunction']; } return result; }; return (element, args) => { const [operation, name] = args; switch (operation) { case AnimationOperation.START: { const keyframes = args[2]; const options = args[3]; animationMap.get(name)?.cancel(); const animation = element.animate(keyframes, mapTimingOptions(options)); animation.oncancel = animation.onfinish = () => { if (animationMap.get(name) === animation) { animationMap.delete(name); } }; animationMap.set(name, animation); break; } case AnimationOperation.PLAY: animationMap.get(name)?.play(); break; case AnimationOperation.PAUSE: animationMap.get(name)?.pause(); break; case AnimationOperation.CANCEL: animationMap.get(name)?.cancel(); animationMap.delete(name); break; case AnimationOperation.FINISH: animationMap.get(name)?.finish(); break; } }; })(), __InvokeUIMethod, __QuerySelector, __QuerySelectorAll, __FlushElementTree: (_, options) => { const pipelineId = options?.pipelineOptions?.pipelineID; const backgroundThread = mtsBinding.lynxViewInstance.backgroundThread; if (page && !page.parentNode && page.getAttribute(lynxDisposedAttribute) !== '') { backgroundThread.markTiming('dispatch_start', pipelineId); backgroundThread.jsContext.dispatchEvent({ type: '__OnNativeAppReady', data: undefined, }); backgroundThread.markTiming('layout_start', pipelineId); backgroundThread.markTiming('ui_operation_flush_start', pipelineId); rootDom.appendChild(page); rootDom.host.style.display = 'flex'; backgroundThread.markTiming('ui_operation_flush_end', pipelineId); backgroundThread.markTiming('layout_end', pipelineId); backgroundThread.markTiming('dispatch_end', pipelineId); backgroundThread.flushTimingInfo(); } let timingFlagsAll = timingFlags.concat(wasmContext.take_timing_flags()); requestIdleCallbackImpl(() => { if (disposed) return; mtsBinding.postTimingFlags(timingFlagsAll, pipelineId); wasmContext.gc(); }); timingFlags.length = 0; const enabledExposureElements = [ ...mtsBinding.toBeEnabledElement, ]; mtsBinding.toBeEnabledElement.clear(); const disabledExposureElements = [ ...mtsBinding.toBeDisabledElement, ]; mtsBinding.toBeDisabledElement.clear(); mtsBinding?.updateExposureStatus(enabledExposureElements, disabledExposureElements); }, }; } //# sourceMappingURL=createElementAPI.js.map