@geastack/core 0.1.42 → 0.1.44
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 +36 -9
- package/events.cpp +11 -0
- package/gea_app_entry.cpp +4 -0
- package/gea_sources.mjs +12 -2
- package/gea_sources.sh +6 -1
- package/include/canvas.h +1 -0
- package/include/display.h +1 -0
- package/include/event_listener_options.h +26 -0
- package/include/events.h +109 -5
- package/include/gea/embedded.h +1 -0
- package/include/graphics/font.h +11 -0
- package/include/native_identity.h +65 -0
- package/include/platform/asset_bundle.h +71 -0
- package/include/platform/esp_now.h +113 -0
- package/include/platform/magnetometer.h +60 -0
- package/include/runtime.h +14 -0
- package/index.d.ts +156 -8
- package/package.json +5 -2
- package/runtime/host.ts +61 -1
- package/runtime-surface.ts +5 -0
- package/runtime.cpp +145 -16
- package/scripts/build-gea-vite-geatsc.mjs +31 -7
- package/scripts/gea-file-asset-bundle.mjs +82 -0
- package/scripts/gea-native-style-plugin.mjs +3 -1
- package/scripts/gea-vite-plugin-resolution.mjs +49 -0
- package/scripts/generate-gea-embedded-assets.mjs +20 -3
- package/scripts/generate-gea-embedded-fonts.mjs +25 -4
- package/scripts/tests/gea-vite-plugin-resolution.test.mjs +46 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <array>
|
|
3
|
+
#include <cstddef>
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
|
|
6
|
+
namespace gea::platform::espnow {
|
|
7
|
+
struct Datagram {
|
|
8
|
+
std::array<std::uint8_t, 6> source{};
|
|
9
|
+
std::array<std::uint8_t, 1470> bytes{};
|
|
10
|
+
std::size_t size = 0;
|
|
11
|
+
std::int64_t receivedUs = 0;
|
|
12
|
+
int rssi = 0;
|
|
13
|
+
// Raw 32-bit receive clock in microseconds, not the esp_timer epoch. The
|
|
14
|
+
// SDK qualifies its precision only without modem/light sleep. Zero is a
|
|
15
|
+
// valid timestamp; absence of RX metadata is represented separately.
|
|
16
|
+
std::uint32_t radioTimestampUs = 0;
|
|
17
|
+
bool radioTimestampValid = false;
|
|
18
|
+
};
|
|
19
|
+
struct Statistics {
|
|
20
|
+
std::uint32_t sent = 0, failed = 0, received = 0, dropped = 0;
|
|
21
|
+
std::uint32_t maximumSendUs = 0;
|
|
22
|
+
// Last completed datagram, published before canSend() becomes true. The
|
|
23
|
+
// single sending task may read these after canSend() for its prior send.
|
|
24
|
+
std::uint64_t lastSendStartedUs = 0, lastSendCompletedUs = 0;
|
|
25
|
+
std::uint32_t lastSendRate = 0, lastSendStatus = 0;
|
|
26
|
+
// Optional diagnostic copy of the SDK callback's 24-byte Action MAC header.
|
|
27
|
+
// Validity is separate: zero fields are valid values, and Retry=0 does not
|
|
28
|
+
// prove that the hardware made only one transmission attempt.
|
|
29
|
+
std::uint16_t lastSendFrameControl = 0, lastSendDuration = 0,
|
|
30
|
+
lastSendSequenceControl = 0;
|
|
31
|
+
bool lastSendMacValid = false;
|
|
32
|
+
};
|
|
33
|
+
} // namespace gea::platform::espnow
|
|
34
|
+
extern "C" {
|
|
35
|
+
bool gea_espnow_open(int channel, int rate) __attribute__((weak));
|
|
36
|
+
void gea_espnow_close() __attribute__((weak));
|
|
37
|
+
bool gea_espnow_is_open() __attribute__((weak));
|
|
38
|
+
bool gea_espnow_can_send() __attribute__((weak));
|
|
39
|
+
bool gea_espnow_capture_tx_header(bool enabled) __attribute__((weak));
|
|
40
|
+
bool gea_espnow_set_max_tx_power(int quarterDbm) __attribute__((weak));
|
|
41
|
+
int gea_espnow_get_max_tx_power() __attribute__((weak));
|
|
42
|
+
bool gea_espnow_peer(const std::uint8_t *mac, const std::uint8_t *key)
|
|
43
|
+
__attribute__((weak));
|
|
44
|
+
int gea_espnow_send(const std::uint8_t *mac, const void *data, std::size_t size)
|
|
45
|
+
__attribute__((weak));
|
|
46
|
+
bool gea_espnow_receive(gea::platform::espnow::Datagram *)
|
|
47
|
+
__attribute__((weak));
|
|
48
|
+
void gea_espnow_wake() __attribute__((weak));
|
|
49
|
+
void gea_espnow_wait(unsigned ms) __attribute__((weak));
|
|
50
|
+
void gea_espnow_statistics(gea::platform::espnow::Statistics *)
|
|
51
|
+
__attribute__((weak));
|
|
52
|
+
}
|
|
53
|
+
namespace gea::platform::espnow {
|
|
54
|
+
class EspNow {
|
|
55
|
+
public:
|
|
56
|
+
// Channel zero borrows the running Wi-Fi radio without changing its channel.
|
|
57
|
+
// Otherwise claims an idle radio. Rate is 24/36/54 Mbps (OFDM), 65 (HT20
|
|
58
|
+
// MCS7), or 115 on ESP32-S31 (HE20 MCS9 short GI, nominal 114.7 Mbps).
|
|
59
|
+
// Unsupported rates/protocols fail; a borrowed radio is never reconfigured.
|
|
60
|
+
static bool open(int channel, int rate = 54) {
|
|
61
|
+
return gea_espnow_open && gea_espnow_open(channel, rate);
|
|
62
|
+
}
|
|
63
|
+
static bool isOpen() { return gea_espnow_is_open && gea_espnow_is_open(); }
|
|
64
|
+
static bool canSend() { return gea_espnow_can_send && gea_espnow_can_send(); }
|
|
65
|
+
// Native diagnostics only; default off and reset on radio close/open.
|
|
66
|
+
// Enable only on an owned radio. Copies metadata, never changes the frame.
|
|
67
|
+
static bool captureTxHeader(bool enabled) {
|
|
68
|
+
return gea_espnow_capture_tx_header &&
|
|
69
|
+
gea_espnow_capture_tx_header(enabled);
|
|
70
|
+
}
|
|
71
|
+
// Quarter-dBm cap, 8..84. Only an owned radio may be changed; the SDK
|
|
72
|
+
// quantizes the request. Read back the actual cap, or -1 if unavailable.
|
|
73
|
+
static bool setMaxTxPower(int quarterDbm) {
|
|
74
|
+
return gea_espnow_set_max_tx_power &&
|
|
75
|
+
gea_espnow_set_max_tx_power(quarterDbm);
|
|
76
|
+
}
|
|
77
|
+
static int getMaxTxPower() {
|
|
78
|
+
return gea_espnow_get_max_tx_power ? gea_espnow_get_max_tx_power() : -1;
|
|
79
|
+
}
|
|
80
|
+
static void close() {
|
|
81
|
+
if (gea_espnow_close)
|
|
82
|
+
gea_espnow_close();
|
|
83
|
+
}
|
|
84
|
+
static bool addPeer(const std::uint8_t *mac,
|
|
85
|
+
const std::uint8_t *key = nullptr) {
|
|
86
|
+
return gea_espnow_peer && gea_espnow_peer(mac, key);
|
|
87
|
+
}
|
|
88
|
+
// Copies one datagram; length on admission, zero when busy, -1 on error.
|
|
89
|
+
// MAC completion is NOT an application acknowledgement.
|
|
90
|
+
static int send(const std::uint8_t *mac, const void *bytes,
|
|
91
|
+
std::size_t size) {
|
|
92
|
+
return gea_espnow_send ? gea_espnow_send(mac, bytes, size) : -1;
|
|
93
|
+
}
|
|
94
|
+
static bool receive(Datagram &packet) {
|
|
95
|
+
return gea_espnow_receive && gea_espnow_receive(&packet);
|
|
96
|
+
}
|
|
97
|
+
// One native consumer. Wakes for RX or TX completion; never call from the UI.
|
|
98
|
+
static void wake() {
|
|
99
|
+
if (gea_espnow_wake)
|
|
100
|
+
gea_espnow_wake();
|
|
101
|
+
}
|
|
102
|
+
static void wait(unsigned ms) {
|
|
103
|
+
if (gea_espnow_wait)
|
|
104
|
+
gea_espnow_wait(ms);
|
|
105
|
+
}
|
|
106
|
+
static Statistics statistics() {
|
|
107
|
+
Statistics s{};
|
|
108
|
+
if (gea_espnow_statistics)
|
|
109
|
+
gea_espnow_statistics(&s);
|
|
110
|
+
return s;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
} // namespace gea::platform::espnow
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#pragma once
|
|
3
|
+
|
|
4
|
+
#include <cstddef>
|
|
5
|
+
#include <cstdint>
|
|
6
|
+
|
|
7
|
+
namespace gea::platform::sensors {
|
|
8
|
+
struct MagnetometerReading {
|
|
9
|
+
// Compensated whole microtesla. BMM150 overflow sentinels are preserved;
|
|
10
|
+
// saturatedAxes identifies axes that must not be used as field strength.
|
|
11
|
+
std::int16_t x = 0, y = 0, z = 0;
|
|
12
|
+
bool valid = false;
|
|
13
|
+
std::uint8_t saturatedAxes = 0; // X=1, Y=2, Z=4.
|
|
14
|
+
std::uint64_t timestampUs = 0;
|
|
15
|
+
};
|
|
16
|
+
} // namespace gea::platform::sensors
|
|
17
|
+
|
|
18
|
+
extern "C" {
|
|
19
|
+
bool gea_magnetometer_open() __attribute__((weak));
|
|
20
|
+
void gea_magnetometer_close() __attribute__((weak));
|
|
21
|
+
unsigned gea_magnetometer_count() __attribute__((weak));
|
|
22
|
+
bool gea_magnetometer_read(unsigned,
|
|
23
|
+
gea::platform::sensors::MagnetometerReading *)
|
|
24
|
+
__attribute__((weak));
|
|
25
|
+
bool gea_magnetometer_read_all(gea::platform::sensors::MagnetometerReading *,
|
|
26
|
+
std::size_t) __attribute__((weak));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
namespace gea::platform::sensors {
|
|
30
|
+
class Magnetometer {
|
|
31
|
+
public:
|
|
32
|
+
// Idempotent. No bus or pins are claimed until open is called.
|
|
33
|
+
static bool open() {
|
|
34
|
+
return gea_magnetometer_open && gea_magnetometer_open();
|
|
35
|
+
}
|
|
36
|
+
static void close() {
|
|
37
|
+
if (gea_magnetometer_close)
|
|
38
|
+
gea_magnetometer_close();
|
|
39
|
+
}
|
|
40
|
+
static unsigned count() {
|
|
41
|
+
return gea_magnetometer_count ? gea_magnetometer_count() : 0;
|
|
42
|
+
}
|
|
43
|
+
static bool read(unsigned index, MagnetometerReading &sample) {
|
|
44
|
+
sample = {};
|
|
45
|
+
return gea_magnetometer_read && gea_magnetometer_read(index, &sample);
|
|
46
|
+
}
|
|
47
|
+
// Reads the board's sensors back-to-back under one driver lock, with a
|
|
48
|
+
// shared completion timestamp. This is not hardware-triggered sampling.
|
|
49
|
+
// False if capacity is insufficient or any sensor failed; inspect valid
|
|
50
|
+
// on individual readings. The output is cleared even when unsupported.
|
|
51
|
+
static bool readAll(MagnetometerReading *samples, std::size_t capacity) {
|
|
52
|
+
if (!samples)
|
|
53
|
+
return false;
|
|
54
|
+
for (std::size_t i = 0; i < capacity; ++i)
|
|
55
|
+
samples[i] = {};
|
|
56
|
+
return gea_magnetometer_read_all &&
|
|
57
|
+
gea_magnetometer_read_all(samples, capacity);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
} // namespace gea::platform::sensors
|
package/include/runtime.h
CHANGED
|
@@ -67,6 +67,20 @@ public:
|
|
|
67
67
|
// calls it, so a target that does not care never sees a difference.
|
|
68
68
|
static bool boot(const RuntimeOptions &options);
|
|
69
69
|
|
|
70
|
+
// Pause UI event/frame callbacks at a safe event-loop boundary. A request
|
|
71
|
+
// made before boot is retained; uiPaused() becomes true only after boot and
|
|
72
|
+
// the current callback/frame (including its locks) have returned. Calls do
|
|
73
|
+
// not wait and may be made from another task. uiPaused() acknowledges the
|
|
74
|
+
// current request, so callers that need quiescence must wait for it.
|
|
75
|
+
//
|
|
76
|
+
// Paused input is discarded and gestures are reset. Resuming requests a fresh
|
|
77
|
+
// frame. Platform tickers, device control, native services and previously
|
|
78
|
+
// submitted display DMA keep running; this does not suspend any task. A
|
|
79
|
+
// headless build has no UI loop, so a pause is acknowledged immediately.
|
|
80
|
+
static void requestUiPause(bool paused);
|
|
81
|
+
static bool uiPauseRequested();
|
|
82
|
+
static bool uiPaused();
|
|
83
|
+
|
|
70
84
|
// Hands the runtime a block of internal RAM to hold for the display and free
|
|
71
85
|
// immediately before Display::init(). For an app whose native boot hook has
|
|
72
86
|
// a phase that must not see less RAM than it actually has, followed by one
|
package/index.d.ts
CHANGED
|
@@ -103,9 +103,20 @@ export interface GeaIntrinsicElements {
|
|
|
103
103
|
'virtual-list': NativeVirtualListProps
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
export interface WindowEventListenerOptions {
|
|
107
|
+
capture?: boolean
|
|
108
|
+
once?: boolean
|
|
109
|
+
}
|
|
110
|
+
export interface GeaWindowEventMap extends GeaEventMap {
|
|
111
|
+
blur: Event
|
|
112
|
+
resize: Event
|
|
113
|
+
}
|
|
114
|
+
/** Native app-window event authority. No global input posting or browser Window privileges. */
|
|
106
115
|
export interface GeaWindow {
|
|
107
116
|
readonly innerWidth: number
|
|
108
117
|
readonly innerHeight: number
|
|
118
|
+
addEventListener<K extends keyof GeaWindowEventMap>(type: K, listener: (event: GeaWindowEventMap[K]) => void, options?: boolean | WindowEventListenerOptions): void
|
|
119
|
+
removeEventListener<K extends keyof GeaWindowEventMap>(type: K, listener: (event: GeaWindowEventMap[K]) => void, options?: boolean | WindowEventListenerOptions): void
|
|
109
120
|
}
|
|
110
121
|
|
|
111
122
|
declare global {
|
|
@@ -140,6 +151,10 @@ declare global {
|
|
|
140
151
|
/** @gea-host-no-property-writes */
|
|
141
152
|
function requestAnimationFrame(callback: (timestampMs: int) => void): number
|
|
142
153
|
|
|
154
|
+
// Shares the native Promise-job FIFO; app replacement retires pending jobs.
|
|
155
|
+
/** @gea-host-no-property-writes */
|
|
156
|
+
function queueMicrotask(callback: () => void): void
|
|
157
|
+
|
|
143
158
|
// Native one-shot / repeating timers (gea::host::setTimeout/setInterval,
|
|
144
159
|
// engine/../host/include/host/timers.h), NOT Node's `NodeJS.Timeout` (there is
|
|
145
160
|
// no `dom` or `node` lib here, so without this declaration a `setTimeout`
|
|
@@ -339,6 +354,7 @@ export interface DataAttributes {
|
|
|
339
354
|
}
|
|
340
355
|
|
|
341
356
|
export interface Event {
|
|
357
|
+
readonly defaultPrevented: boolean
|
|
342
358
|
readonly type: string
|
|
343
359
|
readonly target: EventTarget
|
|
344
360
|
readonly currentTarget: EventTarget
|
|
@@ -354,12 +370,30 @@ export interface EventTarget {
|
|
|
354
370
|
addEventListener(type: string, listener: (event: Event) => void): void
|
|
355
371
|
}
|
|
356
372
|
|
|
373
|
+
/** Live child collection; iterable and bracket protocols are not published yet. */
|
|
374
|
+
export interface NodeList {
|
|
375
|
+
readonly length: number
|
|
376
|
+
item(index: number): Node | null
|
|
377
|
+
}
|
|
378
|
+
|
|
357
379
|
export interface Node extends EventTarget {
|
|
358
|
-
|
|
380
|
+
/** True only for a live allocation in the current mounted document subtree. */
|
|
381
|
+
readonly isConnected: boolean
|
|
382
|
+
readonly nodeType: number
|
|
383
|
+
readonly parentNode: Node | null
|
|
384
|
+
/** The current first child, including text/comments; null when empty. */
|
|
385
|
+
readonly firstChild: Node | null
|
|
386
|
+
readonly childNodes: NodeList
|
|
359
387
|
appendChild(child: Node): void
|
|
388
|
+
/** Move a live node before a direct child; null appends. Return the inserted node. */
|
|
389
|
+
insertBefore(child: Node, reference: Node | null): Node
|
|
390
|
+
/** Detach a direct live child and return that same allocation, without retiring it. */
|
|
391
|
+
removeChild(child: Node): Node
|
|
360
392
|
remove(): void
|
|
361
393
|
}
|
|
362
394
|
|
|
395
|
+
export interface FocusOptions { preventScroll?: boolean }
|
|
396
|
+
|
|
363
397
|
export interface ScrollIntoViewOptions {
|
|
364
398
|
behavior?: 'auto' | 'instant' | 'smooth'
|
|
365
399
|
block?: 'start' | 'center' | 'end' | 'nearest'
|
|
@@ -374,6 +408,12 @@ export interface DOMTokenList {
|
|
|
374
408
|
}
|
|
375
409
|
|
|
376
410
|
export interface Element extends Node {
|
|
411
|
+
readonly lastElementChild: Element | null
|
|
412
|
+
/** Bounded native selector grammar; malformed/unsupported selectors refuse. */
|
|
413
|
+
matches(selector: string): boolean
|
|
414
|
+
closest<E extends Element = Element>(selector: string): E | null
|
|
415
|
+
querySelector(selector: string): Element | null
|
|
416
|
+
querySelectorAll(selector: string): readonly Element[]
|
|
377
417
|
className?: string
|
|
378
418
|
readonly children: readonly Element[]
|
|
379
419
|
scrollLeft?: number
|
|
@@ -395,10 +435,24 @@ export interface Element extends Node {
|
|
|
395
435
|
// `blur()` clears it again (and does nothing when this element was not the
|
|
396
436
|
// one focused). Backed by `NodeHandle::focus()`/`blur()` over
|
|
397
437
|
// `Tree::setActiveInput` (engine/ui/node.cpp).
|
|
398
|
-
focus(): void
|
|
438
|
+
focus(options?: FocusOptions): void
|
|
399
439
|
blur(): void
|
|
400
440
|
}
|
|
401
441
|
|
|
442
|
+
/** Minimal native text input backed by a live NodeHandle allocation.
|
|
443
|
+
* Value uses the native value attribute; HTML dirty-value/defaultValue semantics
|
|
444
|
+
* and checked/type/selection APIs are not declared by this carrier. */
|
|
445
|
+
export interface InputElement extends Element {
|
|
446
|
+
disabled: boolean
|
|
447
|
+
value: string
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** Native button attributes backed by the live node allocation. */
|
|
451
|
+
export interface ButtonElement extends Element {
|
|
452
|
+
disabled: boolean
|
|
453
|
+
value: string
|
|
454
|
+
}
|
|
455
|
+
|
|
402
456
|
export interface VirtualListElement extends Element {
|
|
403
457
|
scrollTop: number
|
|
404
458
|
/**
|
|
@@ -412,15 +466,22 @@ export interface VirtualListElement extends Element {
|
|
|
412
466
|
}
|
|
413
467
|
|
|
414
468
|
export interface Document {
|
|
469
|
+
addEventListener<K extends keyof GeaEventMap>(type: K, listener: (event: GeaEventMap[K]) => void, options?: boolean | WindowEventListenerOptions): void
|
|
470
|
+
removeEventListener<K extends keyof GeaEventMap>(type: K, listener: (event: GeaEventMap[K]) => void, options?: boolean | WindowEventListenerOptions): void
|
|
415
471
|
readonly body: Element
|
|
472
|
+
/** Actual connected native focus, null when no owned responder is focused. */
|
|
473
|
+
readonly activeElement: Element | null
|
|
416
474
|
getElementById(id: string): Element | null
|
|
417
475
|
querySelector(selector: string): Element | null
|
|
418
476
|
querySelectorAll(selector: string): readonly Element[]
|
|
477
|
+
createElement(tag: 'input'): InputElement
|
|
478
|
+
createElement(tag: 'button'): ButtonElement
|
|
419
479
|
createElement(tag: 'audio'): HTMLAudioElement
|
|
420
480
|
createElement(tag: 'video'): HTMLVideoElement
|
|
421
481
|
createElement(tag: 'virtual-list'): VirtualListElement
|
|
422
482
|
createElement(tag: string): Element
|
|
423
483
|
createTextNode(text: string): Node
|
|
484
|
+
createComment(text: string): Node
|
|
424
485
|
createDocumentFragment(): Node
|
|
425
486
|
}
|
|
426
487
|
|
|
@@ -435,6 +496,10 @@ export interface PointerEvent extends Event {
|
|
|
435
496
|
// finger is 2, etc. (web convention, mirrored by the device touch runtime).
|
|
436
497
|
// Multi-touch handlers route gestures by this id.
|
|
437
498
|
readonly pointerId: number
|
|
499
|
+
/** Changed button: primary 0, auxiliary 1, secondary 2; -1 for no known edge. */
|
|
500
|
+
readonly button: number
|
|
501
|
+
/** Held-button bitmask for the producing pointer gesture; zero when released/unknown. */
|
|
502
|
+
readonly buttons: number
|
|
438
503
|
readonly x: number
|
|
439
504
|
readonly y: number
|
|
440
505
|
readonly clientX: number
|
|
@@ -502,7 +567,7 @@ export interface RotaryEvent extends Event {
|
|
|
502
567
|
* `KeyEvent`; `input` carries an `InputEvent`. Both are dispatched off-tree by
|
|
503
568
|
* the runtime (`dispatch_key_input`, and `virtual_keyboard.cpp`'s synthetic
|
|
504
569
|
* pair). Hardware button releases dispatch `keyup` with the same KeyEvent.
|
|
505
|
-
* `pointercancel`
|
|
570
|
+
* Native cancellation names its event `pointercancel`; `touchcancel` is a listener alias.
|
|
506
571
|
*/
|
|
507
572
|
export interface GeaEventMap {
|
|
508
573
|
click: PressEvent
|
|
@@ -512,7 +577,12 @@ export interface GeaEventMap {
|
|
|
512
577
|
pointerdown: PointerEvent
|
|
513
578
|
pointermove: PointerEvent
|
|
514
579
|
pointerup: PointerEvent
|
|
580
|
+
pointercancel: PointerEvent
|
|
581
|
+
touchcancel: PointerEvent
|
|
515
582
|
input: InputEvent
|
|
583
|
+
focusin: Event
|
|
584
|
+
focusout: Event
|
|
585
|
+
change: InputEvent
|
|
516
586
|
keydown: KeyEvent
|
|
517
587
|
keyup: KeyEvent
|
|
518
588
|
rotary: RotaryEvent
|
|
@@ -524,6 +594,15 @@ export type PointerEventHandler = (event: PointerEvent) => void
|
|
|
524
594
|
export type RotaryEventHandler = (event: RotaryEvent) => void
|
|
525
595
|
|
|
526
596
|
export interface NativeTouchEventAttributes {
|
|
597
|
+
/** Bare JSX spellings use the same native listener publication as onX. */
|
|
598
|
+
touchstart?: TouchEventHandler
|
|
599
|
+
touchend?: TouchEventHandler
|
|
600
|
+
touchmove?: TouchEventHandler
|
|
601
|
+
touchcancel?: PointerEventHandler
|
|
602
|
+
pointerdown?: PointerEventHandler
|
|
603
|
+
pointermove?: PointerEventHandler
|
|
604
|
+
pointerup?: PointerEventHandler
|
|
605
|
+
pointercancel?: PointerEventHandler
|
|
527
606
|
onTouchStart?: TouchEventHandler
|
|
528
607
|
onTouchEnd?: TouchEventHandler
|
|
529
608
|
onTouchMove?: TouchEventHandler
|
|
@@ -538,9 +617,18 @@ export interface NativeTouchEventAttributes {
|
|
|
538
617
|
onPointerDown?: PointerEventHandler
|
|
539
618
|
onPointerMove?: PointerEventHandler
|
|
540
619
|
onPointerUp?: PointerEventHandler
|
|
620
|
+
onPointerCancel?: PointerEventHandler
|
|
541
621
|
}
|
|
542
622
|
|
|
543
623
|
export interface NativeEventAttributes extends NativeTouchEventAttributes {
|
|
624
|
+
click?: PressHandler
|
|
625
|
+
input?: (event: InputEvent) => void
|
|
626
|
+
/** Committed user value change; distinct from intermediate input. */
|
|
627
|
+
change?: (event: InputEvent) => void
|
|
628
|
+
onChange?: (event: InputEvent) => void
|
|
629
|
+
keydown?: (event: KeyEvent) => void
|
|
630
|
+
keyup?: (event: KeyEvent) => void
|
|
631
|
+
scroll?: (event: Event) => void
|
|
544
632
|
/** Text services and app-owned editors may dispatch input on ordinary views. */
|
|
545
633
|
onInput?: (event: InputEvent) => void
|
|
546
634
|
onClick?: PressHandler
|
|
@@ -551,6 +639,7 @@ export interface NativeEventAttributes extends NativeTouchEventAttributes {
|
|
|
551
639
|
}
|
|
552
640
|
|
|
553
641
|
export interface InputEventTarget extends Element {
|
|
642
|
+
disabled: boolean
|
|
554
643
|
value: string
|
|
555
644
|
}
|
|
556
645
|
|
|
@@ -559,9 +648,19 @@ export interface InputEvent {
|
|
|
559
648
|
currentTarget: InputEventTarget
|
|
560
649
|
}
|
|
561
650
|
|
|
562
|
-
export interface KeyEvent {
|
|
563
|
-
|
|
564
|
-
|
|
651
|
+
export interface KeyEvent extends Event {
|
|
652
|
+
readonly target: Element
|
|
653
|
+
readonly currentTarget: Element
|
|
654
|
+
readonly keyCode: number
|
|
655
|
+
readonly which: number
|
|
656
|
+
readonly key: string
|
|
657
|
+
readonly code: string
|
|
658
|
+
readonly altKey: boolean
|
|
659
|
+
readonly ctrlKey: boolean
|
|
660
|
+
readonly metaKey: boolean
|
|
661
|
+
readonly shiftKey: boolean
|
|
662
|
+
readonly repeat: boolean
|
|
663
|
+
readonly isComposing: boolean
|
|
565
664
|
}
|
|
566
665
|
|
|
567
666
|
export interface ViewProps extends DataAttributes {
|
|
@@ -1349,6 +1448,32 @@ export interface UartController {
|
|
|
1349
1448
|
|
|
1350
1449
|
export declare const Uart: UartController
|
|
1351
1450
|
|
|
1451
|
+
/** ESP-NOW v2 datagrams; addresses are six-byte MACs. Unsupported targets return false/-1. */
|
|
1452
|
+
export interface EspNowController {
|
|
1453
|
+
/** Integer quarter-dBm cap, 8..84; fails on closed/borrowed radios. The SDK quantizes it. */
|
|
1454
|
+
setMaxTxPower(quarterDbm: number): boolean
|
|
1455
|
+
/** Actual cap in quarter-dBm, or -1 when unavailable. */
|
|
1456
|
+
getMaxTxPower(): number
|
|
1457
|
+
/**
|
|
1458
|
+
* Channel 0 borrows running Wi-Fi. Other channels claim an idle radio.
|
|
1459
|
+
* Default 54; rates 24/36/54 use OFDM, 65 uses HT20 MCS7.
|
|
1460
|
+
* ESP32-S31 also accepts 115 for HE20 MCS9 short GI (nominal 114.7 Mbps).
|
|
1461
|
+
* Unsupported rates/protocols return false without changing a borrowed radio.
|
|
1462
|
+
*/
|
|
1463
|
+
open(channel: number, rateMbps?: number): boolean
|
|
1464
|
+
isOpen(): boolean
|
|
1465
|
+
/** True when the previous radio send has completed. */
|
|
1466
|
+
canSend(): boolean
|
|
1467
|
+
close(): void
|
|
1468
|
+
/** Optional key is a 16-byte LMK; omit only for unencrypted traffic. */
|
|
1469
|
+
addPeer(address: Uint8Array, key?: Uint8Array): boolean
|
|
1470
|
+
/** Whole datagram admission: bytes, 0 when busy, -1 for invalid/unavailable. */
|
|
1471
|
+
send(address: Uint8Array, bytes: Uint8Array): number
|
|
1472
|
+
/** source must be 6 bytes, buffer at least 1470. Returns received length, 0, or -1. */
|
|
1473
|
+
receive(source: Uint8Array, buffer: Uint8Array): number
|
|
1474
|
+
}
|
|
1475
|
+
export declare const EspNow: EspNowController
|
|
1476
|
+
|
|
1352
1477
|
export interface LedController {
|
|
1353
1478
|
// A WS2812/NeoPixel strand on one pin — what "the onboard LED" is on most
|
|
1354
1479
|
// modern ESP32 modules.
|
|
@@ -1550,6 +1675,29 @@ export interface AccelerometerController {
|
|
|
1550
1675
|
|
|
1551
1676
|
export declare const Accelerometer: AccelerometerController
|
|
1552
1677
|
|
|
1678
|
+
export interface MagnetometerReading {
|
|
1679
|
+
/** Compensated whole microtesla; saturated axes retain their sensor sentinel. */
|
|
1680
|
+
x: number
|
|
1681
|
+
y: number
|
|
1682
|
+
z: number
|
|
1683
|
+
/** False for an unavailable sensor or failed I2C read. */
|
|
1684
|
+
valid: boolean
|
|
1685
|
+
/** Saturated-axis bits: X=1, Y=2, Z=4. Those values are not field strength. */
|
|
1686
|
+
saturatedAxes: number
|
|
1687
|
+
timestampUs: number
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
export interface MagnetometerController {
|
|
1691
|
+
/** Initializes the board's sensors. Idempotent; false when unavailable. */
|
|
1692
|
+
open(): boolean
|
|
1693
|
+
close(): void
|
|
1694
|
+
/** Number of board-defined sensors, whether open or closed. */
|
|
1695
|
+
count(): number
|
|
1696
|
+
/** Board-defined sensor index, 0 to count() - 1. */
|
|
1697
|
+
read(index: number): MagnetometerReading
|
|
1698
|
+
}
|
|
1699
|
+
export declare const Magnetometer: MagnetometerController
|
|
1700
|
+
|
|
1553
1701
|
export type CameraFacing = 'front' | 'back' | 'external'
|
|
1554
1702
|
export type CameraFlashMode = 'off' | 'on' | 'auto' | 'torch'
|
|
1555
1703
|
|
|
@@ -1814,7 +1962,7 @@ export interface InputElementProps {
|
|
|
1814
1962
|
keyup?: (keyCode: number) => void
|
|
1815
1963
|
}
|
|
1816
1964
|
|
|
1817
|
-
export interface NativeInputElementProps extends InputElementProps, DataAttributes {
|
|
1965
|
+
export interface NativeInputElementProps extends Omit<InputElementProps, 'input' | 'keydown' | 'keyup'>, NativeEventAttributes, DataAttributes {
|
|
1818
1966
|
id?: string
|
|
1819
1967
|
ref?: GeaElement | null
|
|
1820
1968
|
}
|
|
@@ -1829,7 +1977,7 @@ export interface TextAreaElementProps {
|
|
|
1829
1977
|
onBlur?: () => void
|
|
1830
1978
|
}
|
|
1831
1979
|
|
|
1832
|
-
export interface NativeTextAreaElementProps extends TextAreaElementProps, DataAttributes {
|
|
1980
|
+
export interface NativeTextAreaElementProps extends TextAreaElementProps, NativeEventAttributes, DataAttributes {
|
|
1833
1981
|
id?: string
|
|
1834
1982
|
disabled?: boolean
|
|
1835
1983
|
readOnly?: boolean
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geastack/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.44",
|
|
4
4
|
"description": "GeaStack app API, reactivity headers, runtime sources, and type declarations.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -66,8 +66,11 @@
|
|
|
66
66
|
"@geajs/core": "1.5.1",
|
|
67
67
|
"@geajs/vite-plugin": "1.5.0",
|
|
68
68
|
"@geastack/compiler": "^1.0.29",
|
|
69
|
-
"@geastack/geatsc-plugin-gea": "0.1.
|
|
69
|
+
"@geastack/geatsc-plugin-gea": "0.1.24",
|
|
70
70
|
"opentype.js": "^2.0.0",
|
|
71
71
|
"vite": "^8.0.0"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"jsdom": "^26.1.0"
|
|
72
75
|
}
|
|
73
76
|
}
|
package/runtime/host.ts
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
// fails the representation coverage guard for the whole `Geolocation` literal
|
|
7
7
|
// below. lib.dom's GeolocationPositionError also carries PERMISSION_DENIED /
|
|
8
8
|
// POSITION_UNAVAILABLE / TIMEOUT, which our host object never sets.
|
|
9
|
-
import type { UartOptions } from '../index'
|
|
10
9
|
import type {
|
|
10
|
+
UartOptions,
|
|
11
|
+
MagnetometerReading,
|
|
11
12
|
CanvasRenderingContext2D,
|
|
12
13
|
GeolocationCoordinates,
|
|
13
14
|
GeolocationPosition,
|
|
@@ -355,6 +356,12 @@ declare const __gea_Gpio: {
|
|
|
355
356
|
write(pin: number, level: boolean): boolean
|
|
356
357
|
read(pin: number): boolean
|
|
357
358
|
}
|
|
359
|
+
declare const __gea_Magnetometer: {
|
|
360
|
+
open(): boolean
|
|
361
|
+
close(): void
|
|
362
|
+
count(): number
|
|
363
|
+
read(index: number, values: Float64Array): boolean
|
|
364
|
+
}
|
|
358
365
|
declare const __gea_Uart: {
|
|
359
366
|
open(port: number, tx: number, rx: number, baud: number, rxBytes: number, txBytes: number): boolean
|
|
360
367
|
isOpen(port: number): boolean
|
|
@@ -364,6 +371,17 @@ declare const __gea_Uart: {
|
|
|
364
371
|
available(port: number): number
|
|
365
372
|
writable(port: number): number
|
|
366
373
|
}
|
|
374
|
+
declare const __gea_EspNow: {
|
|
375
|
+
open(channel: number, rate: number): boolean
|
|
376
|
+
setMaxTxPower(quarterDbm: number): boolean
|
|
377
|
+
getMaxTxPower(): number
|
|
378
|
+
isOpen(): boolean
|
|
379
|
+
canSend(): boolean
|
|
380
|
+
close(): void
|
|
381
|
+
addPeer(address: Uint8Array, key: Uint8Array): boolean
|
|
382
|
+
send(address: Uint8Array, bytes: Uint8Array): number
|
|
383
|
+
receive(source: Uint8Array, bytes: Uint8Array): number
|
|
384
|
+
}
|
|
367
385
|
declare const __gea_Led: {
|
|
368
386
|
// A WS2812/NeoPixel strand on one pin — what "the onboard LED" is on most
|
|
369
387
|
// modern ESP32 modules. set() is the one-call default (attach if needed,
|
|
@@ -1066,6 +1084,48 @@ export const Uart = {
|
|
|
1066
1084
|
return typeof __gea_Uart !== 'undefined' ? __gea_Uart.writable(port) : -1
|
|
1067
1085
|
},
|
|
1068
1086
|
}
|
|
1087
|
+
export const Magnetometer = {
|
|
1088
|
+
open(): boolean {
|
|
1089
|
+
return typeof __gea_Magnetometer !== 'undefined' && __gea_Magnetometer.open()
|
|
1090
|
+
},
|
|
1091
|
+
close(): void {
|
|
1092
|
+
if (typeof __gea_Magnetometer !== 'undefined') __gea_Magnetometer.close()
|
|
1093
|
+
},
|
|
1094
|
+
count(): number {
|
|
1095
|
+
return typeof __gea_Magnetometer !== 'undefined' ? __gea_Magnetometer.count() : 0
|
|
1096
|
+
},
|
|
1097
|
+
read(index: number): MagnetometerReading {
|
|
1098
|
+
const values = new Float64Array(5)
|
|
1099
|
+
const valid = typeof __gea_Magnetometer !== 'undefined' && __gea_Magnetometer.read(index, values)
|
|
1100
|
+
return { x: values[0], y: values[1], z: values[2], valid, saturatedAxes: values[3], timestampUs: values[4] }
|
|
1101
|
+
},
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
export const EspNow = {
|
|
1105
|
+
// 115 selects S31 HE20 MCS9 short GI (114.7 Mbps); target validation owns
|
|
1106
|
+
// availability. Other rates and the default retain their existing meaning.
|
|
1107
|
+
open(channel: number, rateMbps: number = 54): boolean {
|
|
1108
|
+
return typeof __gea_EspNow !== 'undefined' && __gea_EspNow.open(channel, rateMbps)
|
|
1109
|
+
},
|
|
1110
|
+
setMaxTxPower(quarterDbm: number): boolean {
|
|
1111
|
+
return typeof __gea_EspNow !== 'undefined' && __gea_EspNow.setMaxTxPower(quarterDbm)
|
|
1112
|
+
},
|
|
1113
|
+
getMaxTxPower(): number {
|
|
1114
|
+
return typeof __gea_EspNow !== 'undefined' ? __gea_EspNow.getMaxTxPower() : -1
|
|
1115
|
+
},
|
|
1116
|
+
isOpen(): boolean { return typeof __gea_EspNow !== 'undefined' && __gea_EspNow.isOpen() },
|
|
1117
|
+
canSend(): boolean { return typeof __gea_EspNow !== 'undefined' && __gea_EspNow.canSend() },
|
|
1118
|
+
close(): void { if (typeof __gea_EspNow !== 'undefined') __gea_EspNow.close() },
|
|
1119
|
+
addPeer(address: Uint8Array, key: Uint8Array = new Uint8Array(0)): boolean {
|
|
1120
|
+
return typeof __gea_EspNow !== 'undefined' && __gea_EspNow.addPeer(address, key)
|
|
1121
|
+
},
|
|
1122
|
+
send(address: Uint8Array, bytes: Uint8Array): number {
|
|
1123
|
+
return typeof __gea_EspNow !== 'undefined' ? __gea_EspNow.send(address, bytes) : -1
|
|
1124
|
+
},
|
|
1125
|
+
receive(source: Uint8Array, bytes: Uint8Array): number {
|
|
1126
|
+
return typeof __gea_EspNow !== 'undefined' ? __gea_EspNow.receive(source, bytes) : -1
|
|
1127
|
+
},
|
|
1128
|
+
}
|
|
1069
1129
|
export const Led = {
|
|
1070
1130
|
set(pin: number, r: number, g: number, b: number): boolean {
|
|
1071
1131
|
return __gea_Led.set(pin, r, g, b)
|
package/runtime-surface.ts
CHANGED
|
@@ -59,8 +59,11 @@ export type {
|
|
|
59
59
|
export type {
|
|
60
60
|
UartOptions,
|
|
61
61
|
UartController,
|
|
62
|
+
EspNowController,
|
|
62
63
|
AccelerometerController,
|
|
63
64
|
AccelerometerReading,
|
|
65
|
+
MagnetometerController,
|
|
66
|
+
MagnetometerReading,
|
|
64
67
|
AppsController,
|
|
65
68
|
AudioBuffer,
|
|
66
69
|
AudioBufferSourceNode,
|
|
@@ -130,6 +133,8 @@ export type {
|
|
|
130
133
|
InputElementProps,
|
|
131
134
|
InputEvent,
|
|
132
135
|
InputEventTarget,
|
|
136
|
+
InputElement,
|
|
137
|
+
ButtonElement,
|
|
133
138
|
KeyEvent,
|
|
134
139
|
MediaDevices,
|
|
135
140
|
MediaRecorder,
|