@uno.build/runtime 0.0.0-stage → 0.2.0

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,3 +1,3 @@
1
- # Temporary Holding Version
1
+ # @uno.build/runtime
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ JavaScript APIs of the uno runtime. The documentation is at [uno.build](https://uno.build).
@@ -0,0 +1,7 @@
1
+ import { getUnoModuleNative } from '../utils'
2
+
3
+ const { PLATFORM } = getUnoModuleNative('base')
4
+
5
+ export const Platform = {
6
+ OS: PLATFORM,
7
+ }
@@ -0,0 +1,3 @@
1
+ export const Platform = {
2
+ OS: 'web',
3
+ }
@@ -0,0 +1,12 @@
1
+ import { getUnoModuleNative } from '../utils'
2
+
3
+ // The native HTMLCanvasElement, which fills the app window.
4
+ const canvas: HTMLCanvasElement = getUnoModuleNative('canvas').element
5
+
6
+ function init(): any {
7
+ return canvas
8
+ }
9
+
10
+ export const Canvas = {
11
+ init,
12
+ }
@@ -0,0 +1,20 @@
1
+ function init(canvasElement?: any): any {
2
+ if (canvasElement === undefined) {
3
+ canvasElement = document.querySelector('canvas')
4
+ if (canvasElement === null) {
5
+ throw new Error(
6
+ 'No canvas element found. Please provide a canvas element to initialize.',
7
+ )
8
+ }
9
+ }
10
+
11
+ const getContext = canvasElement.getContext.bind(canvasElement)
12
+ canvasElement.getContext = (context_type: string, context_attributes?: any) =>
13
+ getContext('webgpu', context_attributes)
14
+
15
+ return canvasElement
16
+ }
17
+
18
+ export const Canvas = {
19
+ init,
20
+ }
package/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './base/base'
2
+ export * from './memory/memory'
3
+ export * from './canvas/canvas'
4
+ export * from './screen/screen'
5
+ export * from './virtualkeyboard/virtualkeyboard'
@@ -0,0 +1,8 @@
1
+ import { getUnoModuleNative } from '../utils'
2
+
3
+ const { usage, gc } = getUnoModuleNative('memory')
4
+
5
+ export const Memory = {
6
+ usage,
7
+ gc,
8
+ }
@@ -0,0 +1,15 @@
1
+ function usage() {
2
+ return {
3
+ footprint: null,
4
+ rss: null,
5
+ virtual: null,
6
+ js: null,
7
+ }
8
+ }
9
+
10
+ function gc(..._args: unknown[]) {}
11
+
12
+ export const Memory = {
13
+ usage,
14
+ gc,
15
+ }
package/package.json CHANGED
@@ -1,6 +1,31 @@
1
1
  {
2
- "name": "@uno.build/runtime",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@uno.build/runtime",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "license": "Apache-2.0",
6
+ "description": "JavaScript APIs of the uno runtime",
7
+ "homepage": "https://uno.build",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/uno-build/runtime"
11
+ },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "main": "./index.ts",
16
+ "exports": {
17
+ ".": "./index.ts",
18
+ "./ui": "./ui/index.ts"
19
+ },
20
+ "dependencies": {
21
+ "yoga-layout": "^3.2.1"
22
+ },
23
+ "peerDependencies": {
24
+ "@uno.build/ui": "^0.4.0"
25
+ },
26
+ "peerDependenciesMeta": {
27
+ "@uno.build/ui": {
28
+ "optional": true
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,39 @@
1
+ # Screen
2
+
3
+ Read the current screen orientation and subscribe to orientation changes:
4
+
5
+ ```ts
6
+ import { Screen } from 'uno'
7
+
8
+ console.log(Screen.getOrientation())
9
+
10
+ function onChange(orientation) {
11
+ console.log(orientation)
12
+ }
13
+
14
+ Screen.addEventListener('orientation', onChange)
15
+
16
+ // Stop listening when the consumer is disposed.
17
+ Screen.removeEventListener('orientation', onChange)
18
+ ```
19
+
20
+ Orientation values are `portrait-primary`, `portrait-secondary`,
21
+ `landscape-primary`, and `landscape-secondary`. Listeners receive the orientation
22
+ string directly. Registering the same callback twice does not duplicate it.
23
+ Registration does not emit the initial value; use `getOrientation()` to read it.
24
+
25
+ ## Web limitations
26
+
27
+ - `setOrientation()` is a no-op. It does not rotate or lock the screen, and it
28
+ does not throw an error.
29
+ - Reading and observing orientation depend on the browser's
30
+ [Screen Orientation API](https://developer.mozilla.org/en-US/docs/Web/API/ScreenOrientation).
31
+ The module reads `screen.orientation.type` and listens for its `change` event.
32
+ - If `screen` or `screen.orientation` is unavailable, `getOrientation()` returns
33
+ `undefined`. Listeners can still be added and removed, but receive no events.
34
+ There is no fallback to sensors, legacy orientation events, or viewport dimensions.
35
+ - The reported value describes screen orientation, not physical device movement.
36
+ The module does not use `deviceorientation` or request sensor permissions.
37
+ - Resizing the browser window or canvas is not an orientation change. Use the window's
38
+ `resize` event, `addEventListener('resize', callback)`, to react to size changes.
39
+
@@ -0,0 +1,33 @@
1
+ import { getUnoModuleNative } from '../utils'
2
+ import type { ScreenOrientation, ScreenEffectiveOrientation } from './types'
3
+
4
+ export type { ScreenOrientation, ScreenEffectiveOrientation } from './types'
5
+
6
+ const module_ref = getUnoModuleNative<{
7
+ getOrientation: () => ScreenEffectiveOrientation
8
+ onChangeOrientation: (callback: ((orientation: ScreenEffectiveOrientation) => void) | null) => void
9
+ setOrientation: (orientation: ScreenOrientation) => void
10
+ }>('screen')
11
+
12
+ const event_listeners = {
13
+ orientation: new Set<(orientation: ScreenEffectiveOrientation) => void>(),
14
+ }
15
+
16
+ module_ref.onChangeOrientation((orientation) => {
17
+ event_listeners.orientation.forEach((callback) => callback(orientation))
18
+ })
19
+
20
+ function addEventListener(name: 'orientation', callback: (orientation: ScreenEffectiveOrientation) => void): void {
21
+ event_listeners[name].add(callback)
22
+ }
23
+
24
+ function removeEventListener(name: 'orientation', callback: (orientation: ScreenEffectiveOrientation) => void): void {
25
+ event_listeners[name].delete(callback)
26
+ }
27
+
28
+ export const Screen = {
29
+ getOrientation: module_ref.getOrientation,
30
+ setOrientation: module_ref.setOrientation,
31
+ addEventListener,
32
+ removeEventListener,
33
+ }
@@ -0,0 +1,35 @@
1
+ import { getGlobalScope } from '../utils'
2
+ import type { ScreenOrientation, ScreenEffectiveOrientation } from './types'
3
+
4
+ export type { ScreenOrientation, ScreenEffectiveOrientation } from './types'
5
+
6
+ const scope = getGlobalScope()
7
+ const screen_orientation = scope.screen?.orientation
8
+ const event_listeners = {
9
+ orientation: new Set<(orientation: ScreenEffectiveOrientation) => void>(),
10
+ }
11
+
12
+ screen_orientation?.addEventListener('change', () => {
13
+ event_listeners.orientation.forEach((callback) => callback(screen_orientation.type))
14
+ })
15
+
16
+ function getOrientation(): ScreenEffectiveOrientation | undefined {
17
+ return screen_orientation?.type
18
+ }
19
+
20
+ function addEventListener(name: 'orientation', callback: (orientation: ScreenEffectiveOrientation) => void): void {
21
+ event_listeners[name].add(callback)
22
+ }
23
+
24
+ function removeEventListener(name: 'orientation', callback: (orientation: ScreenEffectiveOrientation) => void): void {
25
+ event_listeners[name].delete(callback)
26
+ }
27
+
28
+ export const Screen = {
29
+ getOrientation,
30
+ addEventListener,
31
+ removeEventListener,
32
+ setOrientation(_orientation: ScreenOrientation): void {
33
+ // throw new Error('Screen.setOrientation is only supported on iOS')
34
+ },
35
+ }
@@ -0,0 +1,10 @@
1
+ export type ScreenOrientation =
2
+ | 'any'
3
+ | 'portrait'
4
+ | 'portrait-primary'
5
+ | 'portrait-secondary'
6
+ | 'landscape'
7
+ | 'landscape-primary'
8
+ | 'landscape-secondary'
9
+
10
+ export type ScreenEffectiveOrientation = Exclude<ScreenOrientation, 'any' | 'portrait' | 'landscape'>
package/ui/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './ui'
@@ -0,0 +1,22 @@
1
+ import type { EventOptions, UIWebGPUOptions } from '@uno.build/ui/UIWebGPU'
2
+ import ResourcesWebGPU from '@uno.build/ui/ResourcesWebGPU'
3
+ import UIWebGPU from '@uno.build/ui/UIWebGPU'
4
+ import { Yoga } from '../yoga/yoga'
5
+
6
+ export type { DefinedEvent } from '@uno.build/ui/UIWebGPU'
7
+ export type UIOptions = Omit<UIWebGPUOptions, 'loadYoga' | 'defined_events'> &
8
+ EventOptions<UI> & {
9
+ register_platform_events?: boolean
10
+ yoga?: boolean
11
+ }
12
+
13
+ export default class UI extends UIWebGPU {
14
+ static override async create({ register_platform_events = true, yoga = true, ...options }: UIOptions) {
15
+ const ui = new UI({ ...options, loadYoga: yoga ? Yoga.loadYoga : undefined } as UIWebGPUOptions)
16
+ await ui.initialize()
17
+ if (register_platform_events) ui.registerPlatformEvents()
18
+ return { ui }
19
+ }
20
+ }
21
+
22
+ export { ResourcesWebGPU as ResourcesUI, UI }
package/ui/ui.web.ts ADDED
@@ -0,0 +1,11 @@
1
+ import type { EventOptions, UIWebGPUOptions } from '@uno.build/ui/UIWebGPU'
2
+ import ResourcesWebGPU from '@uno.build/ui/ResourcesWebGPU'
3
+ import UI from '@uno.build/ui/UI'
4
+
5
+ export type { DefinedEvent } from '@uno.build/ui/UIWebGPU'
6
+ export type UIOptions = Omit<UIWebGPUOptions, 'loadYoga' | 'defined_events'> &
7
+ EventOptions<UI> & {
8
+ register_platform_events?: boolean
9
+ }
10
+
11
+ export { ResourcesWebGPU as ResourcesUI, UI }
package/utils.ts ADDED
@@ -0,0 +1,24 @@
1
+ export function getGlobalScope(): any {
2
+ if (typeof globalThis !== 'undefined') {
3
+ return globalThis as any
4
+ }
5
+
6
+ if (typeof self !== 'undefined') {
7
+ return self as any
8
+ }
9
+
10
+ if (typeof window !== 'undefined') {
11
+ return window as any
12
+ }
13
+
14
+ if (typeof global !== 'undefined') {
15
+ return global as any
16
+ }
17
+
18
+ return {}
19
+ }
20
+
21
+ export function getUnoModuleNative<T = any>(moduleName: string): T {
22
+ const uno = getGlobalScope().__unoNative
23
+ return uno[moduleName] as T
24
+ }
@@ -0,0 +1,122 @@
1
+ # VirtualKeyboard
2
+
3
+ `VirtualKeyboard.open(options)` accepts the following input and keyboard options:
4
+
5
+ | Option | Category | Behavior |
6
+ | --- | --- | --- |
7
+ | `input_value` | Input | Required initial text. |
8
+ | `appearance` | Input / Keyboard | `dark`, `light`, or `default` (default). On iOS, sets the keyboard and input bar appearance; `default` inherits the app appearance. On web, sets the input's CSS `color-scheme`; `default` inherits the page scheme. Applies to custom inputs too, but does not guarantee the system keyboard appearance or override explicit CSS colors. |
9
+ | `input_visible` | Input | Shows the native iOS input bar. Defaults to `true`. Setting it to `false` keeps keyboard input and callbacks active without displaying the bar. Ignored on web. |
10
+ | `input_placeholder` | Input | Hint text for an empty input. Defaults to an empty string. |
11
+ | `input_secure` | Input | Enables password entry. Defaults to `false`. |
12
+ | `input_max_length` | Input | Optional non-negative integer limiting the input length. Does not truncate the initial value. |
13
+ | `getInputElement` | Input | Optional web-only function returning `{ element, getStatus }`. See below. |
14
+ | `onInputChange` | Input | Receives the updated text when the input value changes. |
15
+ | `onInputSelectionChange` | Input | Receives `{ start, end }` when the input selection changes, including cursor movement without text changes. Offsets are zero-based UTF-16 code units; equal offsets indicate the cursor position. On iOS, also reports the selection when opening a session. Custom web inputs must support the DOM selection APIs. |
16
+ | `onInputSubmit` | Input | Receives the current text when the keyboard's return action confirms the input. |
17
+ | `onInputBlur` | Input | Receives the current text when the input loses focus. Submitting, calling `VirtualKeyboard.close()`, or replacing a session does not emit this callback. |
18
+ | `keyboard_mode` | Keyboard | `text`, `numeric`, `decimal`, or `email`. Defaults to `text`. |
19
+ | `keyboard_enter_key_hint` | Keyboard | `enter`, `done`, `go`, `next`, `search`, or `send`. Defaults to `done`. On iOS, `enter` uses the standard Return key; numeric and decimal keyboards do not use this hint. |
20
+ | `onKeyboardChange` | Keyboard | Receives the complete `VirtualKeyboardStatus` when keyboard visibility or geometry changes. On web, requires `navigator.virtualKeyboard`. |
21
+
22
+ On iOS, calling `open` while editing replaces the session's value, options, and
23
+ callbacks while keeping the native input focused. The keyboard configuration is
24
+ refreshed without closing it first. When transferring focus between app inputs,
25
+ call `open` for the next input without calling `close` from the previous input's
26
+ blur handler. Call `close` when focus leaves the inputs.
27
+
28
+ macOS has no on-screen keyboard, as in desktop browsers: `keyboard_visible` stays `false`,
29
+ `onKeyboardChange` is never called, and `keyboard_mode` and `keyboard_enter_key_hint` do not
30
+ apply. Return submits the input. With `input_visible`, the native input bar spans the bottom of
31
+ the window, where the iOS bar rests while its keyboard is hidden; `appearance` sets its appearance.
32
+ Moving the focus to another control emits `onInputBlur`.
33
+
34
+ ## Status
35
+
36
+ `VirtualKeyboard.status()` always returns a new `VirtualKeyboardStatus` object with all ten fields,
37
+ including before the first `open` and after both elements are hidden:
38
+
39
+ ```ts
40
+ {
41
+ keyboard_visible: false,
42
+ keyboard_screen_x: 0,
43
+ keyboard_screen_y: 0,
44
+ keyboard_width: 0,
45
+ keyboard_height: 0,
46
+ input_visible: false,
47
+ input_screen_x: 0,
48
+ input_screen_y: 0,
49
+ input_width: 0,
50
+ input_height: 0,
51
+ }
52
+ ```
53
+
54
+ Each hidden element has `visible: false` and zero coordinates and dimensions.
55
+ The two visibility flags are independent. The keyboard rectangle excludes the
56
+ input. On iOS, the input rectangle covers the complete native input bar, including
57
+ its padding, in window points. On macOS, it uses the points of the window's content
58
+ area with a top-left origin, matching the canvas coordinates. With `input_visible: false`,
59
+ all input fields stay zero. On web, keyboard coordinates use viewport CSS pixels; without
60
+ `navigator.virtualKeyboard`, the keyboard fields stay at their hidden values.
61
+
62
+ For a layout with the input bar directly above the keyboard, the app can compute
63
+ its available height without checking for missing metrics:
64
+
65
+ ```ts
66
+ const status = VirtualKeyboard.status()
67
+ const available_height = viewport_height - status.keyboard_height - status.input_height
68
+ ```
69
+
70
+ `onKeyboardChange` receives the same complete status, including the input state at
71
+ the time of the keyboard change. Input-only changes do not emit this event. Calling
72
+ `status()` does not emit events. Closing an editing session does not mark the
73
+ keyboard hidden until the platform reports that it is hidden.
74
+
75
+ `VirtualKeyboard` replaces `Keyboard`, and the `VirtualKeyboard*` types replace the `Keyboard*` types.
76
+ `VirtualKeyboard.status()` replaces `Keyboard.metrics()` and `Keyboard.isVisible()`;
77
+ `VirtualKeyboardStatus` replaces `KeyboardMetrics`.
78
+
79
+ ## Custom web input
80
+
81
+ `getInputElement(options)` is an optional, synchronous web-only function called on
82
+ every `open` with the complete options object. It returns:
83
+
84
+ ```ts
85
+ {
86
+ element: HTMLInputElement,
87
+ getStatus: () => VirtualKeyboardInputStatus,
88
+ }
89
+ ```
90
+
91
+ `element` must be attached to the DOM and ready to focus. The consumer owns its
92
+ creation, reuse, replacement, styles, configuration (including the element's
93
+ `value` and keyboard attributes), and removal. `VirtualKeyboard` manages focus and session
94
+ events without replacing existing event handlers. Closing a session leaves the
95
+ element in the DOM.
96
+
97
+ `getStatus()` must synchronously return all five input fields:
98
+
99
+ ```ts
100
+ {
101
+ input_visible: true,
102
+ input_screen_x: 0,
103
+ input_screen_y: 448,
104
+ input_width: 400,
105
+ input_height: 52,
106
+ }
107
+ ```
108
+
109
+ It measures the area the app wants to reserve for the input, which may include a
110
+ surrounding container. Use viewport CSS pixels, matching the keyboard fields. The
111
+ function must read the current state without side effects. The runtime calls it on
112
+ every `status()` query and when preparing an `onKeyboardChange` payload. When
113
+ `input_visible` is `false`, the runtime reports zero input coordinates and dimensions.
114
+
115
+ The latest provider remains available to `status()` after editing ends, because a
116
+ consumer-owned input may still be visible. The next `open` replaces it. The
117
+ consumer should update the state read by `getStatus()` when hiding or removing its
118
+ input. No input polling or extra change notifications are used.
119
+
120
+ Without `getInputElement`, `VirtualKeyboard` creates, configures, and reuses its hidden
121
+ input, whose status always has `input_visible: false` and zero geometry. Native
122
+ iOS ignores `getInputElement` and measures its own input bar.
@@ -0,0 +1,36 @@
1
+ export type VirtualKeyboardInputStatus = Readonly<{
2
+ input_visible: boolean
3
+ input_screen_x: number
4
+ input_screen_y: number
5
+ input_width: number
6
+ input_height: number
7
+ }>
8
+
9
+ export type VirtualKeyboardStatus = Readonly<{
10
+ keyboard_visible: boolean
11
+ keyboard_screen_x: number
12
+ keyboard_screen_y: number
13
+ keyboard_width: number
14
+ keyboard_height: number
15
+ }> &
16
+ VirtualKeyboardInputStatus
17
+
18
+ export type VirtualKeyboardOptions = {
19
+ appearance?: 'dark' | 'light' | 'default'
20
+ input_value: string
21
+ input_visible?: boolean
22
+ input_placeholder?: string
23
+ input_secure?: boolean
24
+ input_max_length?: number
25
+ onInputChange?: (value: string) => void
26
+ onInputSelectionChange?: (selection: { start: number; end: number }) => void
27
+ onInputSubmit?: (value: string) => void
28
+ onInputBlur?: (value: string) => void
29
+ keyboard_mode?: 'text' | 'numeric' | 'decimal' | 'email'
30
+ keyboard_enter_key_hint?: 'enter' | 'done' | 'go' | 'next' | 'search' | 'send'
31
+ onKeyboardChange?: (status: VirtualKeyboardStatus) => void
32
+ getInputElement?: (options: VirtualKeyboardOptions) => {
33
+ element: HTMLInputElement
34
+ getStatus: () => VirtualKeyboardInputStatus
35
+ }
36
+ }
@@ -0,0 +1,10 @@
1
+ import { getUnoModuleNative } from '../utils'
2
+ import type { VirtualKeyboardStatus, VirtualKeyboardOptions } from './types'
3
+
4
+ export type { VirtualKeyboardInputStatus, VirtualKeyboardStatus, VirtualKeyboardOptions } from './types'
5
+
6
+ export const VirtualKeyboard = getUnoModuleNative<{
7
+ open: (options: VirtualKeyboardOptions) => void
8
+ close: () => void
9
+ status: () => VirtualKeyboardStatus
10
+ }>('virtualkeyboard')
@@ -0,0 +1,197 @@
1
+ import { getGlobalScope } from '../utils'
2
+ import type { VirtualKeyboardInputStatus, VirtualKeyboardStatus, VirtualKeyboardOptions } from './types'
3
+
4
+ export type { VirtualKeyboardInputStatus, VirtualKeyboardStatus, VirtualKeyboardOptions } from './types'
5
+
6
+ const scope = getGlobalScope()
7
+ const HIDDEN_INPUT_STATUS: VirtualKeyboardInputStatus = {
8
+ input_visible: false,
9
+ input_screen_x: 0,
10
+ input_screen_y: 0,
11
+ input_width: 0,
12
+ input_height: 0,
13
+ }
14
+ const HIDDEN_KEYBOARD_STATUS = {
15
+ keyboard_visible: false,
16
+ keyboard_screen_x: 0,
17
+ keyboard_screen_y: 0,
18
+ keyboard_width: 0,
19
+ keyboard_height: 0,
20
+ }
21
+ const state = {
22
+ default_input: null as HTMLInputElement | null,
23
+ closeInput: undefined as (() => void) | undefined,
24
+ editing: false,
25
+ keyboard_status: HIDDEN_KEYBOARD_STATUS,
26
+ getInputStatus: (): VirtualKeyboardInputStatus => HIDDEN_INPUT_STATUS,
27
+ onKeyboardChange: undefined as VirtualKeyboardOptions['onKeyboardChange'],
28
+ }
29
+
30
+ const virtual_keyboard = scope.navigator?.virtualKeyboard
31
+
32
+ function readKeyboardStatus() {
33
+ const rect = virtual_keyboard.boundingRect
34
+ if (rect.width === 0 || rect.height === 0) {
35
+ return HIDDEN_KEYBOARD_STATUS
36
+ }
37
+ return {
38
+ keyboard_visible: true,
39
+ keyboard_screen_x: rect.x,
40
+ keyboard_screen_y: rect.y,
41
+ keyboard_width: rect.width,
42
+ keyboard_height: rect.height,
43
+ }
44
+ }
45
+
46
+ function onGeometryChange() {
47
+ const previous_status = state.keyboard_status
48
+ const next_status = readKeyboardStatus()
49
+ if (
50
+ previous_status.keyboard_visible === next_status.keyboard_visible &&
51
+ previous_status.keyboard_screen_x === next_status.keyboard_screen_x &&
52
+ previous_status.keyboard_screen_y === next_status.keyboard_screen_y &&
53
+ previous_status.keyboard_width === next_status.keyboard_width &&
54
+ previous_status.keyboard_height === next_status.keyboard_height
55
+ ) {
56
+ return
57
+ }
58
+
59
+ state.keyboard_status = next_status
60
+ const { onKeyboardChange } = state
61
+ if (!next_status.keyboard_visible && !state.editing) {
62
+ state.onKeyboardChange = undefined
63
+ }
64
+ onKeyboardChange?.(status())
65
+ }
66
+
67
+ if (virtual_keyboard !== undefined) {
68
+ virtual_keyboard.overlaysContent = true
69
+ state.keyboard_status = readKeyboardStatus()
70
+ virtual_keyboard.addEventListener('geometrychange', onGeometryChange)
71
+ }
72
+
73
+ function status(): VirtualKeyboardStatus {
74
+ const input_status = state.getInputStatus()
75
+ return {
76
+ ...state.keyboard_status,
77
+ ...(input_status.input_visible ? input_status : HIDDEN_INPUT_STATUS),
78
+ }
79
+ }
80
+
81
+ function createInput(): HTMLInputElement {
82
+ const element = scope.document.createElement('input')
83
+ element.style.position = 'fixed'
84
+ element.style.width = '1px'
85
+ element.style.height = '1px'
86
+ element.style.opacity = '0'
87
+ element.style.pointerEvents = 'none'
88
+ element.style.left = '0'
89
+ element.style.bottom = '0'
90
+ element.tabIndex = -1
91
+ scope.document.body.appendChild(element)
92
+ return element
93
+ }
94
+
95
+ function getDefaultInputElement({
96
+ input_value,
97
+ input_placeholder = '',
98
+ input_secure = false,
99
+ keyboard_mode = 'text',
100
+ keyboard_enter_key_hint = 'done',
101
+ input_max_length,
102
+ }: VirtualKeyboardOptions) {
103
+ const type = input_secure ? 'password' : 'text'
104
+ if (
105
+ state.default_input !== null &&
106
+ (state.default_input.type !== type ||
107
+ state.default_input.inputMode !== keyboard_mode ||
108
+ state.default_input.enterKeyHint !== keyboard_enter_key_hint)
109
+ ) {
110
+ state.default_input.remove()
111
+ state.default_input = null
112
+ }
113
+ if (state.default_input === null) {
114
+ state.default_input = createInput()
115
+ }
116
+ const input = state.default_input
117
+ input.value = input_value
118
+ input.placeholder = input_placeholder
119
+ input.type = type
120
+ input.inputMode = keyboard_mode
121
+ input.enterKeyHint = keyboard_enter_key_hint
122
+ if (input_max_length === undefined) {
123
+ input.removeAttribute('maxlength')
124
+ } else {
125
+ input.maxLength = input_max_length
126
+ }
127
+ return { element: input, getStatus: () => HIDDEN_INPUT_STATUS }
128
+ }
129
+
130
+ function open(options: VirtualKeyboardOptions) {
131
+ close()
132
+ const {
133
+ getInputElement = getDefaultInputElement,
134
+ appearance = 'default',
135
+ onInputChange,
136
+ onInputSelectionChange,
137
+ onInputSubmit,
138
+ onInputBlur,
139
+ onKeyboardChange,
140
+ } = options
141
+ const { element: input, getStatus } = getInputElement(options)
142
+ input.style.colorScheme = appearance === 'default' ? 'inherit' : appearance
143
+ state.getInputStatus = getStatus
144
+ state.onKeyboardChange = onKeyboardChange
145
+ state.editing = true
146
+ function onBlur() {
147
+ const value = input.value
148
+ close()
149
+ onInputBlur?.(value)
150
+ }
151
+ function onInput() {
152
+ onInputChange?.(input.value)
153
+ }
154
+ function onSelectionChange() {
155
+ const start = input.selectionStart
156
+ const end = input.selectionEnd
157
+ if (start !== null && end !== null) {
158
+ onInputSelectionChange?.({ start, end })
159
+ }
160
+ }
161
+ function onKeyDown(event: KeyboardEvent) {
162
+ if (event.key === 'Enter' && !event.isComposing) {
163
+ event.preventDefault()
164
+ const submitted_value = input.value
165
+ close()
166
+ onInputSubmit?.(submitted_value)
167
+ }
168
+ }
169
+ input.addEventListener('blur', onBlur)
170
+ input.addEventListener('input', onInput)
171
+ input.addEventListener('selectionchange', onSelectionChange)
172
+ input.addEventListener('keydown', onKeyDown)
173
+ input.focus({ preventScroll: true })
174
+ state.closeInput = () => {
175
+ input.removeEventListener('blur', onBlur)
176
+ input.removeEventListener('input', onInput)
177
+ input.removeEventListener('selectionchange', onSelectionChange)
178
+ input.removeEventListener('keydown', onKeyDown)
179
+ input.blur()
180
+ }
181
+ }
182
+
183
+ function close() {
184
+ state.editing = false
185
+ if (!state.keyboard_status.keyboard_visible) {
186
+ state.onKeyboardChange = undefined
187
+ }
188
+ const { closeInput } = state
189
+ state.closeInput = undefined
190
+ closeInput?.()
191
+ }
192
+
193
+ export const VirtualKeyboard = {
194
+ open,
195
+ close,
196
+ status,
197
+ }
@@ -0,0 +1,16 @@
1
+ import { getUnoModuleNative } from '../utils'
2
+
3
+ const moduleRef = getUnoModuleNative('yoga')
4
+
5
+ async function loadYoga() {
6
+ return {
7
+ Config: moduleRef.Config,
8
+ Node: moduleRef.Node,
9
+ ...moduleRef.Constants,
10
+ }
11
+ }
12
+
13
+ export const Yoga = {
14
+ loadYoga,
15
+ ...moduleRef.Enums,
16
+ }
@@ -0,0 +1 @@
1
+ export * as Yoga from 'yoga-layout/load'