native-fn 1.1.7 → 1.1.9

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 CHANGED
@@ -1,36 +1,2378 @@
1
1
  # native-fn API Reference
2
-
3
2
  ![NPM](https://nodei.co/npm/native-fn.png?downloads=true&downloadRank=true&stars=true)<br>
4
- ![NPM Downloads](https://img.shields.io/npm/d18m/native-fn?style=flat&logo=npm&logoColor=%23CB3837&label=Download&color=%23CB3837&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fnative-fn)
5
- ![GitHub Repo stars](https://img.shields.io/github/stars/pjy0509/native-fn?style=flat&logo=github&logoColor=181717&label=Stars&color=181717&link=https%3A%2F%2Fgithub.com%2Fpjy0509%2Fnative-fn)
6
- ![Static Badge](https://img.shields.io/badge/Typescript-8A2BE2?logo=typescript&color=000000)
3
+ <a href="https://www.npmjs.com/package/native-fn">![NPM Downloads](https://img.shields.io/npm/d18m/native-fn?style=flat&logo=npm&logoColor=%23CB3837&label=Download&color=%23CB3837&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fnative-fn)</a>
4
+ <a href="https://www.npmjs.com/package/native-fn">![GitHub Repo stars](https://img.shields.io/github/stars/pjy0509/native-fn?style=flat&logo=github&logoColor=181717&label=Stars&color=181717&link=https%3A%2F%2Fgithub.com%2Fpjy0509%2Fnative-fn)</a>
5
+ <a href="https://github.com/pjy0509/native-fn.git">![Static Badge](https://img.shields.io/badge/Typescript-8A2BE2?logo=typescript&color=000000)</a>
6
+ <br/>
7
+ <a href="https://www.jsdelivr.com/package/npm/native-fn" target="_blank"><img alt="jsDelivr" src="http://www.google.com/s2/favicons?domain=www.jsdelivr.com/"></a>
8
+ <a href="https://www.npmjs.com/package/native-fn" target="_blank"><img alt="npm" src="http://www.google.com/s2/favicons?domain=www.npmjs.com/"></a>
9
+ <a href="https://github.com/pjy0509/native-fn.git" target="_blank"><img alt="repository" src="http://www.google.com/s2/favicons?domain=https://github.com/pjy0509/native-fn.git/"></a>
10
+ ## Installation
7
11
 
8
- ## Overview
12
+ **npm**
9
13
 
10
- ---
11
- ## Install
12
- npm
13
- ```bash
14
+ ```shell
14
15
  npm i native-fn
15
16
  ```
16
- yarn
17
- ```bash
17
+
18
+ **yarn**
19
+
20
+ ```shell
18
21
  yarn add native-fn
19
22
  ```
20
- cdn
23
+
24
+ **cdnjs**
25
+
26
+ ```html
27
+ <script src="https://unpkg.com/native-fn"></script>
28
+ ```
29
+
30
+ **jsdelivr**
31
+
21
32
  ```html
22
- <script src='https://unpkg.com/native-fn'></script>
33
+ <script src="https://cdn.jsdelivr.net/npm/native-fn"></script>
34
+ ```
35
+
36
+ ## Table of Contents
37
+
38
+ - **[appearance](#appearance)**
39
+ - [value](#appearance-value)
40
+ - [onChange](#appearance-onchange)
41
+ - **[badge](#badge)**
42
+ - [set](#badge-set)
43
+ - [clear](#badge-clear)
44
+ - **[battery](#battery)**
45
+ - [value](#battery-value)
46
+ - [onChange](#battery-onchange)
47
+ - **[clipboard](#clipboard)**
48
+ - [copy](#clipboard-copy)
49
+ - [paste](#clipboard-paste)
50
+ - **[dimension](#dimension)**
51
+ - [value](#dimension-value)
52
+ - [environment](#dimension-environment)
53
+ - [onChange](#dimension-onchange)
54
+ - **[fullscreen](#fullscreen)**
55
+ - [request](#fullscreen-request)
56
+ - [exit](#fullscreen-exit)
57
+ - [toggle](#fullscreen-toggle)
58
+ - [onChange](#fullscreen-onchange)
59
+ - [onError](#fullscreen-onerror)
60
+ - **[geolocation](#geolocation)**
61
+ - [value](#geolocation-value)
62
+ - [onChange](#geolocation-onchange)
63
+ - **[notification](#notification)**
64
+ - [send](#notification-send)
65
+ - **[open](#open)**
66
+ - [app](#open-app)
67
+ - [telephone](#open-telephone)
68
+ - [message](#open-message)
69
+ - [mail](#open-mail)
70
+ - [file](#open-file)
71
+ - [directory](#open-directory)
72
+ - [setting](#open-setting)
73
+ - [camera](#open-camera)
74
+ - [contact](#open-contact)
75
+ - [share](#open-share)
76
+ - [calendar](#open-calendar)
77
+ - **[permission](#permission)**
78
+ - [request](#permission-request)
79
+ - [check](#permission-check)
80
+ - **[pip](#pip)**
81
+ - [request](#pip-request)
82
+ - [exit](#pip-exit)
83
+ - [toggle](#pip-toggle)
84
+ - [onChange](#pip-onchange)
85
+ - [onError](#pip-onerror)
86
+ - **[platform](#platform)**
87
+ - [os](#platform-os)
88
+ - [browser](#platform-browser)
89
+ - [engine](#platform-engine)
90
+ - [device](#platform-device)
91
+ - [locale](#platform-locale)
92
+ - [gpu](#platform-gpu)
93
+ - [userAgent](#platform-useragent)
94
+ - [ready](#platform-ready)
95
+ - [isWebview](#platform-iswebview)
96
+ - [isNode](#platform-isnode)
97
+ - [isStandalone](#platform-isstandalone)
98
+ - **[theme](#theme)**
99
+ - [value](#theme-value)
100
+ - **[vibration](#vibration)**
101
+ - [run](#vibration-run)
102
+ - [stop](#vibration-stop)
103
+
104
+ ## appearance
105
+
106
+ [`value`](#appearance-value) · [`onChange`](#appearance-onchange)
107
+
108
+ <h3 id="appearance-value"><code>appearance.value</code></h3>
109
+
110
+ **Signature**
111
+
112
+ ```ts
113
+ get value(): Appearances
114
+ ```
115
+
116
+ Returns the current color scheme of the device.
117
+
118
+ **Example**
119
+
120
+ ```ts
121
+ console.log(Native.appearance.value); // 'dark' | 'light' | 'unknown'
122
+ ```
123
+
124
+ **Returns**
125
+
126
+ ```ts
127
+ Appearances
128
+ ```
129
+
130
+
131
+ ```ts
132
+ enum Appearances {
133
+ Unknown = 'unknown',
134
+ Light = 'light',
135
+ Dark = 'dark',
136
+ }
137
+ ```
138
+
139
+ ---
140
+
141
+ <h3 id="appearance-onchange"><code>appearance.onChange</code></h3>
142
+
143
+ **Signature**
144
+
145
+ ```ts
146
+ onChange(listener: (appearance: Appearances) => void, options?: AddEventListenerOptions): () => void
147
+ ```
148
+
149
+ Subscribes to device color scheme changes.
150
+
151
+ **Example**
152
+
153
+ ```ts
154
+ const unsubscribe = Native.appearance.onChange((appearance) => {
155
+ console.log(appearance); // 'dark' | 'light'
156
+ });
157
+
158
+ unsubscribe();
159
+ ```
160
+
161
+ **Returns**
162
+
163
+ ```ts
164
+ () => void
165
+ ```
166
+
167
+
168
+ ```ts
169
+ // call to remove the listener
170
+ unsubscribe();
171
+ ```
172
+
173
+ ---
174
+
175
+ ## badge
176
+
177
+ [`set`](#badge-set) · [`clear`](#badge-clear)
178
+
179
+ <h3 id="badge-set"><code>badge.set</code></h3>
180
+
181
+ **Signature**
182
+
183
+ ```ts
184
+ set(contents: number): Promise<void>
185
+ ```
186
+
187
+ Sets the app badge count.
188
+
189
+ **Example**
190
+
191
+ ```ts
192
+ await Native.badge.set(5);
193
+ ```
194
+
195
+ **Returns**
196
+
197
+ ```ts
198
+ Promise<void>
199
+ ```
200
+
201
+
202
+ **Throws**
203
+
204
+ ```ts
205
+ throw new NotSupportedError // navigator.setAppBadge unavailable
206
+ ```
207
+
208
+ ---
209
+
210
+ <h3 id="badge-clear"><code>badge.clear</code></h3>
211
+
212
+ **Signature**
213
+
214
+ ```ts
215
+ clear(): Promise<void>
216
+ ```
217
+
218
+ Clears the app badge.
219
+
220
+ **Example**
221
+
222
+ ```ts
223
+ await Native.badge.clear();
224
+ ```
225
+
226
+ **Returns**
227
+
228
+ ```ts
229
+ Promise<void>
230
+ ```
231
+
232
+
233
+ **Throws**
234
+
235
+ ```ts
236
+ throw new NotSupportedError // navigator.setAppBadge unavailable
237
+ ```
238
+
239
+ ---
240
+
241
+ ## battery
242
+
243
+ [`value`](#battery-value) · [`onChange`](#battery-onchange)
244
+
245
+ <h3 id="battery-value"><code>battery.value</code></h3>
246
+
247
+ **Signature**
248
+
249
+ ```ts
250
+ get value(): Promise<BatteryManager>
251
+ ```
252
+
253
+ Returns the current battery status.
254
+
255
+ **Example**
256
+
257
+ ```ts
258
+ const battery = await Native.battery.value;
259
+
260
+ console.log(battery.level); // 0.0 – 1.0
261
+ console.log(battery.charging); // true | false
262
+ console.log(battery.chargingTime); // seconds until full
263
+ console.log(battery.dischargingTime); // seconds until empty
264
+ ```
265
+
266
+ **Returns**
267
+
268
+ ```ts
269
+ Promise<BatteryManager>
270
+ ```
271
+
272
+
273
+ ```ts
274
+ interface BatteryManager {
275
+ readonly charging: boolean;
276
+ readonly chargingTime: number;
277
+ readonly dischargingTime: number;
278
+ readonly level: number;
279
+ }
280
+ ```
281
+
282
+ **Throws**
283
+
284
+ ```ts
285
+ throw new NotSupportedError // navigator.getBattery unavailable
286
+ ```
287
+
288
+ ---
289
+
290
+ <h3 id="battery-onchange"><code>battery.onChange</code></h3>
291
+
292
+ **Signature**
293
+
294
+ ```ts
295
+ onChange(listener: (battery: BatteryManager) => void, options?: AddEventListenerOptions): () => void
296
+ ```
297
+
298
+ Subscribes to battery status changes.
299
+
300
+ **Example**
301
+
302
+ ```ts
303
+ const unsubscribe = Native.battery.onChange((battery) => {
304
+ console.log(battery.level); // 0.0 – 1.0
305
+ console.log(battery.charging); // true | false
306
+ });
307
+
308
+ unsubscribe();
309
+ ```
310
+
311
+ **Returns**
312
+
313
+ ```ts
314
+ () => void
315
+ ```
316
+
317
+
318
+ ```ts
319
+ // call to remove the listener
320
+ unsubscribe();
321
+ ```
322
+
323
+ ---
324
+
325
+ ## clipboard
326
+
327
+ [`copy`](#clipboard-copy) · [`paste`](#clipboard-paste)
328
+
329
+ <h3 id="clipboard-copy"><code>clipboard.copy</code></h3>
330
+
331
+ **Signature**
332
+
333
+ ```ts
334
+ copy(item: any): Promise<boolean>
335
+ ```
336
+
337
+ Copies a value to the clipboard. Accepts string, Element, Selection, object, or array.
338
+
339
+ **Flowchart**
340
+
341
+ ```mermaid
342
+ flowchart TD
343
+ A([clipboard.copy called]) --> B[Convert item to text and html]
344
+ B --> C{Clipboard API available?}
345
+ C -->|yes| D[copyViaClipboardAPI]
346
+ C -->|no| E[copyViaLegacy]
347
+ D --> F{Success?}
348
+ F -->|yes| G([return true])
349
+ F -->|no| E
350
+ E --> H{ClipboardItem + write available?}
351
+ H -->|yes| I[navigator.clipboard.write with text/html + text/plain]
352
+ H -->|no| J{writeText available?}
353
+ J -->|yes| K[navigator.clipboard.writeText]
354
+ J -->|no| L[copyViaSelection via execCommand]
355
+ L --> M{execCommand success?}
356
+ M -->|yes| G
357
+ M -->|no| N[copyViaClipboardData IE fallback]
358
+ N --> O([return boolean])
359
+ ```
360
+
361
+ **Example**
362
+
363
+ ```ts
364
+ // String
365
+ await Native.clipboard.copy('Hello world');
366
+
367
+ // DOM element (copies outerHTML + textContent)
368
+ await Native.clipboard.copy(document.querySelector('.content'));
369
+
370
+ // Object → serialized as JSON
371
+ await Native.clipboard.copy({ key: 'value' });
372
+
373
+ // Current selection
374
+ await Native.clipboard.copy(window.getSelection());
375
+ ```
376
+
377
+ **Returns**
378
+
379
+ ```ts
380
+ Promise<boolean>
381
+ ```
382
+
383
+
384
+ ---
385
+
386
+ <h3 id="clipboard-paste"><code>clipboard.paste</code></h3>
387
+
388
+ **Signature**
389
+
390
+ ```ts
391
+ paste(): Promise<string>
392
+ ```
393
+
394
+ Reads the current clipboard content as a string.
395
+
396
+ **Flowchart**
397
+
398
+ ```mermaid
399
+ flowchart TD
400
+ A([clipboard.paste called]) --> B{Clipboard API available?}
401
+ B -->|yes| C[pasteViaClipboardAPI]
402
+ B -->|no| D[pasteViaLegacy]
403
+ C --> E{ClipboardItem + read available?}
404
+ E -->|yes| F[navigator.clipboard.read]
405
+ E -->|no| G[navigator.clipboard.readText]
406
+ F --> H{text/html type found?}
407
+ H -->|yes| I([return html string])
408
+ H -->|no| J{text/plain found?}
409
+ J -->|yes| K([return plain string])
410
+ J -->|no| D
411
+ G --> L{Success?}
412
+ L -->|yes| M([return string])
413
+ L -->|no| D
414
+ D --> N[pasteViaSelection via execCommand]
415
+ N --> O{Success?}
416
+ O -->|yes| P([return string])
417
+ O -->|no| Q[pasteViaClipboardData IE fallback]
418
+ Q --> R([return string])
419
+ ```
420
+
421
+ **Example**
422
+
423
+ ```ts
424
+ const text = await Native.clipboard.paste();
425
+
426
+ console.log(text); // HTML string if available, plain text otherwise
427
+ ```
428
+
429
+ **Returns**
430
+
431
+ ```ts
432
+ Promise<string>
433
+ ```
434
+
435
+
436
+ ---
437
+
438
+ ## dimension
439
+
440
+ [`value`](#dimension-value) · [`environment`](#dimension-environment) · [`onChange`](#dimension-onchange)
441
+
442
+ <h3 id="dimension-value"><code>dimension.value</code></h3>
443
+
444
+ **Signature**
445
+
446
+ ```ts
447
+ get value(): Dimensions
448
+ ```
449
+
450
+ Returns current viewport dimensions, device pixel ratio, and orientation.
451
+
452
+ **Example**
453
+
454
+ ```ts
455
+ const { innerWidth, innerHeight, outerWidth, outerHeight, scale, orientation } = Native.dimension.value;
456
+
457
+ console.log(innerWidth, innerHeight); // visible viewport size
458
+ console.log(scale); // device pixel ratio e.g. 2, 3
459
+
460
+ if (orientation === Orientation.Portrait) {
461
+ console.log('Portrait mode');
462
+ }
463
+ ```
464
+
465
+ **Returns**
466
+
467
+ ```ts
468
+ Dimensions
469
+ ```
470
+
471
+
472
+ ```ts
473
+ interface Dimensions {
474
+ outerWidth: number;
475
+ outerHeight: number;
476
+ innerWidth: number;
477
+ innerHeight: number;
478
+ scale: number;
479
+ orientation: Orientation;
480
+ }
481
+
482
+ enum Orientation {
483
+ Portrait = 'portrait',
484
+ Landscape = 'landscape',
485
+ Unknown = 'unknown',
486
+ }
487
+ ```
488
+
489
+ ---
490
+
491
+ <h3 id="dimension-environment"><code>dimension.environment</code></h3>
492
+
493
+ **Signature**
494
+
495
+ ```ts
496
+ environment: Environment
23
497
  ```
24
498
 
25
- ## Interface
499
+ Provides access to CSS environment variable values: safe-area-inset, keyboard-inset, titlebar-area, and viewport-segment.
26
500
 
27
- ```typescript
28
- interface Native {
29
- Index: Index
501
+ **Example**
502
+
503
+ ```ts
504
+ // Safe area insets (e.g. iPhone notch / Dynamic Island)
505
+ const inset = Native.dimension.environment.safeAreaInset.value;
506
+ console.log(inset.top, inset.bottom, inset.left, inset.right);
507
+
508
+ // Virtual keyboard height
509
+ const kb = Native.dimension.environment.keyboardInset.value;
510
+ console.log(kb.height); // 0 when keyboard is hidden
511
+
512
+ // Subscribe to safe area changes
513
+ const unsubscribe = Native.dimension.environment.safeAreaInset.onChange((inset) => {
514
+ document.body.style.paddingBottom = inset.bottom + 'px';
515
+ });
516
+ unsubscribe();
517
+ ```
518
+
519
+ **Returns**
520
+
521
+ ```ts
522
+ Environment
523
+ ```
524
+
525
+
526
+ ```ts
527
+ interface Environment {
528
+ safeAreaInset: EnvironmentPreset<'safe-area-inset'>;
529
+ safeAreaMaxInset: EnvironmentPreset<'safe-area-max-inset'>;
530
+ keyboardInset: EnvironmentPreset<'keyboard-inset'>;
531
+ titlebarArea: EnvironmentPreset<'titlebar-area'>;
532
+ viewportSegment: EnvironmentPreset<'viewport-segment'>;
30
533
  }
31
534
 
32
- interface Index {
33
- open: (options: AppOpenOptions, target?: WindowProxy) => Promise<AppOpenState>;
34
- messenger: Messenger;
535
+ interface EnvironmentPreset<K> {
536
+ get value(): EnvironmentPresetValues<K>;
537
+ onChange(listener: (value: EnvironmentPresetValues<K>) => void, options?: AddEventListenerOptions): () => void;
35
538
  }
36
539
  ```
540
+
541
+ ---
542
+
543
+ <h3 id="dimension-onchange"><code>dimension.onChange</code></h3>
544
+
545
+ **Signature**
546
+
547
+ ```ts
548
+ onChange(listener: (dimension: Dimensions) => void, options?: AddEventListenerOptions): () => void
549
+ ```
550
+
551
+ Subscribes to viewport dimension and orientation changes.
552
+
553
+ **Example**
554
+
555
+ ```ts
556
+ const unsubscribe = Native.dimension.onChange((dimension) => {
557
+ console.log(dimension.innerWidth, dimension.innerHeight);
558
+ console.log(dimension.orientation); // 'portrait' | 'landscape'
559
+ });
560
+
561
+ unsubscribe();
562
+ ```
563
+
564
+ **Returns**
565
+
566
+ ```ts
567
+ () => void
568
+ ```
569
+
570
+
571
+ ```ts
572
+ // call to remove the listener
573
+ unsubscribe();
574
+ ```
575
+
576
+ ---
577
+
578
+ ## fullscreen
579
+
580
+ [`request`](#fullscreen-request) · [`exit`](#fullscreen-exit) · [`toggle`](#fullscreen-toggle) · [`onChange`](#fullscreen-onchange) · [`onError`](#fullscreen-onerror)
581
+
582
+ <h3 id="fullscreen-request"><code>fullscreen.request</code></h3>
583
+
584
+ **Signature**
585
+
586
+ ```ts
587
+ request(target?: Element, options?: FullscreenOptions): Promise<void>
588
+ ```
589
+
590
+ Requests fullscreen for an element. Concurrent calls are queued (FIFO).
591
+
592
+ **Flowchart**
593
+
594
+ ```mermaid
595
+ flowchart TD
596
+ A([Fullscreen.request called]) --> B[Set lastIntendedOperation to request]
597
+ B --> C{activeOperation in progress?}
598
+ C -->|no| D[requestImmediately]
599
+ C -->|yes| E[Push to pendingQueue]
600
+ D --> F{api available?}
601
+ F -->|yes| G[Call element api.request]
602
+ F -->|no| H[fallbackToIOSVideo]
603
+ G --> I{Promise returned?}
604
+ I -->|yes| J{Resolved?}
605
+ J -->|yes| K([resolve])
606
+ J -->|no| H
607
+ I -->|no| K
608
+ H --> L{iOS + VIDEO + webkitSupportsFullscreen?}
609
+ L -->|yes| M[video.webkitEnterFullscreen]
610
+ M --> K
611
+ L -->|no| N([Throw NotSupportedError])
612
+ D --> O[drainPendingOperation on settle]
613
+ ```
614
+
615
+ **Example**
616
+
617
+ ```ts
618
+ // Default: documentElement on desktop, first video on iOS
619
+ await Native.fullscreen.request();
620
+
621
+ // Specific element
622
+ await Native.fullscreen.request(document.getElementById('player'));
623
+
624
+ // With options
625
+ await Native.fullscreen.request(element, { navigationUI: 'hide' });
626
+
627
+ // Concurrent calls — safely queued, not dropped
628
+ Native.fullscreen.request(document.querySelector('video#a'));
629
+ Native.fullscreen.request(document.querySelector('video#b'));
630
+ ```
631
+
632
+ **Returns**
633
+
634
+ ```ts
635
+ Promise<void>
636
+ ```
637
+
638
+
639
+ **Throws**
640
+
641
+ ```ts
642
+ throw new NotSupportedError // element does not support fullscreen
643
+ ```
644
+ ```ts
645
+ throw new NotSupportedError // iOS video lacks webkitEnterFullscreen
646
+ ```
647
+
648
+ ---
649
+
650
+ <h3 id="fullscreen-exit"><code>fullscreen.exit</code></h3>
651
+
652
+ **Signature**
653
+
654
+ ```ts
655
+ exit(): Promise<void>
656
+ ```
657
+
658
+ Exits fullscreen. Concurrent calls are queued (FIFO).
659
+
660
+ **Flowchart**
661
+
662
+ ```mermaid
663
+ flowchart TD
664
+ A([Fullscreen.exit called]) --> B[Set lastIntendedOperation to exit]
665
+ B --> C{activeOperation in progress?}
666
+ C -->|no| D[exitImmediately]
667
+ C -->|yes| E[Push to pendingQueue]
668
+ D --> F{getElement null and lastIOSVideo null?}
669
+ F -->|yes| K([resolve immediately])
670
+ F -->|no| G{api available?}
671
+ G -->|yes| H[Call document api.exit]
672
+ G -->|no| I[fallbackToIOSVideo]
673
+ H --> J{Resolved?}
674
+ J -->|yes| K
675
+ J -->|no| I
676
+ I --> L{Displaying fullscreen video found?}
677
+ L -->|yes| M[webkitExitFullscreen]
678
+ M --> K
679
+ L -->|no| N([Throw NotSupportedError])
680
+ ```
681
+
682
+ **Example**
683
+
684
+ ```ts
685
+ await Native.fullscreen.exit();
686
+ ```
687
+
688
+ **Returns**
689
+
690
+ ```ts
691
+ Promise<void>
692
+ ```
693
+
694
+
695
+ **Throws**
696
+
697
+ ```ts
698
+ throw new NotSupportedError // failed to exit fullscreen
699
+ ```
700
+
701
+ ---
702
+
703
+ <h3 id="fullscreen-toggle"><code>fullscreen.toggle</code></h3>
704
+
705
+ **Signature**
706
+
707
+ ```ts
708
+ toggle(target?: Element, options?: FullscreenOptions): Promise<void>
709
+ ```
710
+
711
+ Toggles fullscreen on or off.
712
+
713
+ **Example**
714
+
715
+ ```ts
716
+ // Toggle on button click
717
+ btn.addEventListener('click', () => Native.fullscreen.toggle());
718
+
719
+ // Toggle a specific element
720
+ btn.addEventListener('click', () => {
721
+ Native.fullscreen.toggle(document.getElementById('player'));
722
+ });
723
+ ```
724
+
725
+ **Returns**
726
+
727
+ ```ts
728
+ Promise<void>
729
+ ```
730
+
731
+
732
+ **Throws**
733
+
734
+ ```ts
735
+ throw new NotSupportedError // propagated from request() or exit()
736
+ ```
737
+
738
+ ---
739
+
740
+ <h3 id="fullscreen-onchange"><code>fullscreen.onChange</code></h3>
741
+
742
+ **Signature**
743
+
744
+ ```ts
745
+ onChange(listener: (event: Event) => void, options?: AddEventListenerOptions): () => void
746
+ ```
747
+
748
+ Subscribes to fullscreen state changes.
749
+
750
+ **Example**
751
+
752
+ ```ts
753
+ const unsubscribe = Native.fullscreen.onChange(() => {
754
+ console.log('isFullscreen:', Native.fullscreen.isFullscreen);
755
+ console.log('element:', Native.fullscreen.element);
756
+ });
757
+
758
+ unsubscribe();
759
+ ```
760
+
761
+ **Returns**
762
+
763
+ ```ts
764
+ () => void
765
+ ```
766
+
767
+
768
+ ```ts
769
+ // call to remove the listener
770
+ unsubscribe();
771
+ ```
772
+
773
+ ---
774
+
775
+ <h3 id="fullscreen-onerror"><code>fullscreen.onError</code></h3>
776
+
777
+ **Signature**
778
+
779
+ ```ts
780
+ onError(listener: (event: Event) => void, options?: AddEventListenerOptions): () => void
781
+ ```
782
+
783
+ Subscribes to fullscreen errors.
784
+
785
+ **Example**
786
+
787
+ ```ts
788
+ const unsubscribe = Native.fullscreen.onError((event) => {
789
+ console.error('Fullscreen error:', event);
790
+ });
791
+
792
+ unsubscribe();
793
+ ```
794
+
795
+ **Returns**
796
+
797
+ ```ts
798
+ () => void
799
+ ```
800
+
801
+
802
+ ```ts
803
+ // call to remove the listener
804
+ unsubscribe();
805
+ ```
806
+
807
+ ---
808
+
809
+ ## geolocation
810
+
811
+ [`value`](#geolocation-value) · [`onChange`](#geolocation-onchange)
812
+
813
+ <h3 id="geolocation-value"><code>geolocation.value</code></h3>
814
+
815
+ **Signature**
816
+
817
+ ```ts
818
+ get value(): Promise<GeolocationCoordinates>
819
+ ```
820
+
821
+ Returns the current geographic coordinates. Falls back to IP-based location if the Geolocation API is unavailable or permission is denied.
822
+
823
+ **Flowchart**
824
+
825
+ ```mermaid
826
+ flowchart TD
827
+ A([geolocation.value called]) --> B{navigator.geolocation supported?}
828
+ B -->|no| F[Fallback to ip-api.com]
829
+ B -->|yes| C[Permission.request geolocation]
830
+ C --> D{Permission granted?}
831
+ D -->|no| F
832
+ D -->|yes| E[getCurrentPosition]
833
+ E --> G{Success?}
834
+ G -->|yes| H([Resolve GeolocationCoordinates])
835
+ G -->|no| F
836
+ F --> I{ip-api.com responded?}
837
+ I -->|yes| J([Resolve approximate coordinates])
838
+ I -->|no| K([Throw original error])
839
+ ```
840
+
841
+ **Example**
842
+
843
+ ```ts
844
+ const coords = await Native.geolocation.value;
845
+
846
+ console.log(coords.latitude, coords.longitude);
847
+ console.log(coords.accuracy); // -1 when resolved via IP fallback
848
+ ```
849
+
850
+ **Returns**
851
+
852
+ ```ts
853
+ Promise<GeolocationCoordinates>
854
+ ```
855
+
856
+
857
+ ```ts
858
+ interface GeolocationCoordinates {
859
+ readonly latitude: number;
860
+ readonly longitude: number;
861
+ readonly accuracy: number; // -1 when resolved via IP fallback
862
+ readonly altitude: number | null;
863
+ readonly altitudeAccuracy: number | null;
864
+ readonly heading: number | null;
865
+ readonly speed: number | null;
866
+ }
867
+ ```
868
+
869
+ **Throws**
870
+
871
+ ```ts
872
+ throw new NotSupportedError // geolocation unavailable and IP fallback failed
873
+ ```
874
+ ```ts
875
+ throw new PermissionNotGrantedError // permission denied and IP fallback failed
876
+ ```
877
+
878
+ ---
879
+
880
+ <h3 id="geolocation-onchange"><code>geolocation.onChange</code></h3>
881
+
882
+ **Signature**
883
+
884
+ ```ts
885
+ onChange(listener: (coordinates: GeolocationCoordinates) => void, options?: AddEventListenerOptions): () => void
886
+ ```
887
+
888
+ Subscribes to geographic position changes.
889
+
890
+ **Example**
891
+
892
+ ```ts
893
+ const unsubscribe = Native.geolocation.onChange((coords) => {
894
+ console.log(coords.latitude, coords.longitude);
895
+ console.log(coords.accuracy); // meters
896
+ });
897
+
898
+ unsubscribe();
899
+ ```
900
+
901
+ **Returns**
902
+
903
+ ```ts
904
+ () => void
905
+ ```
906
+
907
+
908
+ ```ts
909
+ // call to remove the listener
910
+ unsubscribe();
911
+ ```
912
+
913
+ ---
914
+
915
+ ## notification
916
+
917
+ [`send`](#notification-send)
918
+
919
+ <h3 id="notification-send"><code>notification.send</code></h3>
920
+
921
+ **Signature**
922
+
923
+ ```ts
924
+ send(options: NotificationOptions): Promise<Notification>
925
+ ```
926
+
927
+ Sends a native notification after requesting permission.
928
+
929
+ **Example**
930
+
931
+ ```ts
932
+ // Basic notification
933
+ const notification = await Native.notification.send({
934
+ title: 'Hello',
935
+ body: 'You have a new message.',
936
+ icon: '/icon.png',
937
+ });
938
+
939
+ // With event handlers
940
+ const notification = await Native.notification.send({
941
+ title: 'Download complete',
942
+ body: 'your-file.zip is ready.',
943
+ onClick: () => window.focus(),
944
+ onClose: () => console.log('dismissed'),
945
+ });
946
+
947
+ // Close the notification programmatically.
948
+ notification.close();
949
+ ```
950
+
951
+ **Returns**
952
+
953
+ ```ts
954
+ Promise<Notification>
955
+ ```
956
+
957
+
958
+ ```ts
959
+ interface NotificationOptions {
960
+ title: string;
961
+ badge?: string;
962
+ body?: string;
963
+ data?: any;
964
+ dir?: NotificationDirection;
965
+ icon?: string;
966
+ lang?: string;
967
+ requireInteraction?: boolean;
968
+ silent?: boolean | null;
969
+ tag?: string;
970
+ onClick?: (event: Event) => any;
971
+ onClose?: (event: Event) => any;
972
+ onError?: (event: Event) => any;
973
+ onShow?: (event: Event) => any;
974
+ }
975
+ ```
976
+
977
+ **Throws**
978
+
979
+ ```ts
980
+ throw new NotSupportedError // window.Notification unavailable
981
+ ```
982
+ ```ts
983
+ throw new PermissionNotGrantedError // notification permission denied
984
+ ```
985
+
986
+ ---
987
+
988
+ ## open
989
+
990
+ [`app`](#open-app) · [`telephone`](#open-telephone) · [`message`](#open-message) · [`mail`](#open-mail) · [`file`](#open-file) · [`directory`](#open-directory) · [`setting`](#open-setting) · [`camera`](#open-camera) · [`contact`](#open-contact) · [`share`](#open-share) · [`calendar`](#open-calendar)
991
+
992
+ <h3 id="open-app"><code>open.app</code></h3>
993
+
994
+ **Signature**
995
+
996
+ ```ts
997
+ app(options: AppOpenOptions): Promise<AppOpenState>
998
+ ```
999
+
1000
+ Opens a native app. Falls back through multiple URL strategies until one succeeds.
1001
+
1002
+ **Flowchart**
1003
+
1004
+ ```mermaid
1005
+ flowchart TD
1006
+ A([Native.open.app called]) --> B[Detect current OS]
1007
+ B --> C[Extract app info by OS]
1008
+ C --> D[Build URL priority list]
1009
+ D --> E[tryOpenURL sequentially]
1010
+ E --> F{Success?}
1011
+ F -->|yes| G([Resolve AppOpenState])
1012
+ F -->|no| H{URLs remaining?}
1013
+ H -->|yes| E
1014
+ H -->|no| I([Throw URLOpenError])
1015
+
1016
+ subgraph tryOpenURL
1017
+ T1([Register blur and focus events]) --> T2[Open URL via href / iframe / cordova]
1018
+ T2 --> T3{blur fired?}
1019
+ T3 -->|yes| T4([resolve])
1020
+ T3 -->|no| T5([reject via timeout])
1021
+ end
1022
+
1023
+ E -.->|delegates to| T1
1024
+ ```
1025
+
1026
+ **Example**
1027
+
1028
+ ```ts
1029
+ try {
1030
+ const result = await Native.open.app({
1031
+ android: {
1032
+ scheme: 'ms-excel://',
1033
+ packageName: 'com.microsoft.office.excel',
1034
+ allowAppStore: true,
1035
+ // allowWebStore: false,
1036
+ // intent: 'intent://#Intent;scheme=ms-excel;...',
1037
+ // fallback: 'https://www.microsoft.com/ko-kr/microsoft-365/excel',
1038
+ // timeout: 1000,
1039
+ },
1040
+ ios: {
1041
+ scheme: 'ms-excel://',
1042
+ trackId: '586683407',
1043
+ allowAppStore: true,
1044
+ // allowWebStore: false,
1045
+ // universal: '',
1046
+ // bundleId: 'com.microsoft.Office.Excel',
1047
+ // fallback: 'https://www.microsoft.com/ko-kr/microsoft-365/excel',
1048
+ // timeout: 2000,
1049
+ },
1050
+ windows: {
1051
+ scheme: 'ms-excel://',
1052
+ productId: 'cfq7ttc0pr28',
1053
+ allowAppStore: true,
1054
+ // allowWebStore: false,
1055
+ // fallback: 'https://www.microsoft.com/ko-kr/microsoft-365/excel',
1056
+ // timeout: 750,
1057
+ },
1058
+ macos: {
1059
+ scheme: 'ms-excel://',
1060
+ trackId: '462058435',
1061
+ allowAppStore: true,
1062
+ // allowWebStore: false,
1063
+ // bundleId: 'com.microsoft.Excel',
1064
+ // fallback: 'https://www.microsoft.com/ko-kr/microsoft-365/excel',
1065
+ // timeout: 750,
1066
+ },
1067
+ });
1068
+
1069
+ switch (result) {
1070
+ AppOpenState.Intent:
1071
+ console.log('Opened via Android intent.'); break;
1072
+ AppOpenState.Universal:
1073
+ console.log('Opened via Universal Link.'); break;
1074
+ AppOpenState.Scheme:
1075
+ console.log('Opened via custom scheme.'); break;
1076
+ AppOpenState.Fallback:
1077
+ console.log('Opened via fallback URL.'); break;
1078
+ AppOpenState.Store:
1079
+ console.log('Redirected to App Store.'); break;
1080
+ }
1081
+ } catch (e) {
1082
+ if (e instanceof Native.open.Errors.URLOpenError) {
1083
+ console.error('All URLs exhausted:', e.message);
1084
+ }
1085
+ }
1086
+ ```
1087
+
1088
+ **Returns**
1089
+
1090
+ ```ts
1091
+ Promise<AppOpenState>
1092
+ ```
1093
+
1094
+
1095
+ ```ts
1096
+ enum AppOpenState {
1097
+ Intent = 'Intent',
1098
+ Universal = 'Universal',
1099
+ Scheme = 'Scheme',
1100
+ Fallback = 'Fallback',
1101
+ Store = 'Store',
1102
+ }
1103
+ ```
1104
+
1105
+ **Throws**
1106
+
1107
+ ```ts
1108
+ throw new URLOpenError // all candidate URLs were tried and none succeeded
1109
+ ```
1110
+
1111
+ ---
1112
+
1113
+ <h3 id="open-telephone"><code>open.telephone</code></h3>
1114
+
1115
+ **Signature**
1116
+
1117
+ ```ts
1118
+ telephone(options: TelephoneOptions): Promise<void>
1119
+ ```
1120
+
1121
+ Opens the native phone dialer.
1122
+
1123
+ **Example**
1124
+
1125
+ ```ts
1126
+ await Native.open.telephone({ to: '+821012345678' });
1127
+ ```
1128
+
1129
+ **Returns**
1130
+
1131
+ ```ts
1132
+ Promise<void>
1133
+ ```
1134
+
1135
+
1136
+ **Throws**
1137
+
1138
+ ```ts
1139
+ throw new URLOpenError // failed to open the phone dialer
1140
+ ```
1141
+
1142
+ ---
1143
+
1144
+ <h3 id="open-message"><code>open.message</code></h3>
1145
+
1146
+ **Signature**
1147
+
1148
+ ```ts
1149
+ message(options: MessageOptions): Promise<void>
1150
+ ```
1151
+
1152
+ Opens the native SMS app.
1153
+
1154
+ **Example**
1155
+
1156
+ ```ts
1157
+ // With pre-filled body
1158
+ await Native.open.message({
1159
+ to: '+821012345678',
1160
+ body: 'Hello from Native.open!',
1161
+ });
1162
+ ```
1163
+
1164
+ **Returns**
1165
+
1166
+ ```ts
1167
+ Promise<void>
1168
+ ```
1169
+
1170
+
1171
+ **Throws**
1172
+
1173
+ ```ts
1174
+ throw new URLOpenError // failed to open the SMS app
1175
+ ```
1176
+
1177
+ ---
1178
+
1179
+ <h3 id="open-mail"><code>open.mail</code></h3>
1180
+
1181
+ **Signature**
1182
+
1183
+ ```ts
1184
+ mail(options: MailOptions): Promise<void>
1185
+ ```
1186
+
1187
+ Opens the native mail client.
1188
+
1189
+ **Example**
1190
+
1191
+ ```ts
1192
+ // Single recipient
1193
+ await Native.open.mail({
1194
+ to: 'hello@example.com',
1195
+ subject: 'Greetings',
1196
+ body: 'Hi there!',
1197
+ });
1198
+
1199
+ // Multiple recipients with cc / bcc
1200
+ await Native.open.mail({
1201
+ to: ['hello@example.com', 'world@example.com'],
1202
+ cc: 'cc@example.com',
1203
+ bcc: 'bcc@example.com',
1204
+ subject: 'Greetings',
1205
+ body: 'Hi there!',
1206
+ });
1207
+ ```
1208
+
1209
+ **Returns**
1210
+
1211
+ ```ts
1212
+ Promise<void>
1213
+ ```
1214
+
1215
+
1216
+ **Throws**
1217
+
1218
+ ```ts
1219
+ throw new URLOpenError // failed to open the mail client
1220
+ ```
1221
+
1222
+ ---
1223
+
1224
+ <h3 id="open-file"><code>open.file</code></h3>
1225
+
1226
+ **Signature**
1227
+
1228
+ ```ts
1229
+ file(options?: FileOptions): Promise<File[]>
1230
+ ```
1231
+
1232
+ Opens a file picker dialog.
1233
+
1234
+ **Example**
1235
+
1236
+ ```ts
1237
+ // Single file
1238
+ const [file] = await Native.open.file({
1239
+ accept: ['.pdf'],
1240
+ });
1241
+
1242
+ // Multiple files with type filter
1243
+ const files = await Native.open.file({
1244
+ multiple: true,
1245
+ accept: ['.png', '.jpg', 'image/webp'],
1246
+ startIn: ExplorerStartIn.Pictures,
1247
+ });
1248
+ ```
1249
+
1250
+ **Returns**
1251
+
1252
+ ```ts
1253
+ Promise<File[]>
1254
+ ```
1255
+
1256
+
1257
+ **Throws**
1258
+
1259
+ ```ts
1260
+ throw new UserCancelledError // user dismissed the picker
1261
+ ```
1262
+ ```ts
1263
+ throw new NotSupportedError // showOpenFilePicker and input fallback both unavailable
1264
+ ```
1265
+
1266
+ ---
1267
+
1268
+ <h3 id="open-directory"><code>open.directory</code></h3>
1269
+
1270
+ **Signature**
1271
+
1272
+ ```ts
1273
+ directory(options?: DirectoryOptions): Promise<FileWithPath[]>
1274
+ ```
1275
+
1276
+ Opens a directory picker and returns all files with their relative paths.
1277
+
1278
+ **Example**
1279
+
1280
+ ```ts
1281
+ // Read-only
1282
+ const entries = await Native.open.directory();
1283
+
1284
+ // Read-write
1285
+ const entries = await Native.open.directory({
1286
+ mode: DirectoryExploreMode.ReadWrite,
1287
+ });
1288
+
1289
+ entries.forEach(({ file, relativePath }) => {
1290
+ console.log(relativePath, file.size); // 'src/index.ts', 1024
1291
+ });
1292
+ ```
1293
+
1294
+ **Returns**
1295
+
1296
+ ```ts
1297
+ Promise<FileWithPath[]>
1298
+ ```
1299
+
1300
+
1301
+ ```ts
1302
+ interface FileWithPath {
1303
+ file: File;
1304
+ relativePath: string;
1305
+ }
1306
+ ```
1307
+
1308
+ **Throws**
1309
+
1310
+ ```ts
1311
+ throw new NotSupportedError // showDirectoryPicker and webkitdirectory both unavailable
1312
+ ```
1313
+ ```ts
1314
+ throw new UserCancelledError // user dismissed the picker
1315
+ ```
1316
+
1317
+ ---
1318
+
1319
+ <h3 id="open-setting"><code>open.setting</code></h3>
1320
+
1321
+ **Signature**
1322
+
1323
+ ```ts
1324
+ setting(type: SettingType): Promise<void>
1325
+ ```
1326
+
1327
+ Opens a system settings screen. iOS is unsupported.
1328
+
1329
+ **Example**
1330
+
1331
+ ```ts
1332
+ // General settings
1333
+ await Native.open.setting(SettingType.General);
1334
+
1335
+ // Accessibility settings
1336
+ await Native.open.setting(SettingType.Accessibility);
1337
+
1338
+ // Battery settings (Android 5.1+)
1339
+ await Native.open.setting(SettingType.Battery);
1340
+ ```
1341
+
1342
+ **Returns**
1343
+
1344
+ ```ts
1345
+ Promise<void>
1346
+ ```
1347
+
1348
+
1349
+ **Throws**
1350
+
1351
+ ```ts
1352
+ throw new URLOpenError // canOpenSetting() returned false
1353
+ ```
1354
+ ```ts
1355
+ throw new URLOpenError // all setting URLs failed
1356
+ ```
1357
+
1358
+ ---
1359
+
1360
+ <h3 id="open-camera"><code>open.camera</code></h3>
1361
+
1362
+ **Signature**
1363
+
1364
+ ```ts
1365
+ camera(options?: CameraOptions): Promise<File[]>
1366
+ ```
1367
+
1368
+ Opens the device camera.
1369
+
1370
+ **Example**
1371
+
1372
+ ```ts
1373
+ // Rear-facing photo (default)
1374
+ const [photo] = await Native.open.camera();
1375
+
1376
+ // Front-facing video
1377
+ const [video] = await Native.open.camera({
1378
+ type: CameraType.Video,
1379
+ capture: CaptureType.User,
1380
+ });
1381
+ ```
1382
+
1383
+ **Returns**
1384
+
1385
+ ```ts
1386
+ Promise<File[]>
1387
+ ```
1388
+
1389
+
1390
+ **Throws**
1391
+
1392
+ ```ts
1393
+ throw new UserCancelledError // user dismissed the camera UI
1394
+ ```
1395
+
1396
+ ---
1397
+
1398
+ <h3 id="open-contact"><code>open.contact</code></h3>
1399
+
1400
+ **Signature**
1401
+
1402
+ ```ts
1403
+ contact(options?: ContactOptions): Promise<Contact[]>
1404
+ ```
1405
+
1406
+ Opens the native contact picker.
1407
+
1408
+ **Example**
1409
+
1410
+ ```ts
1411
+ // Single contact
1412
+ const [contact] = await Native.open.contact();
1413
+ console.log(contact.name, contact.tel);
1414
+
1415
+ // Multiple contacts
1416
+ const contacts = await Native.open.contact({ multiple: true });
1417
+ contacts.forEach((c) => console.log(c.name, c.email));
1418
+ ```
1419
+
1420
+ **Returns**
1421
+
1422
+ ```ts
1423
+ Promise<Contact[]>
1424
+ ```
1425
+
1426
+
1427
+ ```ts
1428
+ interface Contact {
1429
+ name?: string;
1430
+ email?: string;
1431
+ tel?: string;
1432
+ address?: string;
1433
+ icon?: Blob[];
1434
+ }
1435
+ ```
1436
+
1437
+ **Throws**
1438
+
1439
+ ```ts
1440
+ throw new NotSupportedError // navigator.contacts unavailable
1441
+ ```
1442
+
1443
+ ---
1444
+
1445
+ <h3 id="open-share"><code>open.share</code></h3>
1446
+
1447
+ **Signature**
1448
+
1449
+ ```ts
1450
+ share(options: ShareData): Promise<void>
1451
+ ```
1452
+
1453
+ Opens the native OS share sheet.
1454
+
1455
+ **Example**
1456
+
1457
+ ```ts
1458
+ // Share a URL
1459
+ await Native.open.share({
1460
+ title: 'Check this out',
1461
+ url: 'https://example.com',
1462
+ });
1463
+
1464
+ // Share text and URL
1465
+ await Native.open.share({
1466
+ title: 'Check this out',
1467
+ text: 'Shared via Native.open',
1468
+ url: 'https://example.com',
1469
+ });
1470
+ ```
1471
+
1472
+ **Returns**
1473
+
1474
+ ```ts
1475
+ Promise<void>
1476
+ ```
1477
+
1478
+
1479
+ **Throws**
1480
+
1481
+ ```ts
1482
+ throw new NotSupportedError // navigator.share unavailable or canShare() false
1483
+ ```
1484
+ ```ts
1485
+ throw new UserCancelledError // user dismissed the share sheet
1486
+ ```
1487
+
1488
+ ---
1489
+
1490
+ <h3 id="open-calendar"><code>open.calendar</code></h3>
1491
+
1492
+ **Signature**
1493
+
1494
+ ```ts
1495
+ calendar(options: CalendarOptions): void
1496
+ ```
1497
+
1498
+ Generates an RFC 5545 .ics file and triggers a download to open in the default calendar app.
1499
+
1500
+ **Example**
1501
+
1502
+ ```ts
1503
+ // Basic event
1504
+ Native.open.calendar({
1505
+ title: 'Team Sync',
1506
+ description: 'Weekly alignment meeting',
1507
+ location: 'Seoul, Korea',
1508
+ startDate: new Date('2026-04-01T10:00:00Z'),
1509
+ endDate: new Date('2026-04-01T11:00:00Z'),
1510
+ });
1511
+
1512
+ // Recurring event with alarm
1513
+ Native.open.calendar({
1514
+ title: 'Weekly Standup',
1515
+ startDate: new Date('2026-04-01T09:00:00Z'),
1516
+ endDate: new Date('2026-04-01T09:15:00Z'),
1517
+ alarm: [{ minutes: 10, before: true }],
1518
+ recur: { frequency: 'WEEKLY', byDay: ['MO'], count: 12 },
1519
+ });
1520
+
1521
+ // All-day event
1522
+ Native.open.calendar({
1523
+ title: 'Company Holiday',
1524
+ allDay: true,
1525
+ startDate: new Date('2026-05-05T00:00:00Z'),
1526
+ endDate: new Date('2026-05-05T00:00:00Z'),
1527
+ });
1528
+ ```
1529
+
1530
+ **Returns**
1531
+
1532
+ ```ts
1533
+ void
1534
+ ```
1535
+
1536
+
1537
+ ---
1538
+
1539
+ ## permission
1540
+
1541
+ [`request`](#permission-request) · [`check`](#permission-check)
1542
+
1543
+ <h3 id="permission-request"><code>permission.request</code></h3>
1544
+
1545
+ **Signature**
1546
+
1547
+ ```ts
1548
+ request(type: PermissionType): Promise<PermissionState>
1549
+ ```
1550
+
1551
+ Requests a permission. Resolves immediately if already granted.
1552
+
1553
+ **Flowchart**
1554
+
1555
+ ```mermaid
1556
+ flowchart TD
1557
+ A([permission.request called]) --> B[check current state]
1558
+ B --> C{Already granted?}
1559
+ C -->|yes| D([Resolve Grant])
1560
+ C -->|no| E{PermissionType?}
1561
+ E -->|Notification| F[Notification.requestPermission]
1562
+ E -->|Geolocation| G[getCurrentPosition to trigger prompt]
1563
+ E -->|Camera| H[getUserMedia video=true]
1564
+ E -->|ClipboardRead| I[clipboard.read]
1565
+ E -->|Microphone| J[getUserMedia audio=true]
1566
+ E -->|MIDI| k[requestMIDIAccess]
1567
+ E -->|unknown| L([Resolve Unsupported])
1568
+ F & G & H & I & J --> M[check state again]
1569
+ M --> N([Resolve PermissionState])
1570
+ ```
1571
+
1572
+ **Example**
1573
+
1574
+ ```ts
1575
+ const state = await Native.permission.request(PermissionType.Notification);
1576
+
1577
+ switch (state) {
1578
+ case PermissionState.Grant:
1579
+ console.log('Permission granted.'); break;
1580
+ case PermissionState.Denied:
1581
+ console.log('Permission denied.'); break;
1582
+ case PermissionState.Prompt:
1583
+ console.log('Not yet decided.'); break;
1584
+ case PermissionState.Unsupported:
1585
+ console.log('Not supported.'); break;
1586
+ }
1587
+ ```
1588
+
1589
+ **Returns**
1590
+
1591
+ ```ts
1592
+ Promise<PermissionState>
1593
+ ```
1594
+
1595
+
1596
+ ```ts
1597
+ enum PermissionType {
1598
+ Notification = 'notifications',
1599
+ Geolocation = 'geolocation',
1600
+ Camera = 'camera',
1601
+ ClipboardRead = 'clipboard-read',
1602
+ Microphone = 'microphone',
1603
+ MIDI = 'midi',
1604
+ }
1605
+
1606
+ enum PermissionState {
1607
+ Grant = 'grant',
1608
+ Denied = 'denied',
1609
+ Prompt = 'prompt',
1610
+ Unsupported = 'unsupported',
1611
+ }
1612
+ ```
1613
+
1614
+ ---
1615
+
1616
+ <h3 id="permission-check"><code>permission.check</code></h3>
1617
+
1618
+ **Signature**
1619
+
1620
+ ```ts
1621
+ check(type: PermissionType): Promise<PermissionState>
1622
+ ```
1623
+
1624
+ Returns the current permission state without triggering a prompt.
1625
+
1626
+ **Example**
1627
+
1628
+ ```ts
1629
+ // Check before accessing a feature
1630
+ const state = await Native.permission.check(PermissionType.Geolocation);
1631
+
1632
+ if (state === PermissionState.Grant) {
1633
+ const coords = await Native.geolocation.value;
1634
+ }
1635
+ ```
1636
+
1637
+ **Returns**
1638
+
1639
+ ```ts
1640
+ Promise<PermissionState>
1641
+ ```
1642
+
1643
+
1644
+ ```ts
1645
+ enum PermissionState {
1646
+ Grant = 'grant',
1647
+ Denied = 'denied',
1648
+ Prompt = 'prompt',
1649
+ Unsupported = 'unsupported',
1650
+ }
1651
+ ```
1652
+
1653
+ ---
1654
+
1655
+ ## pip
1656
+
1657
+ [`request`](#pip-request) · [`exit`](#pip-exit) · [`toggle`](#pip-toggle) · [`onChange`](#pip-onchange) · [`onError`](#pip-onerror)
1658
+
1659
+ <h3 id="pip-request"><code>pip.request</code></h3>
1660
+
1661
+ **Signature**
1662
+
1663
+ ```ts
1664
+ request(target?: HTMLVideoElement): Promise<void>
1665
+ ```
1666
+
1667
+ Requests Picture-in-Picture for a video element. Concurrent calls are queued (FIFO).
1668
+
1669
+ **Flowchart**
1670
+
1671
+ ```mermaid
1672
+ flowchart TD
1673
+ A([Pip.request called]) --> B[Set lastIntendedOperation to request]
1674
+ B --> C{activeOperation in progress?}
1675
+ C -->|no| D[requestImmediately]
1676
+ C -->|yes| E[Push to pendingQueue]
1677
+ D --> F{target is video element?}
1678
+ F -->|no| Z([Throw NotSupportedError])
1679
+ F -->|yes| G{requestPictureInPicture available?}
1680
+ G -->|yes| H[Call video.requestPictureInPicture]
1681
+ G -->|no| I[fallbackToWebkitVideo]
1682
+ H --> J{Resolved?}
1683
+ J -->|yes| K([resolve])
1684
+ J -->|no| I
1685
+ I --> L{webkitSupportsPresentationMode PIP?}
1686
+ L -->|yes| M[webkitSetPresentationMode picture-in-picture]
1687
+ M --> K
1688
+ L -->|no| N([Throw NotSupportedError])
1689
+ D --> O[drainPendingOperation on settle]
1690
+ ```
1691
+
1692
+ **Example**
1693
+
1694
+ ```ts
1695
+ // Default: first video element
1696
+ await Native.pip.request();
1697
+
1698
+ // Specific video element
1699
+ await Native.pip.request(document.querySelector('video#player'));
1700
+
1701
+ // Concurrent calls — safely queued, not dropped
1702
+ Native.pip.request(document.querySelector('video#a'));
1703
+ Native.pip.request(document.querySelector('video#b'));
1704
+ ```
1705
+
1706
+ **Returns**
1707
+
1708
+ ```ts
1709
+ Promise<void>
1710
+ ```
1711
+
1712
+
1713
+ **Throws**
1714
+
1715
+ ```ts
1716
+ throw new NotSupportedError // target is not a video element
1717
+ ```
1718
+ ```ts
1719
+ throw new NotSupportedError // PiP disabled on this element (disablePictureInPicture)
1720
+ ```
1721
+ ```ts
1722
+ throw new NotSupportedError // requestPictureInPicture and webkitSetPresentationMode both unavailable
1723
+ ```
1724
+
1725
+ ---
1726
+
1727
+ <h3 id="pip-exit"><code>pip.exit</code></h3>
1728
+
1729
+ **Signature**
1730
+
1731
+ ```ts
1732
+ exit(): Promise<void>
1733
+ ```
1734
+
1735
+ Exits Picture-in-Picture. Concurrent calls are queued (FIFO).
1736
+
1737
+ **Example**
1738
+
1739
+ ```ts
1740
+ await Native.pip.exit();
1741
+ ```
1742
+
1743
+ **Returns**
1744
+
1745
+ ```ts
1746
+ Promise<void>
1747
+ ```
1748
+
1749
+
1750
+ **Throws**
1751
+
1752
+ ```ts
1753
+ throw new NotSupportedError // failed to exit PiP
1754
+ ```
1755
+
1756
+ ---
1757
+
1758
+ <h3 id="pip-toggle"><code>pip.toggle</code></h3>
1759
+
1760
+ **Signature**
1761
+
1762
+ ```ts
1763
+ toggle(target?: HTMLVideoElement): Promise<void>
1764
+ ```
1765
+
1766
+ Toggles Picture-in-Picture on or off.
1767
+
1768
+ **Example**
1769
+
1770
+ ```ts
1771
+ // Toggle on button click
1772
+ btn.addEventListener('click', () => Native.pip.toggle());
1773
+
1774
+ // Toggle a specific video element
1775
+ btn.addEventListener('click', () => {
1776
+ Native.pip.toggle(document.querySelector('video#player'));
1777
+ });
1778
+ ```
1779
+
1780
+ **Returns**
1781
+
1782
+ ```ts
1783
+ Promise<void>
1784
+ ```
1785
+
1786
+
1787
+ **Throws**
1788
+
1789
+ ```ts
1790
+ throw new NotSupportedError // propagated from request() or exit()
1791
+ ```
1792
+
1793
+ ---
1794
+
1795
+ <h3 id="pip-onchange"><code>pip.onChange</code></h3>
1796
+
1797
+ **Signature**
1798
+
1799
+ ```ts
1800
+ onChange(listener: (event: Event) => void, options?: AddEventListenerOptions): () => void
1801
+ ```
1802
+
1803
+ Subscribes to Picture-in-Picture state changes.
1804
+
1805
+ **Example**
1806
+
1807
+ ```ts
1808
+ const unsubscribe = Native.pip.onChange(() => {
1809
+ console.log('isPip:', Native.pip.isPip);
1810
+ console.log('element:', Native.pip.element);
1811
+ });
1812
+
1813
+ unsubscribe();
1814
+ ```
1815
+
1816
+ **Returns**
1817
+
1818
+ ```ts
1819
+ () => void
1820
+ ```
1821
+
1822
+
1823
+ ```ts
1824
+ // call to remove the listener
1825
+ unsubscribe();
1826
+ ```
1827
+
1828
+ ---
1829
+
1830
+ <h3 id="pip-onerror"><code>pip.onError</code></h3>
1831
+
1832
+ **Signature**
1833
+
1834
+ ```ts
1835
+ onError(listener: (event: Event) => void, options?: AddEventListenerOptions): () => void
1836
+ ```
1837
+
1838
+ Subscribes to Picture-in-Picture errors.
1839
+
1840
+ **Example**
1841
+
1842
+ ```ts
1843
+ const unsubscribe = Native.pip.onError((event) => {
1844
+ console.error('PiP error:', event);
1845
+ });
1846
+
1847
+ unsubscribe();
1848
+ ```
1849
+
1850
+ **Returns**
1851
+
1852
+ ```ts
1853
+ () => void
1854
+ ```
1855
+
1856
+
1857
+ ```ts
1858
+ // call to remove the listener
1859
+ unsubscribe();
1860
+ ```
1861
+
1862
+ ---
1863
+
1864
+ ## platform
1865
+
1866
+ [`os`](#platform-os) · [`browser`](#platform-browser) · [`engine`](#platform-engine) · [`device`](#platform-device) · [`locale`](#platform-locale) · [`gpu`](#platform-gpu) · [`userAgent`](#platform-useragent) · [`ready`](#platform-ready) · [`isWebview`](#platform-iswebview) · [`isNode`](#platform-isnode) · [`isStandalone`](#platform-isstandalone)
1867
+
1868
+ <h3 id="platform-os"><code>platform.os</code></h3>
1869
+
1870
+ **Signature**
1871
+
1872
+ ```ts
1873
+ get os(): NameVersionPair<OS>
1874
+ ```
1875
+
1876
+ Returns the detected OS name and version.
1877
+
1878
+ **Example**
1879
+
1880
+ ```ts
1881
+ const { name, version } = Native.platform.os;
1882
+
1883
+ switch (name) {
1884
+ case OS.iOS:
1885
+ console.log('iOS', version); break;
1886
+ case OS.Android:
1887
+ console.log('Android', version); break;
1888
+ case OS.Windows:
1889
+ console.log('Windows', version); break;
1890
+ case OS.MacOS:
1891
+ console.log('macOS', version); break;
1892
+ }
1893
+ ```
1894
+
1895
+ **Returns**
1896
+
1897
+ ```ts
1898
+ NameVersionPair<OS>
1899
+ ```
1900
+
1901
+
1902
+ ```ts
1903
+ interface NameVersionPair<T> {
1904
+ name: T;
1905
+ version: string;
1906
+ }
1907
+
1908
+ enum OS {
1909
+ Unknown = 'Unknown',
1910
+ Android = 'Android',
1911
+ iOS = 'iOS',
1912
+ Windows = 'Windows',
1913
+ MacOS = 'MacOS',
1914
+ }
1915
+ ```
1916
+
1917
+ ---
1918
+
1919
+ <h3 id="platform-browser"><code>platform.browser</code></h3>
1920
+
1921
+ **Signature**
1922
+
1923
+ ```ts
1924
+ get browser(): NameVersionPair<Browsers>
1925
+ ```
1926
+
1927
+ Returns the detected browser name and version.
1928
+
1929
+ **Example**
1930
+
1931
+ ```ts
1932
+ const { name, version } = Native.platform.browser;
1933
+
1934
+ if (name === Browsers.Safari) {
1935
+ console.log('Safari', version); // e.g. '17.0'
1936
+ }
1937
+ ```
1938
+
1939
+ **Returns**
1940
+
1941
+ ```ts
1942
+ NameVersionPair<Browsers>
1943
+ ```
1944
+
1945
+
1946
+ ```ts
1947
+ enum Browsers {
1948
+ Unknown = 'Unknown',
1949
+ Chrome = 'Chrome',
1950
+ Safari = 'Safari',
1951
+ Edge = 'Edge',
1952
+ Firefox = 'Firefox',
1953
+ Opera = 'Opera',
1954
+ IE = 'IE',
1955
+ SamsungInternet = 'SamsungInternet',
1956
+ }
1957
+ ```
1958
+
1959
+ ---
1960
+
1961
+ <h3 id="platform-engine"><code>platform.engine</code></h3>
1962
+
1963
+ **Signature**
1964
+
1965
+ ```ts
1966
+ get engine(): NameVersionPair<Engines>
1967
+ ```
1968
+
1969
+ Returns the detected rendering engine name and version.
1970
+
1971
+ **Example**
1972
+
1973
+ ```ts
1974
+ const { name, version } = Native.platform.engine;
1975
+
1976
+ if (name === Engines.Blink) {
1977
+ console.log('Blink', version); // e.g. '120.0.6099.62'
1978
+ }
1979
+ ```
1980
+
1981
+ **Returns**
1982
+
1983
+ ```ts
1984
+ NameVersionPair<Engines>
1985
+ ```
1986
+
1987
+
1988
+ ```ts
1989
+ enum Engines {
1990
+ Unknown = 'Unknown',
1991
+ EdgeHTML = 'EdgeHTML',
1992
+ ArkWeb = 'ArkWeb',
1993
+ Blink = 'Blink',
1994
+ Presto = 'Presto',
1995
+ WebKit = 'WebKit',
1996
+ Trident = 'Trident',
1997
+ Gecko = 'Gecko',
1998
+ }
1999
+ ```
2000
+
2001
+ ---
2002
+
2003
+ <h3 id="platform-device"><code>platform.device</code></h3>
2004
+
2005
+ **Signature**
2006
+
2007
+ ```ts
2008
+ get device(): Devices
2009
+ ```
2010
+
2011
+ Returns the device category: Mobile, Desktop, or Unknown.
2012
+
2013
+ **Example**
2014
+
2015
+ ```ts
2016
+ if (Native.platform.device === Devices.Mobile) {
2017
+ console.log('Running on a mobile device');
2018
+ }
2019
+ ```
2020
+
2021
+ **Returns**
2022
+
2023
+ ```ts
2024
+ Devices
2025
+ ```
2026
+
2027
+
2028
+ ```ts
2029
+ enum Devices {
2030
+ Unknown = 'Unknown',
2031
+ Mobile = 'Mobile',
2032
+ Desktop = 'Desktop',
2033
+ }
2034
+ ```
2035
+
2036
+ ---
2037
+
2038
+ <h3 id="platform-locale"><code>platform.locale</code></h3>
2039
+
2040
+ **Signature**
2041
+
2042
+ ```ts
2043
+ get locale(): Locale
2044
+ ```
2045
+
2046
+ Returns the current locale, timezone, UTC offset, and text direction.
2047
+
2048
+ **Example**
2049
+
2050
+ ```ts
2051
+ const { language, languages, timezone, offset, isRTL } = Native.platform.locale;
2052
+
2053
+ console.log(language); // 'ko-KR'
2054
+ console.log(languages); // ['ko-KR', 'en-US']
2055
+ console.log(timezone); // 'Asia/Seoul'
2056
+ console.log(offset); // 540 (UTC+9 in minutes)
2057
+ console.log(isRTL); // false
2058
+ ```
2059
+
2060
+ **Returns**
2061
+
2062
+ ```ts
2063
+ Locale
2064
+ ```
2065
+
2066
+
2067
+ ```ts
2068
+ interface Locale {
2069
+ language: string | null;
2070
+ languages: string[];
2071
+ timezone: string | null;
2072
+ offset: number;
2073
+ isRTL: boolean;
2074
+ }
2075
+ ```
2076
+
2077
+ ---
2078
+
2079
+ <h3 id="platform-gpu"><code>platform.gpu</code></h3>
2080
+
2081
+ **Signature**
2082
+
2083
+ ```ts
2084
+ get gpu(): GPU
2085
+ ```
2086
+
2087
+ Returns GPU information. Await Native.platform.ready for guaranteed complete data.
2088
+
2089
+ **Example**
2090
+
2091
+ ```ts
2092
+ // Guaranteed complete data
2093
+ await Native.platform.ready;
2094
+ const { vendor, architecture, device, description } = Native.platform.gpu;
2095
+
2096
+ console.log(vendor); // 'apple'
2097
+ console.log(architecture); // 'common-3'
2098
+ console.log(device); // 'Apple M2'
2099
+ ```
2100
+
2101
+ **Returns**
2102
+
2103
+ ```ts
2104
+ GPU
2105
+ ```
2106
+
2107
+
2108
+ ```ts
2109
+ interface GPU {
2110
+ vendor?: string;
2111
+ architecture?: string;
2112
+ device?: string;
2113
+ description?: string;
2114
+ }
2115
+ ```
2116
+
2117
+ ---
2118
+
2119
+ <h3 id="platform-useragent"><code>platform.userAgent</code></h3>
2120
+
2121
+ **Signature**
2122
+
2123
+ ```ts
2124
+ get userAgent(): string
2125
+ set userAgent(value: string)
2126
+ ```
2127
+
2128
+ Gets or sets the User-Agent string used for all platform detection. The setter invalidates all parsed caches.
2129
+
2130
+ **Example**
2131
+
2132
+ ```ts
2133
+ // Read current UA
2134
+ console.log(Native.platform.userAgent);
2135
+
2136
+ // Override for testing
2137
+ Native.platform.userAgent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)...';
2138
+ console.log(Native.platform.os.name); // 'iOS'
2139
+
2140
+ // Restore original UA
2141
+ Native.platform.userAgent = originalUA;
2142
+ await Native.platform.ready;
2143
+ ```
2144
+
2145
+ **Returns**
2146
+
2147
+ ```ts
2148
+ string
2149
+ ```
2150
+
2151
+
2152
+ ---
2153
+
2154
+ <h3 id="platform-ready"><code>platform.ready</code></h3>
2155
+
2156
+ **Signature**
2157
+
2158
+ ```ts
2159
+ get ready(): Promise<void>
2160
+ ```
2161
+
2162
+ Resolves when all async platform detection has settled.
2163
+
2164
+ **Example**
2165
+
2166
+ ```ts
2167
+ await Native.platform.ready;
2168
+
2169
+ // All values now reflect high-entropy and WebGPU data
2170
+ console.log(Native.platform.os.name);
2171
+ console.log(Native.platform.browser.name);
2172
+ console.log(Native.platform.gpu.vendor);
2173
+ ```
2174
+
2175
+ **Returns**
2176
+
2177
+ ```ts
2178
+ Promise<void>
2179
+ ```
2180
+
2181
+
2182
+ ---
2183
+
2184
+ <h3 id="platform-iswebview"><code>platform.isWebview</code></h3>
2185
+
2186
+ **Signature**
2187
+
2188
+ ```ts
2189
+ get isWebview(): boolean
2190
+ ```
2191
+
2192
+ Returns true when running inside a native WebView.
2193
+
2194
+ **Example**
2195
+
2196
+ ```ts
2197
+ if (Native.platform.isWebview) {
2198
+ console.log('Running inside a native WebView');
2199
+ }
2200
+ ```
2201
+
2202
+ **Returns**
2203
+
2204
+ ```ts
2205
+ boolean
2206
+ ```
2207
+
2208
+
2209
+ ---
2210
+
2211
+ <h3 id="platform-isnode"><code>platform.isNode</code></h3>
2212
+
2213
+ **Signature**
2214
+
2215
+ ```ts
2216
+ get isNode(): boolean
2217
+ ```
2218
+
2219
+ Returns true when running in a Node.js environment.
2220
+
2221
+ **Example**
2222
+
2223
+ ```ts
2224
+ if (Native.platform.isNode) {
2225
+ console.log('Running in Node.js');
2226
+ }
2227
+ ```
2228
+
2229
+ **Returns**
2230
+
2231
+ ```ts
2232
+ boolean
2233
+ ```
2234
+
2235
+
2236
+ ---
2237
+
2238
+ <h3 id="platform-isstandalone"><code>platform.isStandalone</code></h3>
2239
+
2240
+ **Signature**
2241
+
2242
+ ```ts
2243
+ get isStandalone(): boolean
2244
+ ```
2245
+
2246
+ Returns true when running as an installed PWA.
2247
+
2248
+ **Example**
2249
+
2250
+ ```ts
2251
+ if (Native.platform.isStandalone) {
2252
+ console.log('Running as installed PWA');
2253
+ }
2254
+ ```
2255
+
2256
+ **Returns**
2257
+
2258
+ ```ts
2259
+ boolean
2260
+ ```
2261
+
2262
+
2263
+ ---
2264
+
2265
+ ## theme
2266
+
2267
+ [`value`](#theme-value)
2268
+
2269
+ <h3 id="theme-value"><code>theme.value</code></h3>
2270
+
2271
+ **Signature**
2272
+
2273
+ ```ts
2274
+ get value(): string | undefined
2275
+ set value(color: string | undefined)
2276
+ ```
2277
+
2278
+ Gets or sets the browser theme color via the meta theme-color tag.
2279
+
2280
+ **Example**
2281
+
2282
+ ```ts
2283
+ // Read
2284
+ console.log(Native.theme.value); // '#ffffff' | undefined
2285
+
2286
+ // Set
2287
+ Native.theme.value = '#1a1a2e';
2288
+
2289
+ // Remove
2290
+ Native.theme.value = undefined;
2291
+ ```
2292
+
2293
+ **Returns**
2294
+
2295
+ ```ts
2296
+ string | undefined
2297
+ ```
2298
+
2299
+
2300
+ ---
2301
+
2302
+ ## vibration
2303
+
2304
+ [`run`](#vibration-run) · [`stop`](#vibration-stop)
2305
+
2306
+ <h3 id="vibration-run"><code>vibration.run</code></h3>
2307
+
2308
+ **Signature**
2309
+
2310
+ ```ts
2311
+ run(pattern: number | number[]): boolean
2312
+ ```
2313
+
2314
+ Triggers device vibration. Pass a number for a single pulse or an array to define a pattern.
2315
+
2316
+ **Example**
2317
+
2318
+ ```ts
2319
+ // Single pulse — 200ms
2320
+ Native.vibration.run(200);
2321
+
2322
+ // Pattern — vibrate 100ms, pause 50ms, vibrate 200ms
2323
+ Native.vibration.run([100, 50, 200]);
2324
+
2325
+ // Stop any ongoing vibration
2326
+ Native.vibration.run(0);
2327
+ ```
2328
+
2329
+ **Returns**
2330
+
2331
+ ```ts
2332
+ boolean
2333
+ ```
2334
+
2335
+
2336
+ **Throws**
2337
+
2338
+ ```ts
2339
+ throw new NotSupportedError // navigator.vibrate unavailable
2340
+ ```
2341
+
2342
+ ---
2343
+
2344
+ <h3 id="vibration-stop"><code>vibration.stop</code></h3>
2345
+
2346
+ **Signature**
2347
+
2348
+ ```ts
2349
+ stop(): boolean
2350
+ ```
2351
+
2352
+ Stops any ongoing vibration.
2353
+
2354
+ **Example**
2355
+
2356
+ ```ts
2357
+ Native.vibration.stop();
2358
+ ```
2359
+
2360
+ **Returns**
2361
+
2362
+ ```ts
2363
+ boolean
2364
+ ```
2365
+
2366
+
2367
+ ```ts
2368
+ // true — stop request accepted by the browser
2369
+ // false — document is hidden or vibration is unsupported
2370
+ ```
2371
+
2372
+ **Throws**
2373
+
2374
+ ```ts
2375
+ throw new NotSupportedError // navigator.vibrate unavailable
2376
+ ```
2377
+
2378
+ ---