@lynx-js/web-core
Version:
This is an internal experimental package, do not use
415 lines • 19.2 kB
JavaScript
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