UNPKG

react-nativescript

Version:
492 lines 26.8 kB
import { View as TNSView, Page as TNSPage, Span as TNSSpan, ActionBar as TNSActionBar, ActionItem as TNSActionItem, NavigationButton as TNSNavigationButton, TabViewItem as TNSTabViewItem, } from '@nativescript/core'; import { __unstable__forwardNavOpts } from "./navigation"; // import { actionBarNodeOps } from './components/ActionBar' // import { warn } from '@vue/runtime-core' import { warn } from "../../shared/Logger"; export let defaultViewMeta = { viewFlags: 0 /* NSVViewFlags.NONE */, }; let elementMap = {}; export function getViewMeta(elementName) { // console.log(`->getViewMeta(${elementName})`) const normalizedName = normalizeElementName(elementName); const entry = elementMap[normalizedName]; if (!entry) { throw new Error(`No known component for element ${elementName}.`); } return entry.meta; } export function getViewClass(elementName) { // console.log(`->getViewClass(${elementName})`) const normalizedName = normalizeElementName(elementName); const entry = elementMap[normalizedName]; if (!entry) { throw new Error(`No known component for element ${elementName}.`); } try { return entry.resolver(); } catch (e) { throw new Error(`Could not load view for: ${elementName}. ${e}`); } } export function normalizeElementName(elementName) { return elementName.replace(/-/g, '').toLowerCase(); } export function registerElement(elementName, resolver, meta) { const normalizedName = normalizeElementName(elementName); const mergedMeta = Object.assign({}, defaultViewMeta, meta); if (elementMap[normalizedName] && !mergedMeta.overwriteExisting) { throw new Error(`Element for ${elementName} already registered.\n` + `If this is intentional set 'overwriteExisting: true' in 'meta'`); } elementMap[normalizedName] = { meta: mergedMeta, resolver, }; // console.log(`->registerElement(${elementName})`) } export function isKnownView(elementName) { return elementMap.hasOwnProperty(normalizeElementName(elementName)); } // register built in elements // prettier-ignore if (!__TEST__) { // layouts registerElement('absoluteLayout', () => require('@nativescript/core').AbsoluteLayout, { viewFlags: 4 /* NSVViewFlags.LAYOUT_VIEW */ }); registerElement('dockLayout', () => require('@nativescript/core').DockLayout, { viewFlags: 4 /* NSVViewFlags.LAYOUT_VIEW */ }); registerElement('flexboxLayout', () => require('@nativescript/core').FlexboxLayout, { viewFlags: 4 /* NSVViewFlags.LAYOUT_VIEW */ }); registerElement('gridLayout', () => require('@nativescript/core').GridLayout, { viewFlags: 4 /* NSVViewFlags.LAYOUT_VIEW */ }); registerElement('stackLayout', () => require('@nativescript/core').StackLayout, { viewFlags: 4 /* NSVViewFlags.LAYOUT_VIEW */ }); registerElement('wrapLayout', () => require('@nativescript/core').WrapLayout, { viewFlags: 4 /* NSVViewFlags.LAYOUT_VIEW */ }); // ContentViews registerElement('contentView', () => require('@nativescript/core').ContentView, { viewFlags: 2 /* NSVViewFlags.CONTENT_VIEW */ }); registerElement('scrollView', () => require('@nativescript/core').ScrollView, { viewFlags: 2 /* NSVViewFlags.CONTENT_VIEW */ }); registerElement('actionBar', () => require('@nativescript/core').ActionBar, { nodeOps: { insert(child, parent, atIndex) { const actionBar = parent.nativeView; if (child.nodeRole === "navigationButton") { if (child.nativeView instanceof TNSNavigationButton) { actionBar.navigationButton = child.nativeView; } else { if (__DEV__) { warn(`Unable to add child "${child.nativeView.constructor.name}" as the navigationButton of <actionBar> as it is not an instance of NavigationButton.`); } } } else if (child.nodeRole === "actionItems") { if (child.nativeView instanceof TNSActionItem === false) { if (__DEV__) { warn(`Unable to add child "${child.nativeView.constructor.name}" to the actionItems of <actionBar> as it is not an instance of ActionItem.`); } ; return; } /** * The implementation shows that getItems() returns a clone of the array, conforming to Array. * @see action-bar-common.js */ const existingItems = actionBar.actionItems.getItems(); if (typeof atIndex === "undefined" || atIndex === existingItems.length) { /** * The implementation shows that addItem() acts as Array.prototype.push(). * @see action-bar-common.js */ actionBar.actionItems.addItem(child.nativeView); } else { /** * actionBar.actionItems doesn't publicly expose a splice() API, so we'll have to do things the hard way. */ const updatedItems = actionBar.actionItems.getItems(); updatedItems.splice(atIndex, 0, child.nativeView); existingItems.forEach(actionItem => actionBar.actionItems.removeItem(actionItem)); updatedItems.forEach(actionItem => actionBar.actionItems.addItem(actionItem)); } } else if (child.nodeRole === "actionItem") { if (__DEV__) { warn(`Unable to add child "${child.nativeView.constructor.name}" to <actionBar> as it had the nodeRole "actionItem"; please correct it to "actionItems".`); } } else if (child.nodeRole === "titleView") { actionBar.titleView = child.nativeView; } else { if (__DEV__) { warn(`Unable to add child "${child.nativeView.constructor.name}" to <actionBar> as it does not have a nodeRole specified; ` + `please set a nodeRole of "navigationButton", "actionItems", or "titleView".`); } } }, remove(child, parent) { const actionBar = parent.nativeView; if (child.nodeRole === "navigationButton") { actionBar.navigationButton = null; // Anything falsy should work. } else if (child.nodeRole === "actionItems") { actionBar.actionItems.removeItem(child.nativeView); } else if (child.nodeRole === "actionItem") { if (__DEV__) { warn(`Unable to remove child "${child.nativeView.constructor.name}" from <actionBar> as it had the nodeRole "actionItem"; please correct it to "actionItems".`); } } else if (child.nodeRole === "titleView") { actionBar.titleView = null; // Anything falsy should work. } else { if (__DEV__) { warn(`Unable to add child "${child.nativeView.constructor.name}" to <actionBar> as it does not have a nodeRole specified; ` + `please set a nodeRole of "navigationButton", "actionItems", or "titleView".`); } } } } }); registerElement('actionItem', () => require('@nativescript/core').ActionItem // _addChildFromBuilder and removeChild both refer to actionView, so no strict need for nodeOps here. ); registerElement('navigationButton', () => require('@nativescript/core').NavigationButton // _addChildFromBuilder and removeChild both refer to actionView, so no strict need for nodeOps here. ); // navigation registerElement('frame', () => require('@nativescript/core').Frame, { stackDepth: 0, // todo: move into Frame.ts when we end up creating a component for Frame nodeOps: { insert(child, parent, atIndex) { const frame = parent.nativeView; const page = child.nativeView; if (child.nativeView instanceof TNSPage) { if (typeof atIndex === "undefined") { const stackDepth = parent.meta.stackDepth; const resolvedNavOpts = Object.assign({}, __unstable__forwardNavOpts.defaultOptions, { animated: stackDepth > -1, clearHistory: stackDepth === -1, backstackVisible: true, }, __unstable__forwardNavOpts.pop() || {}); // console.log(`[frame.insert] [${parent} > ${child} @${atIndex}] => [${parent.childNodes}] via ${parent}.navigate(${child}) (clearHistory ${resolvedNavOpts.clearHistory}); stackDepth ${stackDepth} -> ${stackDepth + 1}`); // console.log(`[frame.insert.pending] backstack is now: [${(frame as unknown as TNSFramePrivate)._backStack.map(entry => entry.resolvedPage)}]`); frame.navigate(Object.assign(Object.assign({}, resolvedNavOpts), { create() { return child.nativeView; } })); // At least on forward navigations, need to wait for the animation to complete before the backstack will reflect the update. // setTimeout(() => { // console.log(`[frame.insert.done] backstack is now: [${(frame as unknown as TNSFramePrivate)._backStack.map(entry => entry.resolvedPage)}]`); // }, 1000); parent.meta.stackDepth++; return; } else { if (__DEV__) { warn(`NativeScript Core does not support splicing pages into frames. Can't add page to index ${atIndex}.`); } } } else { if (__DEV__) { warn(`<frame> must only contain <page> elements - ` + `got <${child.nativeView.constructor.name}> instead.`); } } }, /** * This is a best-of-a-bad-job. NativeScript Core implements push & pop, and * replacement (but only of the topmost, currentEntry of the stack). The latter * was only implemented for HMR purposes. * * We can splice frame._backStack (belonging to frame-common.ts), but it doesn't * update the logic of the frame.ios.ts and frame.android.ts native implementations. */ remove(child, parent) { // console.log(`[frame.remove] ${parent}.childNodes updating to: [${parent.childNodes}]`); var _a; const frame = parent.nativeView; const page = child.nativeView; if (((_a = frame._currentEntry) === null || _a === void 0 ? void 0 : _a.resolvedPage) === page) { if (frame.canGoBack()) { // console.log(`[frame.remove] [${parent} x ${child}] => [${parent.childNodes}] via ${parent}.goBack() on currentEntry page; stackDepth ${parent.meta.stackDepth} -> ${Math.max(0, parent.meta.stackDepth - 1)}`); // console.log(`[frame.remove.pending] backstack is now: [${(frame as unknown as TNSFramePrivate)._backStack.map(entry => entry.resolvedPage)}]`); /** { animated: false } is ignored even if you pass in a backStackEntry that explicitly specifies it. */ frame.goBack(); // console.log(`[frame.remove.done] backstack is now: [${(frame as unknown as TNSFramePrivate)._backStack.map(entry => entry.resolvedPage)}]`); parent.meta.stackDepth = Math.max(0, parent.meta.stackDepth - 1); } else { /** * NativeScript Core does not support transitioning a Frame back to Pageless state. * It's simply not possible, whether by `frame.removeEntry()`, `frame._updateBackstack()`, * or `frame.setCurrent()`. * * ... So the best we can do is indicate that the stack is conceptually empty. * This means that, on next navigation, we should call clearHistory. */ // console.log(`[frame.remove] [${parent} x ${child}] = [${parent.childNodes}] via no-op on currentEntry page; stackDepth ${parent.meta.stackDepth} -> -1`); // console.log(`[frame.remove.done] backstack is now: [${(frame as unknown as TNSFramePrivate)._backStack.map(entry => entry.resolvedPage)}]`); parent.meta.stackDepth = -1; } } else { // console.log(`[frame.remove] ${parent} x ${child}, but child isn't the currentEntry (which is ${frame._currentEntry?.resolvedPage}). May involve a splice or a no-op.`); /** * When React Navigation pops back to the top, it removes from the root of the stack rather than the face. This has some sense, as * it avoids rendering and animating every pop on the way, but it's very problematic for NativeScript Core, which cannot splice * Pages from the Frame's stack. * */ let indexOfBackstackEntry = -1; const backstackEntry = frame._backStack.find((entry, i) => { if (entry.resolvedPage === page) { indexOfBackstackEntry = i; return true; } return false; }); if (backstackEntry) { // console.log(`[frame.remove] Found backStackEntry for ${child} at index ${indexOfBackstackEntry}, so it's a splice.`); // const backStackLengthBefore = (frame as unknown as TNSFramePrivate)._backStack.length; // console.log(`[frame.remove] [${parent} x ${child}] = [${parent.childNodes}] via splice@${indexOfBackstackEntry} of non-currentEntry page; stackDepth ${parent.meta.stackDepth} -> ${Math.max(0, parent.meta.stackDepth - 1)}`); // console.log(`[frame.remove.pending] backstack is now: [${(frame as unknown as TNSFramePrivate)._backStack.map(entry => entry.resolvedPage)}]`); frame._removeEntry(backstackEntry); frame._backStack.splice(indexOfBackstackEntry, 1); // console.log(`[frame.remove] backStackLengthBefore ${backStackLengthBefore} => backStackLengthAfter ${(frame as unknown as TNSFramePrivate)._backStack.length}`); // console.log(`[frame.remove.done] backstack is now: [${(frame as unknown as TNSFramePrivate)._backStack.map(entry => entry.resolvedPage)}]`); } else { /* There's actually valid reason to no-op here: * We might simply be trying to pop a child page in response to a native pop having occurred. */ // console.log(`[frame.remove] Didn't find a backStackEntry for ${child} at so it must have been handled by Core already. No-op. `); // console.log(`[frame.remove] [${parent} x ${child}] = [${parent.childNodes}] via no-op on non-currentEntry page; stackDepth ${parent.meta.stackDepth} -> ${Math.max(0, parent.meta.stackDepth - 1)}`); // console.log(`[frame.remove.done] backstack is now: [${(frame as unknown as TNSFramePrivate)._backStack.map(entry => entry.resolvedPage)}]`); } parent.meta.stackDepth = Math.max(0, parent.meta.stackDepth - 1); return; } } } }); registerElement('page', () => require('@nativescript/core').Page, { viewFlags: 2 /* NSVViewFlags.CONTENT_VIEW */, nodeOps: { insert(child, parent, atIndex) { const page = parent.nativeView; if (typeof atIndex === "number" && atIndex > 0) { if (__DEV__) { warn(`Cannot add child "${child.nativeView.constructor.name}" to Page, as Page only accepts a single child. ` + `If you wish to add more than one child to a Page, wrap them in a LayoutBase like <stackLayout>.`); } return; } if (child.nodeRole === "actionBar" || child.nativeView instanceof TNSActionBar) { page.actionBar = child.nativeView; } else { page.content = child.nativeView; } }, remove(child, parent) { const page = parent.nativeView; if (child.nodeRole === "actionBar" || child.nativeView instanceof TNSActionBar) { /* Well we could technically do this, but it'd be better to just teach good practices. */ // page.actionBar = new TNSActionBar(); if (__DEV__) { warn(`Unable to remove ActionBar from Page; not supported by NativeScript Core. ` + `You may prefer to set page.actionBarHidden = true instead.`); } } else { page.content = null; } } } }); // html registerElement('htmlView', () => require('@nativescript/core').HtmlView); registerElement('webView', () => require('@nativescript/core').WebView); // components registerElement('activityIndicator', () => require('@nativescript/core').ActivityIndicator); registerElement('button', () => require('@nativescript/core').Button); registerElement('datePicker', () => require('@nativescript/core').DatePicker, { model: { prop: 'date', event: 'dateChange' } }); registerElement('formattedString', () => require('@nativescript/core').FormattedString, { // todo: move into FormattedString.ts when we end up creating a component for FormattedString /** * The default parentView._addChildFromBuilder() seems to handle the case of pushing a new child in. * It might even handle the case of splicing a child into place. I wasn't able to check. */ nodeOps: { insert(child, parent, atIndex) { const formattedString = parent.nativeView; if (child.nativeView instanceof TNSSpan) { const spansLength = formattedString.spans.length; formattedString.spans.splice(typeof atIndex === "undefined" ? spansLength : atIndex, 0, child.nativeView); } else { if (__DEV__) { warn(`<formattedString> must only contain <span> elements - ` + `got <${child.nativeView.constructor.name}> instead.`); } } }, /** * The default parentView._removeView() crashes upon removing a span. * This implementation addresses that. */ remove(child, parent) { const formattedString = parent.nativeView; if (child.nativeView instanceof TNSSpan) { const childIndex = formattedString.spans.indexOf(child.nativeView); formattedString.spans.splice(childIndex, 1); } else { if (__DEV__) { warn(`<formattedString> must only contain <span> elements - ` + `got <${child.nativeView.constructor.name}> instead.`); } } } } }); registerElement('image', () => require('@nativescript/core').Image); registerElement('label', () => require('@nativescript/core').Label); registerElement('listPicker', () => require('@nativescript/core').ListPicker, { model: { prop: 'selectedIndex', event: 'selectedIndexChange' } }); registerElement('listView', () => require('@nativescript/core').ListView, { viewFlags: 8 /* NSVViewFlags.NO_CHILDREN */ }); registerElement('placeholder', () => require('@nativescript/core').Placeholder); registerElement('progress', () => require('@nativescript/core').Progress); registerElement('searchBar', () => require('@nativescript/core').SearchBar, { model: { prop: 'text', event: 'textChange', } }); registerElement('segmentedBar', () => require('@nativescript/core').SegmentedBar, { model: { prop: 'selectedIndex', event: 'selectedIndexChange', } }); registerElement('segmentedBarItem', () => require('@nativescript/core').SegmentedBarItem); registerElement('slider', () => require('@nativescript/core').Slider, { model: { prop: 'value', event: 'valueChange', } }); registerElement('span', () => require('@nativescript/core').Span); registerElement('switch', () => require('@nativescript/core').Switch, { model: { prop: 'checked', event: 'checkedChange', } }); registerElement('textField', () => require('@nativescript/core').TextField, { model: { prop: 'text', event: 'textChange', } }); registerElement('textView', () => require('@nativescript/core').TextView, { model: { prop: 'text', event: 'textChange', }, }); registerElement('timePicker', () => require('@nativescript/core').TimePicker, { model: { prop: 'time', event: 'timeChange', }, }); registerElement('tabView', () => require('@nativescript/core').TabView, { nodeOps: { insert(child, parent, atIndex) { const tabView = parent.nativeView; if (child.nodeRole === "items") { if (child.nativeView instanceof TNSTabViewItem === false) { if (__DEV__) { warn(`Unable to add child "${child.nativeView.constructor.name}" to the items of <tabView> as it is not an instance of TabViewItem.`); } ; return; } const items = tabView.items || []; // Annoyingly, it's the consumer's responsibility to ensure there's an array there! if (typeof atIndex === "undefined" || atIndex === items.length) { tabView.items = items.concat(child.nativeView); } else { tabView.items = items.slice().splice(atIndex, 0, child.nativeView); } } else if (child.nodeRole === "item") { if (__DEV__) { warn(`Unable to add child "${child.nativeView.constructor.name}" to <tabView> as it had the nodeRole "item"; please correct it to "items".`); } } else { if (__DEV__) { warn(`Unable to add child "${child.nativeView.constructor.name}" to <tabView> as it does not have a nodeRole specified; ` + `please set a nodeRole of "items".`); } } }, remove(child, parent) { const tabView = parent.nativeView; if (child.nodeRole === "items") { tabView.items = (tabView.items || []).filter(i => i !== child.nativeView); } else if (child.nodeRole === "item") { if (__DEV__) { warn(`Unable to remove child "${child.nativeView.constructor.name}" from <tabView> as it had the nodeRole "item"; please correct it to "items".`); } } else { if (__DEV__) { warn(`Unable to add child "${child.nativeView.constructor.name}" to <tabView> as it does not have a nodeRole specified; ` + `please set a nodeRole of "items"`); } } } } }); registerElement('tabViewItem', () => require('@nativescript/core').TabViewItem, { nodeOps: { insert(child, parent, atIndex) { const tabViewItem = parent.nativeView; if (child.nativeView instanceof TNSView) { tabViewItem.view = child.nativeView; } else { if (__DEV__) { warn(`Unable to add child "${child.nativeView.constructor.name}" as the view of <tabViewItem> as it is not an instance of View.`); } } }, remove(child, parent) { const tabViewItem = parent.nativeView; // tabViewItem.view = null; // Anything falsy should work. if (__DEV__) { warn(`Unable to remove child "${child.nativeView.constructor.name}" from <tabViewItem> as NativeScript Core does not support it; ` + `please re-use and modify the existing TabViewItem instead.`); } } } }); } // todo: more //# sourceMappingURL=registry.js.map