@rdlabo/capacitor-native-ui 1.2.1 → 2.0.0-beta.pr304.sha56daf151ad80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -26
- package/dist/core.d.ts +2 -2
- package/dist/native/activation.d.ts.map +1 -1
- package/dist/native/activation.js +19 -19
- package/dist/native/activation.js.map +1 -1
- package/dist/native/definitions.d.ts +22 -40
- package/dist/native/definitions.d.ts.map +1 -1
- package/dist/native/html.d.ts.map +1 -1
- package/dist/native/html.js +0 -1
- package/dist/native/html.js.map +1 -1
- package/dist/native/index.d.ts +2 -2
- package/dist/native/index.d.ts.map +1 -1
- package/dist/native/index.js +2 -11
- package/dist/native/index.js.map +1 -1
- package/dist/native/overlays/controller.js +1 -1
- package/dist/native/overlays/controller.js.map +1 -1
- package/dist/native/prehide.js +1 -1
- package/dist/native/prehide.js.map +1 -1
- package/dist/native/runtime.d.ts +1 -1
- package/dist/native/runtime.d.ts.map +1 -1
- package/dist/native/runtime.js +8 -20
- package/dist/native/runtime.js.map +1 -1
- package/dist/native/shared/candidate.d.ts +2 -2
- package/dist/native/shared/candidate.d.ts.map +1 -1
- package/dist/native/shared/candidate.js +2 -2
- package/dist/native/shared/candidate.js.map +1 -1
- package/dist/native/shared/control-options.d.ts +6 -0
- package/dist/native/shared/control-options.d.ts.map +1 -0
- package/dist/native/shared/control-options.js +17 -0
- package/dist/native/shared/control-options.js.map +1 -0
- package/dist/native/shared/toolbar-options.d.ts +6 -2
- package/dist/native/shared/toolbar-options.d.ts.map +1 -1
- package/dist/native/shared/toolbar-options.js +7 -15
- package/dist/native/shared/toolbar-options.js.map +1 -1
- package/dist/native/startup.d.ts.map +1 -1
- package/dist/native/startup.js +0 -16
- package/dist/native/startup.js.map +1 -1
- package/dist/native/vertical-bars-web.d.ts.map +1 -1
- package/dist/native/vertical-bars-web.js +3 -2
- package/dist/native/vertical-bars-web.js.map +1 -1
- package/dist/vertical-bars.d.ts +2 -2
- package/docs/configuration.md +94 -72
- package/docs/framework-independent.md +11 -5
- package/docs/migration.md +62 -84
- package/docs/native-ui-extensions.md +31 -28
- package/docs/native-ui.md +34 -50
- package/docs/setup.md +34 -22
- package/docs/vertical-bars.md +59 -65
- package/ios/Sources/NativeUIPlugin/NativeUIPlugin.swift +1 -1
- package/package.json +1 -1
- package/src/core.ts +1 -2
- package/src/native/activation.ts +13 -22
- package/src/native/definitions.ts +40 -90
- package/src/native/html.ts +0 -5
- package/src/native/index.ts +2 -11
- package/src/native/overlays/controller.ts +1 -1
- package/src/native/prehide.ts +1 -1
- package/src/native/runtime.ts +7 -16
- package/src/native/shared/candidate.ts +4 -4
- package/src/native/shared/control-options.ts +20 -0
- package/src/native/shared/toolbar-options.ts +8 -12
- package/src/native/startup.ts +0 -16
- package/src/native/vertical-bars-web.ts +4 -2
- package/src/vertical-bars.ts +2 -1
- package/docs/native-mini-player.md +0 -47
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"startup.js","names":["activate"],"sources":["../../src/native/startup.ts"],"sourcesContent":["import { enableNativeUI as activate } from './activation';\nimport type { NativeUIRuntimeOptions, NativeUIHandle, NativeUIOptions, NativeUIRuntimeHandle } from './definitions';\n\ntype OverlayFactory = Parameters<typeof activate>[1];\nconst handles = new WeakMap<NativeUIRuntimeHandle, NativeUIHandle>();\n\n/** Start one shell. Partial options override defaults; markup owns individual placement. */\nexport const startNativeUI = async (input: NativeUIOptions = {}, overlayFactory?: OverlayFactory): Promise<NativeUIHandle> => {\n const root = input.root ?? (typeof document === 'undefined' ? undefined : document.body);\n const bars = { enabled: true, ...input.verticalBars };\n const host = bars.placement?.webHost;\n if (root && (!root.isConnected || root.ownerDocument !== document))\n throw new Error('Native UI requires a connected root in the current document');\n if (host && (!host.isConnected || host.ownerDocument !== document || root?.contains(host) || host.contains(root!)))\n throw new Error('The Web rail host must be connected in the current document and outside the source root');\n const
|
|
1
|
+
{"version":3,"file":"startup.js","names":["activate"],"sources":["../../src/native/startup.ts"],"sourcesContent":["import { enableNativeUI as activate } from './activation';\nimport type { NativeUIRuntimeOptions, NativeUIHandle, NativeUIOptions, NativeUIRuntimeHandle } from './definitions';\n\ntype OverlayFactory = Parameters<typeof activate>[1];\nconst handles = new WeakMap<NativeUIRuntimeHandle, NativeUIHandle>();\n\n/** Start one shell. Partial options override defaults; markup owns individual placement. */\nexport const startNativeUI = async (input: NativeUIOptions = {}, overlayFactory?: OverlayFactory): Promise<NativeUIHandle> => {\n const root = input.root ?? (typeof document === 'undefined' ? undefined : document.body);\n const bars = { enabled: true, ...input.verticalBars };\n const host = bars.placement?.webHost;\n if (root && (!root.isConnected || root.ownerDocument !== document))\n throw new Error('Native UI requires a connected root in the current document');\n if (host && (!host.isConnected || host.ownerDocument !== document || root?.contains(host) || host.contains(root!)))\n throw new Error('The Web rail host must be connected in the current document and outside the source root');\n const options: NativeUIRuntimeOptions = {\n ...input,\n root,\n source: 'auto',\n verticalBars: bars,\n rail: host ? { host, edge: bars.placement?.edge === 'leading' ? 'left' : 'right' } : undefined,\n };\n const runtime = await activate(options, overlayFactory);\n const existing = handles.get(runtime);\n if (existing) return existing;\n let stopped = false;\n let queue = Promise.resolve();\n const change = (enabled: boolean) => {\n const operation = queue.then(async () => {\n if (stopped) throw new Error('Native UI has been destroyed');\n if (bars.enabled === enabled) return;\n // Retire both surfaces before changing ownership. Resume also waits for native reconciliation.\n const lease = await runtime.suspend();\n try {\n if (stopped) throw new Error('Native UI has been destroyed');\n bars.enabled = enabled;\n root?.ownerDocument.defaultView?.dispatchEvent(new Event('nativeUIRefresh'));\n } finally {\n await lease.resume();\n }\n });\n queue = operation.catch(() => {});\n return operation;\n };\n let destruction: Promise<void> | undefined;\n const destroy = runtime.destroy.bind(runtime);\n const handle: NativeUIHandle = {\n markForCheck: runtime.markForCheck.bind(runtime),\n getStatus: runtime.getStatus.bind(runtime),\n suspend: runtime.suspend.bind(runtime),\n enableVerticalControlArea: () => change(true),\n disableVerticalControlArea: () => change(false),\n destroy() {\n if (!destruction) {\n stopped = true;\n // Reserve native teardown immediately, preserving the shared startup queue.\n destruction = destroy();\n }\n return destruction;\n },\n };\n const managed = Object.assign(runtime, handle);\n handles.set(runtime, managed);\n return managed;\n};\n\n/** Enable the shared standard shell without loading the optional Ionic overlay adapter. */\nexport const enableNativeUI = (options: NativeUIOptions = {}): Promise<NativeUIHandle> => startNativeUI(options);\n"],"mappings":";;AAIA,MAAM,0BAAU,IAAI,QAA+C;;AAGnE,MAAa,gBAAgB,OAAO,QAAyB,CAAC,GAAG,mBAA6D;CAC5H,MAAM,OAAO,MAAM,SAAS,OAAO,aAAa,cAAc,KAAA,IAAY,SAAS;CACnF,MAAM,OAAO;EAAE,SAAS;EAAM,GAAG,MAAM;CAAa;CACpD,MAAM,OAAO,KAAK,WAAW;CAC7B,IAAI,SAAS,CAAC,KAAK,eAAe,KAAK,kBAAkB,WACvD,MAAM,IAAI,MAAM,6DAA6D;CAC/E,IAAI,SAAS,CAAC,KAAK,eAAe,KAAK,kBAAkB,YAAY,MAAM,SAAS,IAAI,KAAK,KAAK,SAAS,IAAK,IAC9G,MAAM,IAAI,MAAM,yFAAyF;CAC3G,MAAM,UAAkC;EACtC,GAAG;EACH;EACA,QAAQ;EACR,cAAc;EACd,MAAM,OAAO;GAAE;GAAM,MAAM,KAAK,WAAW,SAAS,YAAY,SAAS;EAAQ,IAAI,KAAA;CACvF;CACA,MAAM,UAAU,MAAMA,iBAAS,SAAS,cAAc;CACtD,MAAM,WAAW,QAAQ,IAAI,OAAO;CACpC,IAAI,UAAU,OAAO;CACrB,IAAI,UAAU;CACd,IAAI,QAAQ,QAAQ,QAAQ;CAC5B,MAAM,UAAU,YAAqB;EACnC,MAAM,YAAY,MAAM,KAAK,YAAY;GACvC,IAAI,SAAS,MAAM,IAAI,MAAM,8BAA8B;GAC3D,IAAI,KAAK,YAAY,SAAS;GAE9B,MAAM,QAAQ,MAAM,QAAQ,QAAQ;GACpC,IAAI;IACF,IAAI,SAAS,MAAM,IAAI,MAAM,8BAA8B;IAC3D,KAAK,UAAU;IACf,MAAM,cAAc,aAAa,cAAc,IAAI,MAAM,iBAAiB,CAAC;GAC7E,UAAU;IACR,MAAM,MAAM,OAAO;GACrB;EACF,CAAC;EACD,QAAQ,UAAU,YAAY,CAAC,CAAC;EAChC,OAAO;CACT;CACA,IAAI;CACJ,MAAM,UAAU,QAAQ,QAAQ,KAAK,OAAO;CAC5C,MAAM,SAAyB;EAC7B,cAAc,QAAQ,aAAa,KAAK,OAAO;EAC/C,WAAW,QAAQ,UAAU,KAAK,OAAO;EACzC,SAAS,QAAQ,QAAQ,KAAK,OAAO;EACrC,iCAAiC,OAAO,IAAI;EAC5C,kCAAkC,OAAO,KAAK;EAC9C,UAAU;GACR,IAAI,CAAC,aAAa;IAChB,UAAU;IAEV,cAAc,QAAQ;GACxB;GACA,OAAO;EACT;CACF;CACA,MAAM,UAAU,OAAO,OAAO,SAAS,MAAM;CAC7C,QAAQ,IAAI,SAAS,OAAO;CAC5B,OAAO;AACT;;AAGA,MAAa,kBAAkB,UAA2B,CAAC,MAA+B,cAAc,OAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vertical-bars-web.d.ts","names":[],"sources":["../../src/native/vertical-bars-web.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"vertical-bars-web.d.ts","names":[],"sources":["../../src/native/vertical-bars-web.ts"],"mappings":";;qBA6Ca,kCACX,KAAK,UACL,SAAS,iBACT,4BACC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { controlSelected } from "./shared/control-options.js";
|
|
1
2
|
import { inVerticalBarsSurface, topModal } from "./shared/modal.js";
|
|
2
3
|
import { createCrossfade } from "./shared/crossfade.js";
|
|
3
4
|
import { activateProjectedElement, createVerticalBarsPageState, isExcluded, isNativeUIDisabled, isVerticalBarsToolbarGroup, marker, preferredVerticalBarsBack, prehiddenClass, prehideOnlyMutation, syncToolbarText, unprojected, verticalBarsActionCandidate, verticalBarsBackCandidate, verticalBarsEnteringPage, verticalBarsOwned, verticalBarsToolbarActions, withoutPrehide } from "./shared/dom.js";
|
|
@@ -14,7 +15,7 @@ const createVerticalBarsWebProjection = (doc, options, enabled = () => true) =>
|
|
|
14
15
|
const win = doc.defaultView;
|
|
15
16
|
const crossfade = createCrossfade(win);
|
|
16
17
|
let incomingInstant;
|
|
17
|
-
const toolbarEnabled = options.
|
|
18
|
+
const toolbarEnabled = options.ionic?.auto !== false;
|
|
18
19
|
let root;
|
|
19
20
|
let backSource;
|
|
20
21
|
let backProjection;
|
|
@@ -40,7 +41,7 @@ const createVerticalBarsWebProjection = (doc, options, enabled = () => true) =>
|
|
|
40
41
|
const rect = element.getBoundingClientRect();
|
|
41
42
|
return element.isConnected && style.display !== "none" && style.visibility === "visible" && rect.width > 0 && rect.height > 0;
|
|
42
43
|
});
|
|
43
|
-
const search = createVerticalBarsWebSearch(doc, (element) =>
|
|
44
|
+
const search = createVerticalBarsWebSearch(doc, (element) => options.ionic?.auto !== false && controlSelected(element, options, "tabs", true) && !!verticalBarsRoot()?.contains(element) && (!options.root || options.root.contains(element)) && !element.closest("[data-native-ui-placement=\"element\"]") && inVerticalBarsSurface(element) && !isExcluded(element, verticalBarsEnteringPage(element)) && !isNativeUIDisabled(element) && !verticalBarsPages.isDeparted(element) && isRendered(element), () => {
|
|
44
45
|
schedule();
|
|
45
46
|
});
|
|
46
47
|
const inEligibleToolbar = (element) => !!verticalBarsRoot()?.contains(element) && (!options.root || options.root.contains(element)) && !element.closest("[data-native-ui-placement=\"element\"]") && inVerticalBarsSurface(element) && verticalBarsActionCandidate(element) && !isExcluded(element, verticalBarsEnteringPage(element)) && !verticalBarsPages.isDeparted(element);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vertical-bars-web.js","names":[],"sources":["../../src/native/vertical-bars-web.ts"],"sourcesContent":["import { createCrossfade, fadeMarker } from './shared/crossfade';\nimport { alignVerticalBarClone, verticalBarFrame } from './shared/vertical-bars-geometry';\nimport { createVerticalBarsWebSearch, searchProjectionClass } from './vertical-bars-web-search';\nimport { inVerticalBarsSurface, topModal } from './shared/modal';\nimport type { NativeUIRuntimeHandle, NativeUIOptions, NativeUIStatus } from './definitions';\nimport { VERTICAL_BARS_TRANSITION_CANCELED } from '../native-integration';\nimport {\n activateProjectedElement,\n createVerticalBarsPageState,\n verticalBarsActionCandidate,\n verticalBarsBackCandidate,\n verticalBarsEnteringPage,\n verticalBarsToolbarActions,\n isExcluded,\n isNativeUIDisabled,\n isVerticalBarsToolbarGroup,\n preferredVerticalBarsBack,\n verticalBarsOwned,\n marker,\n syncToolbarText,\n toolbarTextMarker,\n prehideOnlyMutation,\n prehiddenClass,\n unprojected,\n withoutPrehide,\n} from './shared/dom';\n\nconst backProjectionClass = 'ios-theme-vertical-bars-back-button-projection';\nconst toolbarProjectionClass = 'ios-theme-vertical-bars-toolbar-projection';\nconst readyClass = 'ios-theme-vertical-bars-toolbar-ready';\nconst toolbarControlSize = 46;\nconst toolbarControlGap = 10;\n\ninterface ToolbarProjection {\n source: HTMLElement;\n projection: HTMLElement;\n actions: { source: HTMLElement; projection: HTMLElement; previousAriaHidden: string | null }[];\n}\n\ninterface ToolbarSource {\n group: HTMLElement;\n actions: HTMLElement[];\n}\n\nexport const createVerticalBarsWebProjection = (\n doc: Document,\n options: NativeUIOptions,\n enabled: () => boolean = () => true,\n): NativeUIRuntimeHandle => {\n const win = doc.defaultView!;\n const crossfade = createCrossfade(win);\n let incomingInstant: boolean | undefined;\n const toolbarEnabled = options.controls === undefined || options.controls.toolbar === true;\n let root: HTMLElement | undefined;\n let backSource: HTMLIonBackButtonElement | undefined;\n let backProjection: HTMLIonBackButtonElement | undefined;\n let previousBackAriaHidden: string | null = null;\n let toolbarProjections: ToolbarProjection[] = [];\n let suspended = 0;\n let stopped = false;\n let frame = 0;\n let updates = 0;\n let observingVerticalBars = false;\n const verticalBarsPages = createVerticalBarsPageState();\n let sourceObserver: MutationObserver | undefined;\n let waiters: (() => void)[] = [];\n const listeners = new AbortController();\n const verticalBarsRoot = () => doc.querySelector<HTMLElement>('ion-app.ios-theme-vertical-bars');\n const projectedSources = () =>\n [backSource, search.source, ...toolbarProjections.flatMap(({ actions }) => actions.map(({ source }) => source))].filter(\n (source): source is HTMLElement => !!source,\n );\n const isRendered = (element: HTMLElement) =>\n withoutPrehide(element, () => {\n const style = getComputedStyle(element);\n const rect = element.getBoundingClientRect();\n return element.isConnected && style.display !== 'none' && style.visibility === 'visible' && rect.width > 0 && rect.height > 0;\n });\n const search = createVerticalBarsWebSearch(\n doc,\n (element) =>\n (options.controls === undefined || options.controls.tabs === true) &&\n !!verticalBarsRoot()?.contains(element) &&\n (!options.root || options.root.contains(element)) &&\n !element.closest('[data-native-ui-placement=\"element\"]') &&\n inVerticalBarsSurface(element) &&\n !isExcluded(element, verticalBarsEnteringPage(element)) &&\n !isNativeUIDisabled(element) &&\n !verticalBarsPages.isDeparted(element) &&\n isRendered(element),\n () => {\n void schedule();\n },\n );\n const inEligibleToolbar = (element: HTMLElement) =>\n !!verticalBarsRoot()?.contains(element) &&\n (!options.root || options.root.contains(element)) &&\n !element.closest('[data-native-ui-placement=\"element\"]') &&\n inVerticalBarsSurface(element) &&\n verticalBarsActionCandidate(element) &&\n !isExcluded(element, verticalBarsEnteringPage(element)) &&\n !verticalBarsPages.isDeparted(element);\n const isEligibleBack = (element: HTMLIonBackButtonElement) =>\n !!verticalBarsRoot()?.contains(element) &&\n (!options.root || options.root.contains(element)) &&\n !element.closest('[data-native-ui-placement=\"element\"]') &&\n inVerticalBarsSurface(element) &&\n verticalBarsOwned(element) &&\n verticalBarsBackCandidate(element) &&\n !isExcluded(element, verticalBarsEnteringPage(element)) &&\n !verticalBarsPages.isDeparted(element) &&\n unprojected(projectedSources(), () => isRendered(element));\n const pageOrder = (element: Element) => Array.from(doc.querySelectorAll('.ion-page')).indexOf(element.closest('.ion-page')!);\n const findBack = () => {\n const candidates = Array.from(doc.querySelectorAll<HTMLIonBackButtonElement>(`ion-back-button:not(.${backProjectionClass})`)).filter(\n isEligibleBack,\n );\n return preferredVerticalBarsBack(candidates, doc);\n };\n const findToolbarGroups = () => {\n const candidates = Array.from(doc.querySelectorAll<HTMLIonButtonsElement>(`ion-buttons:not(.${toolbarProjectionClass})`)).flatMap(\n (group): ToolbarSource[] => {\n const actions = verticalBarsToolbarActions(group).filter((action) => unprojected(projectedSources(), () => isRendered(action)));\n if (!actions.length) return [];\n if (!isVerticalBarsToolbarGroup(group)) return actions.filter(inEligibleToolbar).map((action) => ({ group, actions: [action] }));\n return inEligibleToolbar(group) && unprojected(projectedSources(), () => isRendered(group)) ? [{ group, actions }] : [];\n },\n );\n for (const action of Array.from(doc.querySelectorAll<HTMLElement>('ion-button, ion-menu-button'))) {\n if (action.closest('ion-buttons') || !verticalBarsOwned(action) || !inEligibleToolbar(action)) continue;\n if (unprojected(projectedSources(), () => isRendered(action))) candidates.push({ group: action, actions: [action] });\n }\n const highestPage = Math.max(...candidates.map(({ group }) => pageOrder(group)));\n return candidates.filter(({ group }) => pageOrder(group) === highestPage);\n };\n const isCurrentToolbarAction = (source: HTMLElement) => findToolbarGroups().some(({ actions }) => actions.includes(source));\n const restore = (retire?: (surface: HTMLElement) => void) => {\n search.restore(retire);\n if (backProjection) {\n if (retire) retire(backProjection);\n else backProjection.remove();\n }\n backProjection = undefined;\n if (backSource) {\n backSource.removeAttribute(marker);\n if (previousBackAriaHidden === null) backSource.removeAttribute('aria-hidden');\n else backSource.setAttribute('aria-hidden', previousBackAriaHidden);\n backSource.dispatchEvent(new CustomEvent('nativeUIChange'));\n }\n backSource = undefined;\n previousBackAriaHidden = null;\n for (const { projection, actions } of toolbarProjections) {\n if (retire) retire(projection);\n else projection.remove();\n for (const { source, previousAriaHidden } of actions) {\n source.removeAttribute(marker);\n if (previousAriaHidden === null) source.removeAttribute('aria-hidden');\n else source.setAttribute('aria-hidden', previousAriaHidden);\n source.dispatchEvent(new CustomEvent('nativeUIChange'));\n }\n }\n toolbarProjections = [];\n sourceObserver?.disconnect();\n sourceObserver = undefined;\n root?.classList.remove(readyClass);\n root = undefined;\n };\n const copyAttributes = (target: HTMLElement, original: HTMLElement) => {\n for (const name of ['id', 'slot', marker, 'aria-hidden']) target.removeAttribute(name);\n for (const attribute of Array.from(target.attributes))\n if (!['class', marker, fadeMarker].includes(attribute.name) && !original.hasAttribute(attribute.name))\n target.removeAttribute(attribute.name);\n for (const attribute of Array.from(original.attributes))\n if (!['id', 'slot', marker, 'aria-hidden'].includes(attribute.name)) target.setAttribute(attribute.name, attribute.value);\n target.classList.remove(prehiddenClass);\n };\n const syncBack = (target: HTMLIonBackButtonElement, original: HTMLIonBackButtonElement) => {\n copyAttributes(target, original);\n target.classList.add(backProjectionClass, 'ion-cloned-element');\n target.disabled = original.disabled;\n target.defaultHref = original.defaultHref;\n target.icon = original.icon;\n target.color = original.color;\n target.mode = original.mode;\n target.text = '';\n if (root) alignVerticalBarClone(target, original, root);\n };\n const syncAction = (target: HTMLElement, original: HTMLElement) => {\n copyAttributes(target, original);\n target.classList.add('ios-theme-vertical-bars-toolbar-action', 'ion-cloned-element');\n const label =\n original.getAttribute('aria-label') ??\n original.shadowRoot?.querySelector('[part~=native]')?.getAttribute('aria-label') ??\n original.textContent?.trim();\n if (label) target.setAttribute('aria-label', label);\n if ('disabled' in original) (target as HTMLIonButtonElement).disabled = (original as HTMLIonButtonElement).disabled;\n if (original.matches('ion-button')) {\n const icon = original.querySelector<HTMLElement>('ion-icon, svg');\n const clonedIcon = icon?.cloneNode(true) as HTMLElement | undefined;\n clonedIcon?.setAttribute('slot', 'icon-only');\n target.replaceChildren(...(clonedIcon ? [clonedIcon] : [document.createTextNode(original.textContent?.trim() || label || '')]));\n }\n };\n const sameSources = (back: HTMLIonBackButtonElement | undefined, groups: ToolbarSource[]) =>\n back === backSource &&\n groups.length === toolbarProjections.length &&\n groups.every(({ group, actions }, index) => {\n const current = toolbarProjections[index];\n return (\n group === current.source &&\n actions.length === current.actions.length &&\n actions.every((action, actionIndex) => action === current.actions[actionIndex].source)\n );\n });\n const syncExisting = () => {\n if (backSource && backProjection) syncBack(backProjection, backSource);\n let topOffset = backSource ? toolbarControlSize + toolbarControlGap : 0;\n for (const { projection, source, actions } of toolbarProjections) {\n if (actions.length === 1 && !projection.matches('ion-buttons')) syncAction(projection, actions[0].source);\n else copyAttributes(projection, source);\n projection.classList.add(toolbarProjectionClass, 'ion-cloned-element');\n projection.style.setProperty('--ios-theme-vertical-bars-toolbar-offset', `${topOffset}px`);\n if (projection.matches('ion-buttons')) actions.forEach(({ source: action, projection: clone }) => syncAction(clone, action));\n const frames = actions.map(({ source }) => verticalBarFrame(source));\n if (root && frames.every((frame) => !!frame)) {\n if (actions.length === 1) alignVerticalBarClone(projection, actions[0].source, root);\n else {\n const left = Math.min(...frames.map((frame) => frame!.x));\n const top = Math.min(...frames.map((frame) => frame!.y));\n const right = Math.max(...frames.map((frame) => frame!.x + frame!.width));\n const bottom = Math.max(...frames.map((frame) => frame!.y + frame!.height));\n const origin = root.getBoundingClientRect();\n Object.assign(projection.style, {\n left: `${left - origin.left}px`,\n top: `${top - origin.top}px`,\n right: 'auto',\n bottom: 'auto',\n width: `${right - left}px`,\n height: `${bottom - top}px`,\n padding: '0',\n });\n actions.forEach(({ source, projection: clone }) => alignVerticalBarClone(clone, source, projection));\n }\n }\n topOffset += actions.length * toolbarControlSize + toolbarControlGap;\n }\n };\n const project = (nextBack: HTMLIonBackButtonElement | undefined, groups: ToolbarSource[]) => {\n root = topModal(doc) ?? verticalBarsRoot()!;\n let topOffset = 0;\n if (nextBack) {\n backSource = nextBack;\n previousBackAriaHidden = nextBack.getAttribute('aria-hidden');\n backProjection = nextBack.cloneNode(false) as HTMLIonBackButtonElement;\n syncBack(backProjection, nextBack);\n backProjection.addEventListener(\n 'click',\n (event) => {\n event.preventDefault();\n event.stopImmediatePropagation();\n if (backSource && backSource === findBack()) activateProjectedElement(backSource);\n },\n { capture: true },\n );\n root.append(backProjection);\n nextBack.setAttribute(marker, '');\n nextBack.setAttribute('aria-hidden', 'true');\n nextBack.dispatchEvent(new CustomEvent('nativeUIChange'));\n topOffset = toolbarControlSize + toolbarControlGap;\n }\n for (const { group, actions: sources } of groups) {\n const projection = (sources.length === 1 ? sources[0] : group).cloneNode(false) as HTMLElement;\n if (sources.length === 1) syncAction(projection, sources[0]);\n else copyAttributes(projection, group);\n projection.classList.add(toolbarProjectionClass, 'ion-cloned-element');\n projection.style.setProperty('--ios-theme-vertical-bars-toolbar-offset', `${topOffset}px`);\n const actions = sources.map((source) => {\n const clone = sources.length === 1 ? projection : (source.cloneNode(false) as HTMLElement);\n if (sources.length > 1) syncAction(clone, source);\n clone.addEventListener(\n 'click',\n (event) => {\n event.preventDefault();\n event.stopImmediatePropagation();\n if (isCurrentToolbarAction(source)) activateProjectedElement(source);\n },\n { capture: true },\n );\n if (sources.length > 1) projection.append(clone);\n const previousAriaHidden = source.getAttribute('aria-hidden');\n source.setAttribute(marker, '');\n source.setAttribute('aria-hidden', 'true');\n source.dispatchEvent(new CustomEvent('nativeUIChange'));\n return { source, projection: clone, previousAriaHidden };\n });\n root.append(projection);\n toolbarProjections.push({ source: group, projection, actions });\n topOffset += actions.length * toolbarControlSize + toolbarControlGap;\n }\n syncExisting();\n if (nextBack || toolbarProjections.length) root.classList.add(readyClass);\n sourceObserver = new MutationObserver((records) => {\n if (\n records.some(\n (record) =>\n record.type !== 'attributes' || (![marker, 'aria-hidden'].includes(record.attributeName ?? '') && !prehideOnlyMutation(record)),\n )\n )\n schedule();\n });\n if (nextBack?.shadowRoot)\n sourceObserver.observe(nextBack.shadowRoot, { subtree: true, childList: true, attributes: true, attributeOldValue: true });\n for (const { group } of groups)\n sourceObserver.observe(group, { subtree: true, childList: true, attributes: true, attributeOldValue: true });\n };\n const reveal = () => {\n if (incomingInstant === undefined) return;\n for (const surface of [backProjection, ...toolbarProjections.map(({ projection }) => projection), search.surface])\n if (surface) crossfade.play(surface, false, incomingInstant);\n incomingInstant = undefined;\n };\n const performUpdate = () => {\n frame = 0;\n syncToolbarText(doc);\n const currentRoot = verticalBarsRoot();\n if (observingVerticalBars !== !!currentRoot) {\n observingVerticalBars = !!currentRoot;\n observer.disconnect();\n observer.observe(doc.documentElement, {\n subtree: true,\n childList: true,\n characterData: observingVerticalBars,\n attributes: true,\n attributeOldValue: true,\n attributeFilter: observingVerticalBars ? undefined : ['class'],\n });\n }\n if (stopped || suspended || !currentRoot || !enabled()) return restore();\n const nextBack = toolbarEnabled ? findBack() : undefined;\n const groups = toolbarEnabled ? findToolbarGroups() : [];\n const projectionRoot = topModal(doc) ?? currentRoot;\n search.update(projectionRoot);\n if (!nextBack && !groups.length && !search.source) return restore();\n if (root === (topModal(doc) ?? currentRoot) && sameSources(nextBack, groups)) {\n syncExisting();\n reveal();\n return;\n }\n restore();\n project(nextBack, groups);\n search.update(projectionRoot);\n reveal();\n updates++;\n };\n const update = () => {\n try {\n performUpdate();\n } catch {\n restore();\n } finally {\n const pending = waiters;\n waiters = [];\n pending.forEach((resolve) => resolve());\n }\n };\n const schedule = (): Promise<void> => {\n if (stopped) return Promise.resolve();\n // Release Web ownership before the native runtime can acknowledge its next frame.\n if (!enabled()) restore();\n const done = new Promise<void>((resolve) => waiters.push(resolve));\n if (!frame) frame = win.requestAnimationFrame(update);\n return done;\n };\n const observer = new MutationObserver((records) => {\n const insideProjection = (target: Node) => {\n const element = target instanceof Element ? target : target.parentNode instanceof Element ? target.parentNode : undefined;\n const rootNode = target.getRootNode();\n const shadowHost = rootNode instanceof ShadowRoot ? rootNode.host : undefined;\n return !![element, shadowHost].some((candidate) =>\n candidate?.closest(`.${backProjectionClass}, .${toolbarProjectionClass}, .${searchProjectionClass}`),\n );\n };\n const verticalBarsChanged = records.some(\n (record) =>\n (record.type === 'attributes' && (record.target as Element).matches('ion-app')) ||\n (record.type === 'childList' &&\n Array.from(record.addedNodes).some(\n (node) =>\n node instanceof Element &&\n (node.matches('ion-app.ios-theme-vertical-bars') || !!node.querySelector('ion-app.ios-theme-vertical-bars')),\n )),\n );\n if (\n (observingVerticalBars &&\n records.some(\n (record) =>\n record.attributeName !== marker &&\n record.attributeName !== toolbarTextMarker &&\n !prehideOnlyMutation(record) &&\n !insideProjection(record.target),\n )) ||\n (!observingVerticalBars && verticalBarsChanged && records.some((record) => !prehideOnlyMutation(record)))\n )\n schedule();\n });\n observer.observe(doc.documentElement, {\n subtree: true,\n childList: true,\n attributes: true,\n attributeOldValue: true,\n attributeFilter: ['class'],\n });\n const pageLifecycle = (event: Event) => {\n const page = event.target;\n if (!(page instanceof HTMLElement) || !page.matches('.ion-page')) return;\n if (event.type === VERTICAL_BARS_TRANSITION_CANCELED) {\n const entering = (event as CustomEvent<{ entering?: HTMLElement }>).detail?.entering;\n verticalBarsPages.cancel(entering, page);\n } else verticalBarsPages.lifecycle(event);\n if (verticalBarsRoot()) schedule();\n };\n for (const name of ['ionViewWillEnter', 'ionViewWillLeave', 'ionViewDidEnter', 'ionViewDidLeave'])\n doc.addEventListener(name, pageLifecycle, { capture: true, signal: listeners.signal });\n doc.addEventListener(VERTICAL_BARS_TRANSITION_CANCELED, pageLifecycle, { capture: true, signal: listeners.signal });\n for (const name of ['ionModalWillPresent', 'ionModalDidDismiss'])\n doc.addEventListener(name, schedule, { capture: true, signal: listeners.signal });\n win.addEventListener(\n 'nativeUIVerticalBarsAcquire',\n (event) => {\n const instant = (event as CustomEvent<{ instant: boolean }>).detail?.instant ?? true;\n incomingInstant = undefined;\n restore((surface) => {\n // Keep only the outgoing visual at its last rectangle after Web ownership\n // is released. The native acknowledgement transfers input exactly once.\n const box = surface.getBoundingClientRect();\n surface.style.setProperty('position', 'fixed', 'important');\n surface.style.setProperty('display', 'block', 'important');\n surface.style.setProperty('left', `${box.left}px`, 'important');\n surface.style.setProperty('top', `${box.top}px`, 'important');\n surface.style.setProperty('right', 'auto', 'important');\n surface.style.setProperty('bottom', 'auto', 'important');\n surface.style.setProperty('width', `${box.width}px`, 'important');\n surface.style.setProperty('height', `${box.height}px`, 'important');\n crossfade.retire(surface, instant);\n });\n },\n { signal: listeners.signal },\n );\n win.addEventListener(\n 'nativeUIVerticalBarsRelease',\n (event) => {\n incomingInstant = (event as CustomEvent<{ instant: boolean }>).detail.instant;\n void schedule();\n },\n { signal: listeners.signal },\n );\n win.addEventListener('resize', schedule, { signal: listeners.signal });\n win.addEventListener('nativeUIRefresh', schedule, { signal: listeners.signal });\n schedule();\n\n return {\n markForCheck: () => {\n void schedule();\n },\n getStatus: (): NativeUIStatus => ({\n state: stopped ? 'stopped' : 'web',\n projected:\n Number(!!backSource) + Number(!!search.source) + toolbarProjections.reduce((count, group) => count + group.actions.length, 0),\n updates,\n }),\n async suspend() {\n suspended++;\n restore();\n let resumed = false;\n return {\n async resume() {\n if (resumed) return;\n resumed = true;\n suspended = Math.max(0, suspended - 1);\n if (!suspended) await schedule();\n },\n };\n },\n async destroy() {\n if (stopped) return;\n stopped = true;\n win.cancelAnimationFrame(frame);\n frame = 0;\n waiters.forEach((resolve) => resolve());\n waiters = [];\n observer.disconnect();\n sourceObserver?.disconnect();\n listeners.abort();\n crossfade.destroy();\n restore();\n },\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vertical-bars-web.js","names":[],"sources":["../../src/native/vertical-bars-web.ts"],"sourcesContent":["import { controlSelected } from './shared/control-options';\nimport { createCrossfade, fadeMarker } from './shared/crossfade';\nimport { alignVerticalBarClone, verticalBarFrame } from './shared/vertical-bars-geometry';\nimport { createVerticalBarsWebSearch, searchProjectionClass } from './vertical-bars-web-search';\nimport { inVerticalBarsSurface, topModal } from './shared/modal';\nimport type { NativeUIRuntimeHandle, NativeUIOptions, NativeUIStatus } from './definitions';\nimport { VERTICAL_BARS_TRANSITION_CANCELED } from '../native-integration';\nimport {\n activateProjectedElement,\n createVerticalBarsPageState,\n verticalBarsActionCandidate,\n verticalBarsBackCandidate,\n verticalBarsEnteringPage,\n verticalBarsToolbarActions,\n isExcluded,\n isNativeUIDisabled,\n isVerticalBarsToolbarGroup,\n preferredVerticalBarsBack,\n verticalBarsOwned,\n marker,\n syncToolbarText,\n toolbarTextMarker,\n prehideOnlyMutation,\n prehiddenClass,\n unprojected,\n withoutPrehide,\n} from './shared/dom';\n\nconst backProjectionClass = 'ios-theme-vertical-bars-back-button-projection';\nconst toolbarProjectionClass = 'ios-theme-vertical-bars-toolbar-projection';\nconst readyClass = 'ios-theme-vertical-bars-toolbar-ready';\nconst toolbarControlSize = 46;\nconst toolbarControlGap = 10;\n\ninterface ToolbarProjection {\n source: HTMLElement;\n projection: HTMLElement;\n actions: { source: HTMLElement; projection: HTMLElement; previousAriaHidden: string | null }[];\n}\n\ninterface ToolbarSource {\n group: HTMLElement;\n actions: HTMLElement[];\n}\n\nexport const createVerticalBarsWebProjection = (\n doc: Document,\n options: NativeUIOptions,\n enabled: () => boolean = () => true,\n): NativeUIRuntimeHandle => {\n const win = doc.defaultView!;\n const crossfade = createCrossfade(win);\n let incomingInstant: boolean | undefined;\n const toolbarEnabled = options.ionic?.auto !== false;\n let root: HTMLElement | undefined;\n let backSource: HTMLIonBackButtonElement | undefined;\n let backProjection: HTMLIonBackButtonElement | undefined;\n let previousBackAriaHidden: string | null = null;\n let toolbarProjections: ToolbarProjection[] = [];\n let suspended = 0;\n let stopped = false;\n let frame = 0;\n let updates = 0;\n let observingVerticalBars = false;\n const verticalBarsPages = createVerticalBarsPageState();\n let sourceObserver: MutationObserver | undefined;\n let waiters: (() => void)[] = [];\n const listeners = new AbortController();\n const verticalBarsRoot = () => doc.querySelector<HTMLElement>('ion-app.ios-theme-vertical-bars');\n const projectedSources = () =>\n [backSource, search.source, ...toolbarProjections.flatMap(({ actions }) => actions.map(({ source }) => source))].filter(\n (source): source is HTMLElement => !!source,\n );\n const isRendered = (element: HTMLElement) =>\n withoutPrehide(element, () => {\n const style = getComputedStyle(element);\n const rect = element.getBoundingClientRect();\n return element.isConnected && style.display !== 'none' && style.visibility === 'visible' && rect.width > 0 && rect.height > 0;\n });\n const search = createVerticalBarsWebSearch(\n doc,\n (element) =>\n options.ionic?.auto !== false &&\n controlSelected(element, options, 'tabs', true) &&\n !!verticalBarsRoot()?.contains(element) &&\n (!options.root || options.root.contains(element)) &&\n !element.closest('[data-native-ui-placement=\"element\"]') &&\n inVerticalBarsSurface(element) &&\n !isExcluded(element, verticalBarsEnteringPage(element)) &&\n !isNativeUIDisabled(element) &&\n !verticalBarsPages.isDeparted(element) &&\n isRendered(element),\n () => {\n void schedule();\n },\n );\n const inEligibleToolbar = (element: HTMLElement) =>\n !!verticalBarsRoot()?.contains(element) &&\n (!options.root || options.root.contains(element)) &&\n !element.closest('[data-native-ui-placement=\"element\"]') &&\n inVerticalBarsSurface(element) &&\n verticalBarsActionCandidate(element) &&\n !isExcluded(element, verticalBarsEnteringPage(element)) &&\n !verticalBarsPages.isDeparted(element);\n const isEligibleBack = (element: HTMLIonBackButtonElement) =>\n !!verticalBarsRoot()?.contains(element) &&\n (!options.root || options.root.contains(element)) &&\n !element.closest('[data-native-ui-placement=\"element\"]') &&\n inVerticalBarsSurface(element) &&\n verticalBarsOwned(element) &&\n verticalBarsBackCandidate(element) &&\n !isExcluded(element, verticalBarsEnteringPage(element)) &&\n !verticalBarsPages.isDeparted(element) &&\n unprojected(projectedSources(), () => isRendered(element));\n const pageOrder = (element: Element) => Array.from(doc.querySelectorAll('.ion-page')).indexOf(element.closest('.ion-page')!);\n const findBack = () => {\n const candidates = Array.from(doc.querySelectorAll<HTMLIonBackButtonElement>(`ion-back-button:not(.${backProjectionClass})`)).filter(\n isEligibleBack,\n );\n return preferredVerticalBarsBack(candidates, doc);\n };\n const findToolbarGroups = () => {\n const candidates = Array.from(doc.querySelectorAll<HTMLIonButtonsElement>(`ion-buttons:not(.${toolbarProjectionClass})`)).flatMap(\n (group): ToolbarSource[] => {\n const actions = verticalBarsToolbarActions(group).filter((action) => unprojected(projectedSources(), () => isRendered(action)));\n if (!actions.length) return [];\n if (!isVerticalBarsToolbarGroup(group)) return actions.filter(inEligibleToolbar).map((action) => ({ group, actions: [action] }));\n return inEligibleToolbar(group) && unprojected(projectedSources(), () => isRendered(group)) ? [{ group, actions }] : [];\n },\n );\n for (const action of Array.from(doc.querySelectorAll<HTMLElement>('ion-button, ion-menu-button'))) {\n if (action.closest('ion-buttons') || !verticalBarsOwned(action) || !inEligibleToolbar(action)) continue;\n if (unprojected(projectedSources(), () => isRendered(action))) candidates.push({ group: action, actions: [action] });\n }\n const highestPage = Math.max(...candidates.map(({ group }) => pageOrder(group)));\n return candidates.filter(({ group }) => pageOrder(group) === highestPage);\n };\n const isCurrentToolbarAction = (source: HTMLElement) => findToolbarGroups().some(({ actions }) => actions.includes(source));\n const restore = (retire?: (surface: HTMLElement) => void) => {\n search.restore(retire);\n if (backProjection) {\n if (retire) retire(backProjection);\n else backProjection.remove();\n }\n backProjection = undefined;\n if (backSource) {\n backSource.removeAttribute(marker);\n if (previousBackAriaHidden === null) backSource.removeAttribute('aria-hidden');\n else backSource.setAttribute('aria-hidden', previousBackAriaHidden);\n backSource.dispatchEvent(new CustomEvent('nativeUIChange'));\n }\n backSource = undefined;\n previousBackAriaHidden = null;\n for (const { projection, actions } of toolbarProjections) {\n if (retire) retire(projection);\n else projection.remove();\n for (const { source, previousAriaHidden } of actions) {\n source.removeAttribute(marker);\n if (previousAriaHidden === null) source.removeAttribute('aria-hidden');\n else source.setAttribute('aria-hidden', previousAriaHidden);\n source.dispatchEvent(new CustomEvent('nativeUIChange'));\n }\n }\n toolbarProjections = [];\n sourceObserver?.disconnect();\n sourceObserver = undefined;\n root?.classList.remove(readyClass);\n root = undefined;\n };\n const copyAttributes = (target: HTMLElement, original: HTMLElement) => {\n for (const name of ['id', 'slot', marker, 'aria-hidden']) target.removeAttribute(name);\n for (const attribute of Array.from(target.attributes))\n if (!['class', marker, fadeMarker].includes(attribute.name) && !original.hasAttribute(attribute.name))\n target.removeAttribute(attribute.name);\n for (const attribute of Array.from(original.attributes))\n if (!['id', 'slot', marker, 'aria-hidden'].includes(attribute.name)) target.setAttribute(attribute.name, attribute.value);\n target.classList.remove(prehiddenClass);\n };\n const syncBack = (target: HTMLIonBackButtonElement, original: HTMLIonBackButtonElement) => {\n copyAttributes(target, original);\n target.classList.add(backProjectionClass, 'ion-cloned-element');\n target.disabled = original.disabled;\n target.defaultHref = original.defaultHref;\n target.icon = original.icon;\n target.color = original.color;\n target.mode = original.mode;\n target.text = '';\n if (root) alignVerticalBarClone(target, original, root);\n };\n const syncAction = (target: HTMLElement, original: HTMLElement) => {\n copyAttributes(target, original);\n target.classList.add('ios-theme-vertical-bars-toolbar-action', 'ion-cloned-element');\n const label =\n original.getAttribute('aria-label') ??\n original.shadowRoot?.querySelector('[part~=native]')?.getAttribute('aria-label') ??\n original.textContent?.trim();\n if (label) target.setAttribute('aria-label', label);\n if ('disabled' in original) (target as HTMLIonButtonElement).disabled = (original as HTMLIonButtonElement).disabled;\n if (original.matches('ion-button')) {\n const icon = original.querySelector<HTMLElement>('ion-icon, svg');\n const clonedIcon = icon?.cloneNode(true) as HTMLElement | undefined;\n clonedIcon?.setAttribute('slot', 'icon-only');\n target.replaceChildren(...(clonedIcon ? [clonedIcon] : [document.createTextNode(original.textContent?.trim() || label || '')]));\n }\n };\n const sameSources = (back: HTMLIonBackButtonElement | undefined, groups: ToolbarSource[]) =>\n back === backSource &&\n groups.length === toolbarProjections.length &&\n groups.every(({ group, actions }, index) => {\n const current = toolbarProjections[index];\n return (\n group === current.source &&\n actions.length === current.actions.length &&\n actions.every((action, actionIndex) => action === current.actions[actionIndex].source)\n );\n });\n const syncExisting = () => {\n if (backSource && backProjection) syncBack(backProjection, backSource);\n let topOffset = backSource ? toolbarControlSize + toolbarControlGap : 0;\n for (const { projection, source, actions } of toolbarProjections) {\n if (actions.length === 1 && !projection.matches('ion-buttons')) syncAction(projection, actions[0].source);\n else copyAttributes(projection, source);\n projection.classList.add(toolbarProjectionClass, 'ion-cloned-element');\n projection.style.setProperty('--ios-theme-vertical-bars-toolbar-offset', `${topOffset}px`);\n if (projection.matches('ion-buttons')) actions.forEach(({ source: action, projection: clone }) => syncAction(clone, action));\n const frames = actions.map(({ source }) => verticalBarFrame(source));\n if (root && frames.every((frame) => !!frame)) {\n if (actions.length === 1) alignVerticalBarClone(projection, actions[0].source, root);\n else {\n const left = Math.min(...frames.map((frame) => frame!.x));\n const top = Math.min(...frames.map((frame) => frame!.y));\n const right = Math.max(...frames.map((frame) => frame!.x + frame!.width));\n const bottom = Math.max(...frames.map((frame) => frame!.y + frame!.height));\n const origin = root.getBoundingClientRect();\n Object.assign(projection.style, {\n left: `${left - origin.left}px`,\n top: `${top - origin.top}px`,\n right: 'auto',\n bottom: 'auto',\n width: `${right - left}px`,\n height: `${bottom - top}px`,\n padding: '0',\n });\n actions.forEach(({ source, projection: clone }) => alignVerticalBarClone(clone, source, projection));\n }\n }\n topOffset += actions.length * toolbarControlSize + toolbarControlGap;\n }\n };\n const project = (nextBack: HTMLIonBackButtonElement | undefined, groups: ToolbarSource[]) => {\n root = topModal(doc) ?? verticalBarsRoot()!;\n let topOffset = 0;\n if (nextBack) {\n backSource = nextBack;\n previousBackAriaHidden = nextBack.getAttribute('aria-hidden');\n backProjection = nextBack.cloneNode(false) as HTMLIonBackButtonElement;\n syncBack(backProjection, nextBack);\n backProjection.addEventListener(\n 'click',\n (event) => {\n event.preventDefault();\n event.stopImmediatePropagation();\n if (backSource && backSource === findBack()) activateProjectedElement(backSource);\n },\n { capture: true },\n );\n root.append(backProjection);\n nextBack.setAttribute(marker, '');\n nextBack.setAttribute('aria-hidden', 'true');\n nextBack.dispatchEvent(new CustomEvent('nativeUIChange'));\n topOffset = toolbarControlSize + toolbarControlGap;\n }\n for (const { group, actions: sources } of groups) {\n const projection = (sources.length === 1 ? sources[0] : group).cloneNode(false) as HTMLElement;\n if (sources.length === 1) syncAction(projection, sources[0]);\n else copyAttributes(projection, group);\n projection.classList.add(toolbarProjectionClass, 'ion-cloned-element');\n projection.style.setProperty('--ios-theme-vertical-bars-toolbar-offset', `${topOffset}px`);\n const actions = sources.map((source) => {\n const clone = sources.length === 1 ? projection : (source.cloneNode(false) as HTMLElement);\n if (sources.length > 1) syncAction(clone, source);\n clone.addEventListener(\n 'click',\n (event) => {\n event.preventDefault();\n event.stopImmediatePropagation();\n if (isCurrentToolbarAction(source)) activateProjectedElement(source);\n },\n { capture: true },\n );\n if (sources.length > 1) projection.append(clone);\n const previousAriaHidden = source.getAttribute('aria-hidden');\n source.setAttribute(marker, '');\n source.setAttribute('aria-hidden', 'true');\n source.dispatchEvent(new CustomEvent('nativeUIChange'));\n return { source, projection: clone, previousAriaHidden };\n });\n root.append(projection);\n toolbarProjections.push({ source: group, projection, actions });\n topOffset += actions.length * toolbarControlSize + toolbarControlGap;\n }\n syncExisting();\n if (nextBack || toolbarProjections.length) root.classList.add(readyClass);\n sourceObserver = new MutationObserver((records) => {\n if (\n records.some(\n (record) =>\n record.type !== 'attributes' || (![marker, 'aria-hidden'].includes(record.attributeName ?? '') && !prehideOnlyMutation(record)),\n )\n )\n schedule();\n });\n if (nextBack?.shadowRoot)\n sourceObserver.observe(nextBack.shadowRoot, { subtree: true, childList: true, attributes: true, attributeOldValue: true });\n for (const { group } of groups)\n sourceObserver.observe(group, { subtree: true, childList: true, attributes: true, attributeOldValue: true });\n };\n const reveal = () => {\n if (incomingInstant === undefined) return;\n for (const surface of [backProjection, ...toolbarProjections.map(({ projection }) => projection), search.surface])\n if (surface) crossfade.play(surface, false, incomingInstant);\n incomingInstant = undefined;\n };\n const performUpdate = () => {\n frame = 0;\n syncToolbarText(doc);\n const currentRoot = verticalBarsRoot();\n if (observingVerticalBars !== !!currentRoot) {\n observingVerticalBars = !!currentRoot;\n observer.disconnect();\n observer.observe(doc.documentElement, {\n subtree: true,\n childList: true,\n characterData: observingVerticalBars,\n attributes: true,\n attributeOldValue: true,\n attributeFilter: observingVerticalBars ? undefined : ['class'],\n });\n }\n if (stopped || suspended || !currentRoot || !enabled()) return restore();\n const nextBack = toolbarEnabled ? findBack() : undefined;\n const groups = toolbarEnabled ? findToolbarGroups() : [];\n const projectionRoot = topModal(doc) ?? currentRoot;\n search.update(projectionRoot);\n if (!nextBack && !groups.length && !search.source) return restore();\n if (root === (topModal(doc) ?? currentRoot) && sameSources(nextBack, groups)) {\n syncExisting();\n reveal();\n return;\n }\n restore();\n project(nextBack, groups);\n search.update(projectionRoot);\n reveal();\n updates++;\n };\n const update = () => {\n try {\n performUpdate();\n } catch {\n restore();\n } finally {\n const pending = waiters;\n waiters = [];\n pending.forEach((resolve) => resolve());\n }\n };\n const schedule = (): Promise<void> => {\n if (stopped) return Promise.resolve();\n // Release Web ownership before the native runtime can acknowledge its next frame.\n if (!enabled()) restore();\n const done = new Promise<void>((resolve) => waiters.push(resolve));\n if (!frame) frame = win.requestAnimationFrame(update);\n return done;\n };\n const observer = new MutationObserver((records) => {\n const insideProjection = (target: Node) => {\n const element = target instanceof Element ? target : target.parentNode instanceof Element ? target.parentNode : undefined;\n const rootNode = target.getRootNode();\n const shadowHost = rootNode instanceof ShadowRoot ? rootNode.host : undefined;\n return !![element, shadowHost].some((candidate) =>\n candidate?.closest(`.${backProjectionClass}, .${toolbarProjectionClass}, .${searchProjectionClass}`),\n );\n };\n const verticalBarsChanged = records.some(\n (record) =>\n (record.type === 'attributes' && (record.target as Element).matches('ion-app')) ||\n (record.type === 'childList' &&\n Array.from(record.addedNodes).some(\n (node) =>\n node instanceof Element &&\n (node.matches('ion-app.ios-theme-vertical-bars') || !!node.querySelector('ion-app.ios-theme-vertical-bars')),\n )),\n );\n if (\n (observingVerticalBars &&\n records.some(\n (record) =>\n record.attributeName !== marker &&\n record.attributeName !== toolbarTextMarker &&\n !prehideOnlyMutation(record) &&\n !insideProjection(record.target),\n )) ||\n (!observingVerticalBars && verticalBarsChanged && records.some((record) => !prehideOnlyMutation(record)))\n )\n schedule();\n });\n observer.observe(doc.documentElement, {\n subtree: true,\n childList: true,\n attributes: true,\n attributeOldValue: true,\n attributeFilter: ['class'],\n });\n const pageLifecycle = (event: Event) => {\n const page = event.target;\n if (!(page instanceof HTMLElement) || !page.matches('.ion-page')) return;\n if (event.type === VERTICAL_BARS_TRANSITION_CANCELED) {\n const entering = (event as CustomEvent<{ entering?: HTMLElement }>).detail?.entering;\n verticalBarsPages.cancel(entering, page);\n } else verticalBarsPages.lifecycle(event);\n if (verticalBarsRoot()) schedule();\n };\n for (const name of ['ionViewWillEnter', 'ionViewWillLeave', 'ionViewDidEnter', 'ionViewDidLeave'])\n doc.addEventListener(name, pageLifecycle, { capture: true, signal: listeners.signal });\n doc.addEventListener(VERTICAL_BARS_TRANSITION_CANCELED, pageLifecycle, { capture: true, signal: listeners.signal });\n for (const name of ['ionModalWillPresent', 'ionModalDidDismiss'])\n doc.addEventListener(name, schedule, { capture: true, signal: listeners.signal });\n win.addEventListener(\n 'nativeUIVerticalBarsAcquire',\n (event) => {\n const instant = (event as CustomEvent<{ instant: boolean }>).detail?.instant ?? true;\n incomingInstant = undefined;\n restore((surface) => {\n // Keep only the outgoing visual at its last rectangle after Web ownership\n // is released. The native acknowledgement transfers input exactly once.\n const box = surface.getBoundingClientRect();\n surface.style.setProperty('position', 'fixed', 'important');\n surface.style.setProperty('display', 'block', 'important');\n surface.style.setProperty('left', `${box.left}px`, 'important');\n surface.style.setProperty('top', `${box.top}px`, 'important');\n surface.style.setProperty('right', 'auto', 'important');\n surface.style.setProperty('bottom', 'auto', 'important');\n surface.style.setProperty('width', `${box.width}px`, 'important');\n surface.style.setProperty('height', `${box.height}px`, 'important');\n crossfade.retire(surface, instant);\n });\n },\n { signal: listeners.signal },\n );\n win.addEventListener(\n 'nativeUIVerticalBarsRelease',\n (event) => {\n incomingInstant = (event as CustomEvent<{ instant: boolean }>).detail.instant;\n void schedule();\n },\n { signal: listeners.signal },\n );\n win.addEventListener('resize', schedule, { signal: listeners.signal });\n win.addEventListener('nativeUIRefresh', schedule, { signal: listeners.signal });\n schedule();\n\n return {\n markForCheck: () => {\n void schedule();\n },\n getStatus: (): NativeUIStatus => ({\n state: stopped ? 'stopped' : 'web',\n projected:\n Number(!!backSource) + Number(!!search.source) + toolbarProjections.reduce((count, group) => count + group.actions.length, 0),\n updates,\n }),\n async suspend() {\n suspended++;\n restore();\n let resumed = false;\n return {\n async resume() {\n if (resumed) return;\n resumed = true;\n suspended = Math.max(0, suspended - 1);\n if (!suspended) await schedule();\n },\n };\n },\n async destroy() {\n if (stopped) return;\n stopped = true;\n win.cancelAnimationFrame(frame);\n frame = 0;\n waiters.forEach((resolve) => resolve());\n waiters = [];\n observer.disconnect();\n sourceObserver?.disconnect();\n listeners.abort();\n crossfade.destroy();\n restore();\n },\n };\n};\n"],"mappings":"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"}
|
package/dist/vertical-bars.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { NativeUIStatus, NativeUISuspension,
|
|
1
|
+
import { ControlProjection, NativeUIControls, NativeUIStatus, NativeUISuspension, VerticalBarEdge, VerticalControlAreaHandle, VerticalControlAreaOptions } from "./native/definitions.js";
|
|
2
2
|
import { NativeUI } from "./native/activation.js";
|
|
3
3
|
import { applyFoldStateClasses } from "./native/foldable/fold-state.js";
|
|
4
4
|
import { setVerticalControlAreaPlacement } from "./native/legacy-layout.js";
|
|
5
5
|
import { enableVerticalControlArea } from "./native/index.js";
|
|
6
|
-
export { NativeUI, type
|
|
6
|
+
export { type ControlProjection, NativeUI, type NativeUIControls, type NativeUIStatus, type NativeUISuspension, type VerticalBarEdge, type VerticalControlAreaHandle, type VerticalControlAreaOptions, applyFoldStateClasses, enableVerticalControlArea, setVerticalControlAreaPlacement };
|
package/docs/configuration.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Configure Native UI
|
|
2
2
|
|
|
3
|
+
This is the settings reference. Start with [how projection works](../README.md#how-projection-works), then [Ionic setup](setup.md) or [HTML setup](framework-independent.md) for installation.
|
|
4
|
+
|
|
3
5
|
Call `enableNativeUI()` once after your application's DOM is mounted. Ionic and explicitly marked HTML controls share the standard Swift renderers. The theme is optional; Web layout, safe areas and spacing belong to your app or theme.
|
|
4
6
|
|
|
5
7
|
```ts
|
|
@@ -12,63 +14,85 @@ Without Ionic, import the same API from `@rdlabo/capacitor-native-ui/core`. That
|
|
|
12
14
|
|
|
13
15
|
## Platform behavior
|
|
14
16
|
|
|
15
|
-
| Feature
|
|
16
|
-
|
|
|
17
|
-
| Automatic Ionic controls (`ionic.auto`) | Native
|
|
18
|
-
| Explicit element buttons
|
|
19
|
-
| Element extensions
|
|
20
|
-
| Vertical Bars
|
|
21
|
-
| `tab-accessory` extensions
|
|
17
|
+
| Feature | Supported iOS | Android | Browser / older iOS |
|
|
18
|
+
| --------------------------------------- | ----------------------------------------- | --------------------- | --------------------- |
|
|
19
|
+
| Automatic Ionic controls (`ionic.auto`) | Native | Original Web controls | Original Web controls |
|
|
20
|
+
| Explicit element buttons | Native | Original Web controls | Original Web controls |
|
|
21
|
+
| Element extensions | SwiftUI | Kotlin / Compose | Original Web content |
|
|
22
|
+
| Vertical Bars | Native when available; otherwise Web rail | Web rail | Web rail |
|
|
23
|
+
| `tab-accessory` extensions | Supported iOS tab host only | Original Web content | Original Web content |
|
|
22
24
|
|
|
23
|
-
Web rails require the Ionic rail layout or an application-owned HTML `webHost`. Settings never create a device capability.
|
|
25
|
+
Web rails require the Ionic rail layout or an application-owned HTML `webHost`. Settings never create a device capability.
|
|
24
26
|
|
|
25
27
|
## All settings
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
`controls` chooses selection and grouping defaults. `verticalBars` configures the rail. `ionic` configures Ionic discovery and appearance. Omitted fields default to `true` in `controls`, including partial objects.
|
|
28
30
|
|
|
29
31
|
```ts
|
|
30
32
|
const shell = await enableNativeUI({
|
|
31
33
|
root: document.body,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
},
|
|
34
|
+
controls: {
|
|
35
|
+
buttonGroup: true,
|
|
36
|
+
button: true,
|
|
37
|
+
backButton: true,
|
|
38
|
+
tabs: true,
|
|
39
|
+
segment: true,
|
|
40
|
+
fab: true,
|
|
40
41
|
},
|
|
41
42
|
verticalBars: {
|
|
42
43
|
enabled: true,
|
|
43
|
-
only: false,
|
|
44
|
-
buttonProjection: 'system',
|
|
45
|
-
buttonDefaultFill: 'glass',
|
|
46
|
-
popover: false,
|
|
47
44
|
// Optional: placement: { edge: 'trailing', webHost: appOwnedHost },
|
|
48
45
|
},
|
|
49
|
-
horizontal: true,
|
|
50
|
-
vertical: true,
|
|
51
46
|
extensions: [],
|
|
47
|
+
ionic: {
|
|
48
|
+
auto: true,
|
|
49
|
+
verticalBars: {
|
|
50
|
+
buttonProjection: 'system',
|
|
51
|
+
buttonDefaultFill: 'glass',
|
|
52
|
+
popover: false,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
debug: false,
|
|
52
56
|
});
|
|
53
57
|
```
|
|
54
58
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
|
60
|
-
|
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
|
69
|
-
|
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
59
|
+
### Selection defaults
|
|
60
|
+
|
|
61
|
+
Every control accepts `ControlProjection`. `controls` itself accepts the same values as a shorthand for all six fields.
|
|
62
|
+
|
|
63
|
+
| Value | Ordinary placement | Vertical rail placement |
|
|
64
|
+
| ------------------ | ------------------ | ----------------------- |
|
|
65
|
+
| `true` (default) | Selected | Selected |
|
|
66
|
+
| `false` | Not selected | Not selected |
|
|
67
|
+
| `'horizontalOnly'` | Selected | Not selected |
|
|
68
|
+
| `'verticalOnly'` | Not selected | Selected |
|
|
69
|
+
|
|
70
|
+
Horizontal and vertical describe **control placement**, not screen orientation or device model. These defaults do not move a control, provide a missing device capability, or make unsupported markup eligible.
|
|
71
|
+
|
|
72
|
+
| Control | Responsibility |
|
|
73
|
+
| ------------- | -------------------------------------------------------------------------------- |
|
|
74
|
+
| `buttonGroup` | Group already selected actions when the group qualifies. Never selects children. |
|
|
75
|
+
| `button` | Select ordinary buttons and Ionic menu buttons. |
|
|
76
|
+
| `backButton` | Select back navigation, independently of its parent group. |
|
|
77
|
+
| `tabs` | Select tabs and their registered search presentation. |
|
|
78
|
+
| `segment` | Select supported segments. |
|
|
79
|
+
| `fab` | Select supported floating action buttons. |
|
|
80
|
+
|
|
81
|
+
Local `data-horizontal-bars="true|false"` and `data-vertical-bars="true|false"` override the matching selection default. On `ion-buttons` they control grouping only. Explicit `data-native-ui` sources are independent of these defaults. Structural exclusions and rail availability still apply. See [toolbar selection and grouping](native-ui.md#toolbar-selection-and-grouping).
|
|
82
|
+
|
|
83
|
+
### Other settings
|
|
84
|
+
|
|
85
|
+
| Setting | Default | Meaning |
|
|
86
|
+
| -------------------------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
87
|
+
| `root` | `document.body` | Connected source subtree. Controls outside it are not projected. |
|
|
88
|
+
| `verticalBars.enabled` | `true` | Allow rail projection when available. |
|
|
89
|
+
| `verticalBars.placement` | omitted | Optional `{ edge: 'leading' \| 'trailing', webHost?: HTMLElement }`. Edge is a preference, not a capability declaration. |
|
|
90
|
+
| `extensions` | `[]` | **Experimental.** App-specific SwiftUI / Compose extensions. Independent of `controls` and `ionic.auto`; the app decides when its reader returns content. |
|
|
91
|
+
| `ionic.auto` | `true` | Enable automatic Ionic discovery. `false` disables it, including local layout opt-ins. Explicit `data-native-ui` sources and extensions remain independent. |
|
|
92
|
+
| `ionic.verticalBars.buttonProjection` | `'system'` | `'system'` uses native styling; `'source'` projects supported Ionic styling. Local appearance overrides take precedence. |
|
|
93
|
+
| `ionic.verticalBars.buttonDefaultFill` | `'glass'` | `'glass'` or `'solid'` for source-style standalone Ionic buttons without explicit fill. Explicit fills and the clear default inside groups take precedence. |
|
|
94
|
+
| `ionic.verticalBars.popover` | `false` | Opt into Ionic popovers anchored to native rail buttons. Requires the Ionic entry and its optional peers. |
|
|
95
|
+
| `debug` | `false` | Log source elements and fallback reasons to the developer console. |
|
|
72
96
|
|
|
73
97
|
`webHost` is an optional, application-styled HTML fallback rail. It must be connected in the same document and outside `root`, with neither element containing the other. Use a limited `root`, such as your `<main>`, when supplying a sibling host; the default `document.body` cannot have such a host. Without a host, unavailable HTML rails leave their original controls visible. Ionic's existing rail layout uses its own fallback adapter.
|
|
74
98
|
|
|
@@ -80,25 +104,20 @@ HTML currently supports buttons and back buttons. Tab, segment and FAB readers r
|
|
|
80
104
|
// Ionic or HTML: all supported controls, rail support enabled by default.
|
|
81
105
|
const shell = await enableNativeUI();
|
|
82
106
|
|
|
83
|
-
// Alternative
|
|
84
|
-
const railOnly = await enableNativeUI({
|
|
107
|
+
// Alternative: automatic selection only in the rail.
|
|
108
|
+
const railOnly = await enableNativeUI({ controls: 'verticalOnly' });
|
|
85
109
|
|
|
86
|
-
// Alternative
|
|
110
|
+
// Alternative: automatically select only tabs.
|
|
87
111
|
const tabsOnly = await enableNativeUI({
|
|
88
|
-
|
|
112
|
+
controls: { buttonGroup: false, button: false, backButton: false, tabs: true, segment: false, fab: false },
|
|
89
113
|
});
|
|
90
114
|
```
|
|
91
115
|
|
|
92
|
-
These are alternatives: start only one shell. Repeating
|
|
116
|
+
These are alternatives: start only one shell. Repeating equivalent startup settings returns its handle; different settings require destroying the previous shell.
|
|
93
117
|
|
|
94
|
-
|
|
95
|
-
| --- | --- | --- |
|
|
96
|
-
| `enabled: true, only: false` | Eligible rail and ordinary native controls | Ordinary native controls; unavailable rail controls retain Web fallback |
|
|
97
|
-
| `enabled: true, only: true` | Eligible rail controls only | Web fallback |
|
|
98
|
-
| `enabled: false, only: false` | Ordinary native controls; rail controls remain Web | Same |
|
|
99
|
-
| `enabled: false, only: true` | All controls remain Web | Same |
|
|
118
|
+
`controls: false` disables all default selection and grouping, while local attributes can still opt in. `ionic: { auto: false }` instead disables automatic Ionic discovery altogether. Neither disables explicit HTML sources or extensions.
|
|
100
119
|
|
|
101
|
-
`data-native-ui-placement="element"` preserves
|
|
120
|
+
`verticalBars.enabled: false` leaves rail controls on the Web regardless of selection. It does not affect ordinary controls. `data-native-ui-placement="element"` preserves an explicit control's original position, even with `controls: 'verticalOnly'`. For Ionic groups, put placement on the whole `ion-buttons` group so its shared native surface has one placement.
|
|
102
121
|
|
|
103
122
|
## Select controls explicitly in a customized Ionic app
|
|
104
123
|
|
|
@@ -108,10 +127,13 @@ Disable automatic Ionic projection while retaining the native rail and your cust
|
|
|
108
127
|
const shell = await enableNativeUI({
|
|
109
128
|
root: document.querySelector<HTMLElement>('#content')!,
|
|
110
129
|
ionic: { auto: false },
|
|
111
|
-
verticalBars: {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
130
|
+
verticalBars: {
|
|
131
|
+
enabled: true,
|
|
132
|
+
placement: {
|
|
133
|
+
edge: 'trailing',
|
|
134
|
+
webHost: document.querySelector<HTMLElement>('#rail')!,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
115
137
|
extensions: [myExtension],
|
|
116
138
|
});
|
|
117
139
|
```
|
|
@@ -129,7 +151,7 @@ Keep your original handlers on each source. Explicit `ion-button` uses the stand
|
|
|
129
151
|
|
|
130
152
|
Explicit built-in sources support individual `button`, `ion-button`, and `[role="button"]` elements with `data-native-ui="button"` or `"back-button"`. This does not opt existing `ion-back-button`, `ion-menu-button`, button groups, tabs, segments, or FABs into their Ionic-specific readers. Keep automatic projection enabled for the corresponding kind, use a standard explicit button with your own click handler, or register an extension for custom UI.
|
|
131
153
|
|
|
132
|
-
`verticalBars.enabled`
|
|
154
|
+
`verticalBars.enabled` still governs rail availability. To leave a specific source on the Web, remove its explicit marker or use `data-native-ui="disabled"`.
|
|
133
155
|
|
|
134
156
|
## Change rail use after startup
|
|
135
157
|
|
|
@@ -138,12 +160,12 @@ await shell.disableVerticalControlArea();
|
|
|
138
160
|
await shell.enableVerticalControlArea();
|
|
139
161
|
```
|
|
140
162
|
|
|
141
|
-
These methods change `verticalBars.enabled` on this shell and preserve
|
|
163
|
+
These methods change `verticalBars.enabled` on this shell and preserve appearance, controls and placement. They retire existing projection before transferring ownership, without creating another shell. Calls are serialized. Calling either method after `destroy()` rejects.
|
|
142
164
|
|
|
143
165
|
```ts
|
|
144
166
|
const lease = await shell.suspend(); // temporarily restore all Web controls
|
|
145
167
|
await lease.resume();
|
|
146
|
-
await shell.destroy();
|
|
168
|
+
await shell.destroy(); // stop the shell and restore sources
|
|
147
169
|
```
|
|
148
170
|
|
|
149
171
|
Disabling rail projection does not remove application CSS or change application-owned spacing. Update your own layout alongside this setting if needed.
|
|
@@ -167,21 +189,21 @@ console.info(shell.getStatus());
|
|
|
167
189
|
|
|
168
190
|
Open the WebView developer console and filter for `[Native UI]`. Diagnostics include the DOM source when available. Each reason is logged once per source during that activation, so scrolling or repeated `markForCheck()` calls do not flood the console. Props, action payloads and application exception messages are not logged.
|
|
169
191
|
|
|
170
|
-
| Diagnostic
|
|
171
|
-
|
|
|
172
|
-
| `web-fallback`
|
|
173
|
-
| `unregistered-extension`
|
|
174
|
-
| `reader-error` / `invalid-props`
|
|
175
|
-
| `unsupported-placement` / `missing-tab-owner`
|
|
176
|
-
| `source-hidden` / `source-clipped` / `source-focused` | Visibility, clipping/transformed ancestors, or a focused Web descendant.
|
|
177
|
-
| `reader-opt-out`
|
|
178
|
-
| `unsupported-control`
|
|
179
|
-
| `native-rejected`
|
|
192
|
+
| Diagnostic | What to check |
|
|
193
|
+
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
194
|
+
| `web-fallback` | Platform support, native plugin registration, sync and native rebuild. |
|
|
195
|
+
| `unregistered-extension` | Native registration name matches the DOM definition and runs before startup. Newly inserted sources are checked too. |
|
|
196
|
+
| `reader-error` / `invalid-props` | Reader implementation, JSON-only values and the 64K size limit. |
|
|
197
|
+
| `unsupported-placement` / `missing-tab-owner` | Platform and owning native tab host for a tab accessory. |
|
|
198
|
+
| `source-hidden` / `source-clipped` / `source-focused` | Visibility, clipping/transformed ancestors, or a focused Web descendant. |
|
|
199
|
+
| `reader-opt-out` | Your reader returned null. |
|
|
200
|
+
| `unsupported-control` | Supported markup, accessible name, fixed placement and styling. |
|
|
201
|
+
| `native-rejected` | Native props decoding, placement or device support. The bridge reports rejection, not the native exception. |
|
|
180
202
|
|
|
181
203
|
`getStatus()` summarizes the shell; it is not a per-element diagnostic report. Normal navigation, suspension and unsupported content can intentionally retain Web presentation. Turn debug off after investigating; it does not change projection eligibility.
|
|
182
204
|
|
|
183
|
-
##
|
|
205
|
+
## Existing applications
|
|
184
206
|
|
|
185
|
-
`
|
|
207
|
+
The standalone `enableVerticalControlArea()` defaults selection to `'verticalOnly'`. Its `setPlacement()` helper is a deprecated Web CSS adapter; use the theme's `setVerticalBarsLayout({ edge, inset })` for Web layout. Native availability comes from the plugin's device metrics.
|
|
186
208
|
|
|
187
|
-
|
|
209
|
+
See [upgrading from 1.2.1](migration.md) for package separation, removed options and changed partial-control defaults.
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# Native UI without Ionic
|
|
2
2
|
|
|
3
|
-
> This guide targets the upcoming package split. Until the first native package release, use the [candidate archive installation](../README.md#try-the-unreleased-packages) in place of registry install commands.
|
|
4
|
-
|
|
5
3
|
Add native buttons to an existing Web app without Ionic or theme CSS. Keep labels, state, and click handlers on ordinary HTML elements. Standard buttons require no custom Swift registration.
|
|
6
4
|
|
|
7
5
|
Use a Capacitor 8 app with an iOS project, Xcode 26+, and an iOS 26+ device or simulator. Browsers and Android retain Web controls. For custom Android native content, use [Kotlin/Compose extensions](native-ui-extensions.md#2b-register-compose-content-on-android).
|
|
8
6
|
|
|
7
|
+
Start with [how projection works](../README.md#how-projection-works): the HTML element remains the source while native controls or Web proxies provide its presentation.
|
|
8
|
+
|
|
9
|
+
**Want only Vertical Bars?** Follow steps 1–2 to install and mark your HTML buttons, then use the [Web rail configuration](#optional-web-rail) in place of step 3. Remove explicit markers from buttons you want to leave on the Web. Style the rail host in your application and build with step 4 for iOS. You do not need Ionic, `@rdlabo/ionic-theme-ios27`, or `vertical-bars.css`.
|
|
10
|
+
|
|
11
|
+
For Vertical Bars in an app using `ion-tabs`, `ion-toolbar` or other Ionic components with a custom theme, use the [Ionic Vertical Bars setup](../../theme-ios27/docs/iphone-duo-with-original-theme.md#try-it-in-your-existing-ionic-app) instead. The framework (Angular, React or Vue) alone does not determine which adapter to use.
|
|
12
|
+
|
|
9
13
|
## 1. Install the plugin
|
|
10
14
|
|
|
11
15
|
Keep your existing Capacitor 8 installation and add the plugin directly:
|
|
@@ -49,7 +53,7 @@ Call once after the source root exists. Keep the handle and call `await shell.de
|
|
|
49
53
|
|
|
50
54
|
## 4. Build and run on iOS
|
|
51
55
|
|
|
52
|
-
Build your Web app, run `npx cap sync ios`, and launch the iOS project from Xcode.
|
|
56
|
+
Build your Web app, run `npx cap sync ios`, and launch the iOS project from Xcode. With the step 3 configuration, Add renders natively and invokes its original click handler. With the rail configuration below, Add retains native element placement and Back uses the native rail when available. In a browser, the same button remains an ordinary Web button. Unmarked HTML remains unchanged.
|
|
53
57
|
|
|
54
58
|
If a button stays on the Web on supported iOS, check that it is visible within the configured root, has an accessible name, and that the direct plugin dependency was synced and the iOS app rebuilt.
|
|
55
59
|
|
|
@@ -57,9 +61,11 @@ In an app that also uses Ionic, set `ionic: { auto: false }` to project only exp
|
|
|
57
61
|
|
|
58
62
|
## Optional Web rail
|
|
59
63
|
|
|
60
|
-
|
|
64
|
+
For a Web fallback rail, use this instead of the startup call in step 3. Call once after both the source root and rail host are mounted; call `await shell.destroy()` when their owner is disposed. Do not start a second shell:
|
|
61
65
|
|
|
62
66
|
```ts
|
|
67
|
+
import { enableNativeUI } from '@rdlabo/capacitor-native-ui/core';
|
|
68
|
+
|
|
63
69
|
const shell = await enableNativeUI({
|
|
64
70
|
root: document.querySelector<HTMLElement>('#app')!,
|
|
65
71
|
verticalBars: {
|
|
@@ -73,7 +79,7 @@ const shell = await enableNativeUI({
|
|
|
73
79
|
|
|
74
80
|
The plugin uses device-reported rail availability and resolves leading/trailing through the document direction. A placement preference never forces an unsupported system rail. Native rejection restores Web presentation. With `webHost`, explicitly rail-marked HTML controls have Web proxies when native projection is unavailable. Without it, their originals remain visible. The application styles and positions the host and reserves any content spacing. The host must be outside the source root.
|
|
75
81
|
|
|
76
|
-
To
|
|
82
|
+
Explicit markup chooses placement: Back goes to the rail and Add keeps native element placement. To project only Back, remove `data-native-ui="button"` from Add. `controls` does not disable explicit source declarations. To change rail use later, call `await shell.disableVerticalControlArea()` or `await shell.enableVerticalControlArea()`. Disabling restores original rail controls and removes Web rail proxies without destroying ordinary native projection.
|
|
77
83
|
|
|
78
84
|
Currently the HTML adapter supports buttons and back buttons. Arbitrary HTML tabs, segments, searchbars and FAB structures are not translated. Ionic adapters continue to support their existing controls. Custom element extensions work with the same scoped root; the existing `tab-accessory` extension placement still requires an Ionic tab-bar owner.
|
|
79
85
|
|