@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
|
@@ -17,104 +17,61 @@ export interface NativeUIStatus {
|
|
|
17
17
|
reason?: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
/** Default selection in ordinary (horizontal) or rail (vertical) placement.
|
|
21
|
+
* Explicit source settings override these defaults. This does not choose placement.
|
|
22
|
+
*/
|
|
23
|
+
export type ControlProjection = boolean | 'horizontalOnly' | 'verticalOnly';
|
|
24
|
+
|
|
25
|
+
export interface NativeUIControls {
|
|
26
|
+
/** Group selected ordinary buttons. Does not select children or include back buttons. */
|
|
27
|
+
buttonGroup?: ControlProjection;
|
|
28
|
+
/** Select ordinary actions, including menu buttons. */
|
|
29
|
+
button?: ControlProjection;
|
|
30
|
+
backButton?: ControlProjection;
|
|
31
|
+
tabs?: ControlProjection;
|
|
32
|
+
segment?: ControlProjection;
|
|
33
|
+
fab?: ControlProjection;
|
|
27
34
|
}
|
|
28
35
|
|
|
29
|
-
export interface
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
horizontal?: boolean | ToolbarProjectionOptions;
|
|
41
|
-
/** Vertical toolbar selection/grouping. true enables all three options; false disables them.
|
|
42
|
-
* Object fields default to true. Local data-vertical-bars overrides still apply.
|
|
43
|
-
* Neither layout setting controls tabs, segments or FABs.
|
|
44
|
-
*/
|
|
45
|
-
vertical?: boolean | ToolbarProjectionOptions;
|
|
46
|
-
/** Appearance of native vertical ion-button and ion-menu-button actions.
|
|
47
|
-
* `system` (default) uses SwiftUI styling and template icons; `source` projects supported Ionic fill and colors.
|
|
48
|
-
* Local data-projection="source|system" or ios-theme-projection-source / ios-theme-projection-system take precedence:
|
|
49
|
-
* the button itself, then its nearest ion-buttons, then this option. On the same element, a valid attribute wins;
|
|
50
|
-
* otherwise system wins if both classes are present. Invalid attribute values are ignored.
|
|
51
|
-
* Local settings update live; removing them restores inheritance. Actions, disabled state and grouping are preserved.
|
|
52
|
-
* Does not affect back buttons, tabs, FABs, horizontal controls, source elements or Web clones.
|
|
53
|
-
*/
|
|
54
|
-
buttonProjection?: 'source' | 'system';
|
|
55
|
-
/** Default fill for native vertical ion-button actions resolved to `source`, including local overrides.
|
|
56
|
-
* Use `solid` for Ionic's default design, or `null` (also the omitted default) for the iOS theme's glass design.
|
|
57
|
-
* Applies when fill is omitted or `default`, outside ion-buttons. Inside ion-buttons the default is clear.
|
|
58
|
-
* Explicit clear, solid and outline take precedence; clear and outline cannot be configured as defaults.
|
|
59
|
-
* Source elements and Web clones are unchanged.
|
|
60
|
-
*/
|
|
61
|
-
buttonDefaultFill?: 'solid' | null;
|
|
36
|
+
export interface IonicProjectionOptions {
|
|
37
|
+
/** Discover supported Ionic controls automatically. Explicit sources remain independent. Defaults to true. */
|
|
38
|
+
auto?: boolean;
|
|
39
|
+
verticalBars?: {
|
|
40
|
+
/** Native appearance of vertical Ionic buttons. Local source settings take precedence. Defaults to system. */
|
|
41
|
+
buttonProjection?: 'system' | 'source';
|
|
42
|
+
/** Source-style standalone ion-button default; ion-buttons still defaults to clear. Defaults to glass. */
|
|
43
|
+
buttonDefaultFill?: 'glass' | 'solid';
|
|
44
|
+
/** Relay Ionic popovers anchored to native rail buttons. Defaults to false. */
|
|
45
|
+
popover?: boolean;
|
|
46
|
+
};
|
|
62
47
|
}
|
|
63
48
|
|
|
64
|
-
export interface NativeUIOptions
|
|
65
|
-
/**
|
|
66
|
-
|
|
49
|
+
export interface NativeUIOptions {
|
|
50
|
+
/** Connected source subtree. Defaults to document.body. */
|
|
51
|
+
root?: HTMLElement;
|
|
52
|
+
/** Default selection and grouping. Scalars apply to every kind; omitted fields default to true. */
|
|
53
|
+
controls?: ControlProjection | NativeUIControls;
|
|
67
54
|
verticalBars?: NativeUIVerticalBarsOptions;
|
|
68
|
-
/**
|
|
55
|
+
/** App-local projection definitions, independent of controls.
|
|
56
|
+
* @experimental API and behavior may change.
|
|
57
|
+
*/
|
|
58
|
+
extensions?: readonly NativeUIExtension[];
|
|
69
59
|
ionic?: IonicProjectionOptions;
|
|
70
|
-
/**
|
|
71
|
-
|
|
72
|
-
/** @deprecated Call destroy() on the running handle to stop the shell. */
|
|
73
|
-
enabled?: boolean;
|
|
74
|
-
/** @deprecated Use ionic.auto for automatic Ionic projection and verticalBars.popover for popovers. */
|
|
75
|
-
controls?: NativeUIControls;
|
|
76
|
-
/** @deprecated Use verticalBars.only. */
|
|
77
|
-
verticalBarsOnly?: boolean;
|
|
60
|
+
/** Log Web fallback reasons. Defaults to false. */
|
|
61
|
+
debug?: boolean;
|
|
78
62
|
}
|
|
79
63
|
|
|
64
|
+
/** Options for the standalone rail entry. */
|
|
65
|
+
export type VerticalControlAreaOptions = NativeUIOptions;
|
|
66
|
+
|
|
80
67
|
/** @internal Resolved adapter state; not an application configuration API. */
|
|
81
68
|
export interface NativeUIRuntimeOptions extends NativeUIOptions {
|
|
82
69
|
source?: 'ionic' | 'html' | 'auto' | 'extensions';
|
|
83
|
-
/** @internal Preserves legacy controls.buttons behavior for explicit sources. */
|
|
84
|
-
explicitControls?: boolean;
|
|
85
70
|
nativeState?: { edge?: 'left' | 'right' };
|
|
86
|
-
/** @internal Keep unavailable definitions discoverable for diagnostics without calling their readers. */
|
|
87
71
|
unavailableExtensions?: ReadonlyMap<string, string>;
|
|
88
72
|
rail?: { host: HTMLElement; edge: 'left' | 'right' };
|
|
89
73
|
}
|
|
90
74
|
|
|
91
|
-
export interface IonicProjectionOptions {
|
|
92
|
-
/** Automatic Ionic projection. Defaults to true; partial objects keep omitted kinds enabled. */
|
|
93
|
-
auto?: boolean | AutoIonicControls;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export interface AutoIonicControls {
|
|
97
|
-
buttons?: boolean;
|
|
98
|
-
tabs?: boolean;
|
|
99
|
-
segment?: boolean;
|
|
100
|
-
fab?: boolean;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export interface NativeUIControls {
|
|
104
|
-
/** Toolbar actions and explicitly marked HTML buttons. Defaults to true. */
|
|
105
|
-
buttons?: boolean;
|
|
106
|
-
/** Projects tab bars and their native search presentation. */
|
|
107
|
-
tabs?: boolean;
|
|
108
|
-
/** @deprecated Use buttons. Projects toolbar buttons, including back and menu buttons. */
|
|
109
|
-
toolbar?: boolean;
|
|
110
|
-
/** Projects segments. */
|
|
111
|
-
segment?: boolean;
|
|
112
|
-
/** Projects floating action buttons. */
|
|
113
|
-
fab?: boolean;
|
|
114
|
-
/** Relays popovers anchored to native Vertical Bars buttons. Defaults to false. */
|
|
115
|
-
popover?: boolean;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
75
|
/** A running shell. Placement changes preserve all other startup settings. */
|
|
119
76
|
export interface NativeUIHandle extends NativeUIRuntimeHandle {
|
|
120
77
|
/** Enable rail projection when available, preserving other settings. Rejects after destroy. */
|
|
@@ -126,13 +83,6 @@ export interface NativeUIHandle extends NativeUIRuntimeHandle {
|
|
|
126
83
|
export interface NativeUIVerticalBarsOptions {
|
|
127
84
|
/** Defaults to true. Availability is always determined separately. */
|
|
128
85
|
enabled?: boolean;
|
|
129
|
-
/** Project only rail controls; unavailable or disabled rails leave controls on the Web. */
|
|
130
|
-
only?: boolean;
|
|
131
|
-
buttonProjection?: 'system' | 'source';
|
|
132
|
-
/** Source-style standalone buttons with no explicit fill. Defaults to glass. */
|
|
133
|
-
buttonDefaultFill?: 'glass' | 'solid';
|
|
134
|
-
/** Ionic popover relay. Defaults to false. */
|
|
135
|
-
popover?: boolean;
|
|
136
86
|
/** Placement preference, not a declaration of device capability. */
|
|
137
87
|
placement?: { edge: Exclude<VerticalBarEdge, null>; webHost?: HTMLElement };
|
|
138
88
|
}
|
|
@@ -290,7 +240,7 @@ export interface WebViewMetrics {
|
|
|
290
240
|
}
|
|
291
241
|
|
|
292
242
|
export interface NativeUIPlugin {
|
|
293
|
-
configure(options?: {
|
|
243
|
+
configure(options?: { suppressScrollEdgeEffect?: boolean }): Promise<{ supported: boolean; extensions?: string[] }>;
|
|
294
244
|
completeExtensionAction(options: { requestId: string; accepted: boolean }): Promise<void>;
|
|
295
245
|
getWebViewMetrics(): Promise<WebViewMetrics>;
|
|
296
246
|
getVerticalBarsGeometry(options: { viewportWidth: number; ids: string[] }): Promise<{ revision?: number; items: Record<string, Frame> }>;
|
package/src/native/html.ts
CHANGED
|
@@ -19,11 +19,6 @@ export const readHTMLControl = (element: HTMLElement, id: Identify, options: Nat
|
|
|
19
19
|
if (!element.matches('button, ion-button, [role="button"]') || !visible(element, htmlRailSource(element))) return;
|
|
20
20
|
if (element.contains(element.ownerDocument.activeElement)) return;
|
|
21
21
|
if (!htmlRailSource(element) && !fitsClip(element)) return;
|
|
22
|
-
if (
|
|
23
|
-
options.explicitControls === false ||
|
|
24
|
-
(options.explicitControls === undefined && (options.controls?.toolbar === false || options.controls?.buttons === false))
|
|
25
|
-
)
|
|
26
|
-
return;
|
|
27
22
|
if (htmlRailSource(element) && options.verticalBars?.enabled === false) return;
|
|
28
23
|
const kind = element.getAttribute('data-native-ui') === 'back-button' ? 'back-button' : 'button';
|
|
29
24
|
const candidate = createCandidate(element, kind, id, options);
|
package/src/native/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ export type {
|
|
|
10
10
|
NativeUIVerticalBarsOptions,
|
|
11
11
|
NativeUIComponent,
|
|
12
12
|
NativeUIControls,
|
|
13
|
-
|
|
13
|
+
ControlProjection,
|
|
14
14
|
IonicProjectionOptions,
|
|
15
15
|
NativeUIHandle,
|
|
16
16
|
NativeUIOptions,
|
|
@@ -19,7 +19,6 @@ export type {
|
|
|
19
19
|
VerticalBarEdge,
|
|
20
20
|
VerticalBarPlacement,
|
|
21
21
|
VerticalControlAreaHandle,
|
|
22
|
-
ToolbarProjectionOptions,
|
|
23
22
|
VerticalControlAreaOptions,
|
|
24
23
|
WebViewMetrics,
|
|
25
24
|
} from './definitions';
|
|
@@ -28,14 +27,6 @@ export const enableNativeUI = (options: NativeUIOptions = {}) =>
|
|
|
28
27
|
activate(options, async (...args) => (await import('./overlays/controller')).createOverlayController(...args));
|
|
29
28
|
|
|
30
29
|
export const enableVerticalControlArea = async (options: VerticalControlAreaOptions = {}): Promise<VerticalControlAreaHandle> => {
|
|
31
|
-
const handle = await enableNativeUI({
|
|
32
|
-
extensions: options.extensions,
|
|
33
|
-
horizontal: options.horizontal,
|
|
34
|
-
vertical: options.vertical,
|
|
35
|
-
buttonProjection: options.buttonProjection,
|
|
36
|
-
buttonDefaultFill: options.buttonDefaultFill,
|
|
37
|
-
controls: { tabs: true, toolbar: true, popover: options.controls?.popover === true },
|
|
38
|
-
verticalBarsOnly: true,
|
|
39
|
-
});
|
|
30
|
+
const handle = await enableNativeUI({ ...options, controls: options.controls ?? 'verticalOnly' });
|
|
40
31
|
return { ...handle, setPlacement: setVerticalControlAreaPlacement };
|
|
41
32
|
};
|
|
@@ -227,7 +227,7 @@ export const createOverlayController = async (
|
|
|
227
227
|
overlay.localName === 'ion-popover' &&
|
|
228
228
|
overlay.mode !== 'md' &&
|
|
229
229
|
!overlay.classList.contains('md') &&
|
|
230
|
-
options.
|
|
230
|
+
options.ionic?.verticalBars?.popover === true &&
|
|
231
231
|
nativeVerticalBars() &&
|
|
232
232
|
verticalBarsPopoverAnchor(overlay) !== undefined;
|
|
233
233
|
if (stopped || suspensions.size || !enabled || excluded(overlay)) {
|
package/src/native/prehide.ts
CHANGED
|
@@ -99,7 +99,7 @@ export const prehideVerticalBarsToolbarSources = (
|
|
|
99
99
|
const children = childElements(element);
|
|
100
100
|
const actions = children.filter((child) => eligible(child) && selectedAction(child, options));
|
|
101
101
|
const group =
|
|
102
|
-
(localPlacement(element) ?? toolbarOptions(options, true).
|
|
102
|
+
(localPlacement(element) ?? toolbarOptions(options, true).buttonGroup) &&
|
|
103
103
|
eligible(element) &&
|
|
104
104
|
actions.length === children.length &&
|
|
105
105
|
(actions.length > 1 || (actions.length === 1 && actions[0].matches('ion-menu-button')));
|
package/src/native/runtime.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { controlSelected } from './shared/control-options';
|
|
1
2
|
import { createDiagnostics } from './shared/diagnostics';
|
|
2
3
|
import { htmlSelector, readHTMLControl } from './html';
|
|
3
4
|
import { extensionAncestors, readExtension } from './extensions';
|
|
@@ -53,7 +54,6 @@ export const createRuntime = async (
|
|
|
53
54
|
plugin: NativeUIPlugin,
|
|
54
55
|
options: NativeUIRuntimeOptions = {},
|
|
55
56
|
nativeVerticalBars: () => boolean = () => true,
|
|
56
|
-
verticalBarsOnly = false,
|
|
57
57
|
onStop?: (cause: 'failure' | 'pagehide') => void,
|
|
58
58
|
): Promise<NativeUIRuntimeHandle & { retain: (deferActivation?: (id: string) => boolean) => () => void }> => {
|
|
59
59
|
const win = doc.defaultView!;
|
|
@@ -118,16 +118,12 @@ export const createRuntime = async (
|
|
|
118
118
|
let listener: PluginListenerHandle | undefined;
|
|
119
119
|
let searchListener: PluginListenerHandle | undefined;
|
|
120
120
|
let waiters: (() => void)[] = [];
|
|
121
|
-
const control = (kind: Candidate['control']['kind']): keyof NonNullable<NativeUIRuntimeOptions['controls']> => {
|
|
122
|
-
if (kind === 'tab-bar') return 'tabs';
|
|
123
|
-
if (kind === 'segment') return 'segment';
|
|
124
|
-
if (kind === 'floating-action-button') return 'fab';
|
|
125
|
-
return 'toolbar';
|
|
126
|
-
};
|
|
127
121
|
const controlEnabled = (candidate: Candidate) => {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
122
|
+
const kind = candidate.control.kind;
|
|
123
|
+
// Button readers apply selection/grouping per child; explicit HTML and extensions
|
|
124
|
+
// are independent opt-ins. Only other automatic kinds need this final selection.
|
|
125
|
+
const key = kind === 'tab-bar' ? 'tabs' : kind === 'segment' ? 'segment' : kind === 'floating-action-button' ? 'fab' : undefined;
|
|
126
|
+
return !key || controlSelected(candidate.element, options, key, candidate.control.placement === 'vertical-bars');
|
|
131
127
|
};
|
|
132
128
|
const id = (element: Element) => {
|
|
133
129
|
let value = ids.get(element);
|
|
@@ -200,10 +196,6 @@ export const createRuntime = async (
|
|
|
200
196
|
return;
|
|
201
197
|
}
|
|
202
198
|
const candidate = readExtension(element, extensions, id, (reason) => diagnose(`${extension.name}: ${reason}`, element));
|
|
203
|
-
if (verticalBarsOnly && (!candidate?.control.extension?.tabBarId || !nativeVerticalBars())) {
|
|
204
|
-
if (candidate) diagnose('rail-only: element extensions stay on the Web when verticalBars.only is true.', element);
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
199
|
return candidate;
|
|
208
200
|
}
|
|
209
201
|
if (options.source === 'extensions') return;
|
|
@@ -217,12 +209,11 @@ export const createRuntime = async (
|
|
|
217
209
|
return;
|
|
218
210
|
}
|
|
219
211
|
const rail = candidate.control.placement === 'vertical-bars';
|
|
220
|
-
if (verticalBarsOnly && !rail) return;
|
|
221
212
|
if (rail && !nativeVerticalBars()) return;
|
|
222
213
|
return candidate;
|
|
223
214
|
}
|
|
224
215
|
if (element.querySelector(htmlSelector)) return; // A shared automatic surface must not cover explicitly selected descendants.
|
|
225
|
-
if (
|
|
216
|
+
if (options.ionic?.auto === false) return;
|
|
226
217
|
if (options.verticalBars?.enabled === false && isVerticalBarsCandidate(element)) return;
|
|
227
218
|
const pointerPage = isVerticalBarsCandidate(element) ? element.closest<HTMLElement>('.ion-page') : undefined;
|
|
228
219
|
let candidate: Candidate | undefined;
|
|
@@ -4,8 +4,8 @@ import { iconSource, type createIconRenderer } from './icons';
|
|
|
4
4
|
|
|
5
5
|
export interface Candidate {
|
|
6
6
|
assets?: { signature: string; render(renderer: ReturnType<typeof createIconRenderer>): Promise<void> };
|
|
7
|
-
buttonProjection?:
|
|
8
|
-
buttonDefaultFill?:
|
|
7
|
+
buttonProjection?: 'system' | 'source';
|
|
8
|
+
buttonDefaultFill?: 'solid' | null;
|
|
9
9
|
element: HTMLElement;
|
|
10
10
|
sources?: HTMLElement[];
|
|
11
11
|
control: NativeUIControl;
|
|
@@ -26,8 +26,8 @@ export const createCandidate = (
|
|
|
26
26
|
const style = getComputedStyle(element);
|
|
27
27
|
return {
|
|
28
28
|
element,
|
|
29
|
-
buttonProjection: options.buttonProjection,
|
|
30
|
-
buttonDefaultFill: options.buttonDefaultFill,
|
|
29
|
+
buttonProjection: options.ionic?.verticalBars?.buttonProjection ?? 'system',
|
|
30
|
+
buttonDefaultFill: options.ionic?.verticalBars?.buttonDefaultFill === 'solid' ? 'solid' : null,
|
|
31
31
|
control: {
|
|
32
32
|
id: id(element),
|
|
33
33
|
kind:
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { NativeUIControls, NativeUIOptions } from '../definitions';
|
|
2
|
+
|
|
3
|
+
/** Resolve one default without changing discovery, explicit selection, or placement. */
|
|
4
|
+
export const controlDefault = (options: NativeUIOptions, kind: keyof NativeUIControls, vertical: boolean): boolean => {
|
|
5
|
+
const controls = options.controls;
|
|
6
|
+
const value = typeof controls === 'object' ? controls[kind] : controls;
|
|
7
|
+
if (value === undefined || value === true) return true;
|
|
8
|
+
if (value === false) return false;
|
|
9
|
+
return value === (vertical ? 'verticalOnly' : 'horizontalOnly');
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const controlSelected = (
|
|
13
|
+
element: HTMLElement,
|
|
14
|
+
options: NativeUIOptions,
|
|
15
|
+
kind: keyof NativeUIControls,
|
|
16
|
+
vertical: boolean,
|
|
17
|
+
): boolean => {
|
|
18
|
+
const local = element.getAttribute(vertical ? 'data-vertical-bars' : 'data-horizontal-bars');
|
|
19
|
+
return local === 'true' ? true : local === 'false' ? false : controlDefault(options, kind, vertical);
|
|
20
|
+
};
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import { modalUsesVerticalBars } from './modal';
|
|
2
|
-
import type {
|
|
2
|
+
import type { VerticalControlAreaOptions } from '../definitions';
|
|
3
|
+
import { controlDefault } from './control-options';
|
|
3
4
|
import { childElements, inFixedToolbar, isExcluded, isNativeUIDisabled } from './dom';
|
|
4
5
|
|
|
5
|
-
export const toolbarOptions = (options: VerticalControlAreaOptions, vertical: boolean)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
buttons: settings.buttons ?? true,
|
|
11
|
-
button: settings.button ?? true,
|
|
12
|
-
backButton: settings.backButton ?? true,
|
|
13
|
-
};
|
|
14
|
-
};
|
|
6
|
+
export const toolbarOptions = (options: VerticalControlAreaOptions, vertical: boolean) => ({
|
|
7
|
+
buttonGroup: controlDefault(options, 'buttonGroup', vertical),
|
|
8
|
+
button: controlDefault(options, 'button', vertical),
|
|
9
|
+
backButton: controlDefault(options, 'backButton', vertical),
|
|
10
|
+
});
|
|
15
11
|
|
|
16
12
|
export const localToolbarOption = (element: HTMLElement, vertical: boolean): boolean | undefined => {
|
|
17
13
|
const value = element.getAttribute(vertical ? 'data-vertical-bars' : 'data-horizontal-bars');
|
|
@@ -29,7 +25,7 @@ export const horizontalSelected = (element: HTMLElement, options: VerticalContro
|
|
|
29
25
|
export const horizontalToolbarGroup = (element: HTMLElement, options: VerticalControlAreaOptions): boolean => {
|
|
30
26
|
if (
|
|
31
27
|
!element.matches('ion-buttons.ios') ||
|
|
32
|
-
!(localToolbarOption(element, false) ?? toolbarOptions(options, false).
|
|
28
|
+
!(localToolbarOption(element, false) ?? toolbarOptions(options, false).buttonGroup) ||
|
|
33
29
|
!inFixedToolbar(element) ||
|
|
34
30
|
isExcluded(element) ||
|
|
35
31
|
isNativeUIDisabled(element)
|
package/src/native/startup.ts
CHANGED
|
@@ -13,27 +13,11 @@ export const startNativeUI = async (input: NativeUIOptions = {}, overlayFactory?
|
|
|
13
13
|
throw new Error('Native UI requires a connected root in the current document');
|
|
14
14
|
if (host && (!host.isConnected || host.ownerDocument !== document || root?.contains(host) || host.contains(root!)))
|
|
15
15
|
throw new Error('The Web rail host must be connected in the current document and outside the source root');
|
|
16
|
-
const legacy = input.controls;
|
|
17
|
-
const automatic = input.ionic?.auto;
|
|
18
|
-
const controls = automatic === undefined ? legacy : typeof automatic === 'object' ? automatic : {};
|
|
19
|
-
const defaultEnabled = automatic !== false;
|
|
20
16
|
const options: NativeUIRuntimeOptions = {
|
|
21
17
|
...input,
|
|
22
18
|
root,
|
|
23
19
|
source: 'auto',
|
|
24
|
-
explicitControls: automatic !== undefined || (legacy?.buttons ?? legacy?.toolbar ?? true),
|
|
25
20
|
verticalBars: bars,
|
|
26
|
-
controls: {
|
|
27
|
-
...controls,
|
|
28
|
-
tabs: controls?.tabs ?? defaultEnabled,
|
|
29
|
-
segment: controls?.segment ?? defaultEnabled,
|
|
30
|
-
fab: controls?.fab ?? defaultEnabled,
|
|
31
|
-
toolbar: controls?.buttons ?? (automatic === undefined ? legacy?.toolbar : undefined) ?? defaultEnabled,
|
|
32
|
-
popover: bars.popover ?? legacy?.popover ?? false,
|
|
33
|
-
},
|
|
34
|
-
verticalBarsOnly: bars.only ?? input.verticalBarsOnly ?? false,
|
|
35
|
-
buttonProjection: bars.buttonProjection ?? input.buttonProjection ?? 'system',
|
|
36
|
-
buttonDefaultFill: bars.buttonDefaultFill === 'glass' ? null : (bars.buttonDefaultFill ?? input.buttonDefaultFill ?? null),
|
|
37
21
|
rail: host ? { host, edge: bars.placement?.edge === 'leading' ? 'left' : 'right' } : undefined,
|
|
38
22
|
};
|
|
39
23
|
const runtime = await activate(options, overlayFactory);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { controlSelected } from './shared/control-options';
|
|
1
2
|
import { createCrossfade, fadeMarker } from './shared/crossfade';
|
|
2
3
|
import { alignVerticalBarClone, verticalBarFrame } from './shared/vertical-bars-geometry';
|
|
3
4
|
import { createVerticalBarsWebSearch, searchProjectionClass } from './vertical-bars-web-search';
|
|
@@ -50,7 +51,7 @@ export const createVerticalBarsWebProjection = (
|
|
|
50
51
|
const win = doc.defaultView!;
|
|
51
52
|
const crossfade = createCrossfade(win);
|
|
52
53
|
let incomingInstant: boolean | undefined;
|
|
53
|
-
const toolbarEnabled = options.
|
|
54
|
+
const toolbarEnabled = options.ionic?.auto !== false;
|
|
54
55
|
let root: HTMLElement | undefined;
|
|
55
56
|
let backSource: HTMLIonBackButtonElement | undefined;
|
|
56
57
|
let backProjection: HTMLIonBackButtonElement | undefined;
|
|
@@ -79,7 +80,8 @@ export const createVerticalBarsWebProjection = (
|
|
|
79
80
|
const search = createVerticalBarsWebSearch(
|
|
80
81
|
doc,
|
|
81
82
|
(element) =>
|
|
82
|
-
|
|
83
|
+
options.ionic?.auto !== false &&
|
|
84
|
+
controlSelected(element, options, 'tabs', true) &&
|
|
83
85
|
!!verticalBarsRoot()?.contains(element) &&
|
|
84
86
|
(!options.root || options.root.contains(element)) &&
|
|
85
87
|
!element.closest('[data-native-ui-placement="element"]') &&
|
package/src/vertical-bars.ts
CHANGED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Native UI Extension: Now Playing
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Native UI Extension: Now Playing
|
|
6
|
-
|
|
7
|
-
This example uses the experimental extension API, which may change in future releases.
|
|
8
|
-
|
|
9
|
-
Use the [app-local extension API](native-ui-extensions.md) to add a SwiftUI mini-player with a Web fallback. The demo's Index → Native UI Extension page provides a sample you can copy into your application.
|
|
10
|
-
|
|
11
|
-
- [Web component](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/demo/src/app/mini-player/mini-player.component.ts): owns playback state and the Now Playing modal. Play/pause is separate from opening the player. Tap or swipe up on the track opens the same modal.
|
|
12
|
-
- [Web renderer + DOM reader](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/demo/src/native-ui/mini-player.ts): renders the Web UI from attributes, sends title, artist and playback state to SwiftUI, and maps `open` / `toggle` to the original Web buttons.
|
|
13
|
-
- [SwiftUI View](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/demo/ios/App/App/NativeMiniPlayer.swift): uses `Button`, SF Symbols, `DragGesture`, and a capsule glass background. It awaits `NativeUIActions.activate()` rather than duplicating playback state or presenting its own modal.
|
|
14
|
-
|
|
15
|
-
The SwiftUI sample uses `NativeUIContext.sourceFrame.width` for compact spacing and `actions.isAvailable(...)` to disable actions whose Web targets are unavailable. See [projection context](native-ui-extensions.md#read-source-geometry-and-presentation-state) for coordinate units and system placement.
|
|
16
|
-
|
|
17
|
-
Register the View once in the app delegate and pass `miniPlayer` in `enableNativeUI({ extensions: [miniPlayer] })`. Copy these app-local files to adapt the sample to your own playback state and artwork. The original sunset artwork is drawn with SwiftUI shapes, with a matching local SVG for the Web. The compact player shows only artwork, track metadata and playback; elapsed time stays in Now Playing.
|
|
18
|
-
|
|
19
|
-
The sample uses the default **element projection**: SwiftUI follows the Web element’s position and size on iOS 26+, with ordinary tabs or Vertical Bars. Browsers and unsupported environments retain the Web player. Use the **Native extension** toggle to switch native projection on or off. The player starts in Web mode; disabling the extension restores its Web UI. It is shown only on this demo page; leaving the page hides it without resetting playback state.
|
|
20
|
-
|
|
21
|
-
The Web fallback reserves its own bottom-slot space and keeps play state across tab changes. Opening Now Playing retires the native player through the shell's existing overlay policy; dismissing it restores the current Web state.
|
|
22
|
-
|
|
23
|
-
This is a UI example: elapsed time is fixed and no audio engine is started. Applications own playback and reflect current values into the Web DOM. Connect your application's audio engine to the sample's playback events before using it as a player.
|
|
24
|
-
|
|
25
|
-
## Use the Web + SwiftUI set in your application
|
|
26
|
-
|
|
27
|
-
The local TypeScript file includes both a renderer for an ordinary DOM element and its native reader. Copy it together with `NativeMiniPlayer.swift`; in your app, replace the demo’s relative `defineNativeUI` import with `@rdlabo/capacitor-native-ui`. The Web renderer works without Angular, Capacitor or an enabled Native UI; SwiftUI replaces it only after native accepts the projection.
|
|
28
|
-
|
|
29
|
-
```ts
|
|
30
|
-
import { miniPlayer, mountNowPlaying } from './native-ui/mini-player';
|
|
31
|
-
import { enableNativeUI } from '@rdlabo/capacitor-native-ui';
|
|
32
|
-
|
|
33
|
-
const player = document.querySelector<HTMLElement>('[data-mini-player]')!;
|
|
34
|
-
const destroyWeb = mountNowPlaying(player); // Run on every platform, including the Web.
|
|
35
|
-
player.addEventListener('togglePlayback', () => {
|
|
36
|
-
player.setAttribute('data-playing', String(player.getAttribute('data-playing') !== 'true'));
|
|
37
|
-
});
|
|
38
|
-
player.addEventListener('openNowPlaying', () => openNowPlaying());
|
|
39
|
-
await enableNativeUI({ extensions: [miniPlayer] });
|
|
40
|
-
// Call destroyWeb() when the application's source element is destroyed.
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
```html
|
|
44
|
-
<section data-mini-player data-title="Midnight Drive" data-artist="The Coast" data-playing="true"></section>
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
The sample does not specify `placement`; the default is `'element'`. Applications can opt into `placement: 'tab-accessory'` when they want a system tab accessory; see the [placement options and host requirements](native-ui-extensions.md#content-and-placement-are-independent). Its surrounding Web layout belongs to the application. The same attributes feed the Web view and SwiftUI; both dispatch the same events. The application updates attributes after its playback state changes. Mount into an empty element. The Web renderer owns only its generated children and releases its observer and handlers on teardown. Frameworks own the host's attributes, not those children. Text is rendered as text, never interpreted as HTML.
|