@geastack/core 0.1.41 → 0.1.43

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.
@@ -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
@@ -0,0 +1,58 @@
1
+ #pragma once
2
+
3
+ #include <cstddef>
4
+ #include <cstdint>
5
+
6
+ // Target-owned implementation. Unsupported platforms report unavailable.
7
+ extern "C" {
8
+ bool gea_uart_open(int port, int tx, int rx, int baud, int rxBytes, int txBytes)
9
+ __attribute__((weak));
10
+ bool gea_uart_is_open(int port) __attribute__((weak));
11
+ void gea_uart_close(int port) __attribute__((weak));
12
+ int gea_uart_read(int port, void *bytes, std::size_t capacity)
13
+ __attribute__((weak));
14
+ int gea_uart_write(int port, const void *bytes, std::size_t length)
15
+ __attribute__((weak));
16
+ int gea_uart_available(int port) __attribute__((weak));
17
+ int gea_uart_writable(int port) __attribute__((weak));
18
+ }
19
+
20
+ namespace gea::platform::uart {
21
+ struct Config {
22
+ int tx;
23
+ int rx;
24
+ int baudRate;
25
+ int rxBufferSize = 4096;
26
+ int txBufferSize = 4096;
27
+ };
28
+
29
+ class Uart {
30
+ public:
31
+ static bool open(int port, const Config &config) {
32
+ return gea_uart_open &&
33
+ gea_uart_open(port, config.tx, config.rx, config.baudRate,
34
+ config.rxBufferSize, config.txBufferSize);
35
+ }
36
+ static bool isOpen(int port) {
37
+ return gea_uart_is_open && gea_uart_is_open(port);
38
+ }
39
+ static void close(int port) {
40
+ if (gea_uart_close)
41
+ gea_uart_close(port);
42
+ }
43
+ // Nonblocking. -1 means unavailable/closed/error; 0 means no progress.
44
+ static int read(int port, void *bytes, std::size_t capacity) {
45
+ return gea_uart_read ? gea_uart_read(port, bytes, capacity) : -1;
46
+ }
47
+ // Enqueues the complete buffer, or returns 0 if it cannot fit yet.
48
+ static int write(int port, const void *bytes, std::size_t length) {
49
+ return gea_uart_write ? gea_uart_write(port, bytes, length) : -1;
50
+ }
51
+ static int available(int port) {
52
+ return gea_uart_available ? gea_uart_available(port) : -1;
53
+ }
54
+ static int writable(int port) {
55
+ return gea_uart_writable ? gea_uart_writable(port) : -1;
56
+ }
57
+ };
58
+ } // namespace gea::platform::uart
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
- readonly nodeType?: number
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` remains unsupported by the native event dispatcher.
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
- keyCode: number
564
- which: number
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 {
@@ -1322,6 +1421,59 @@ export interface GpioController {
1322
1421
 
1323
1422
  export declare const Gpio: GpioController
1324
1423
 
1424
+ export interface UartOptions {
1425
+ tx: number
1426
+ rx: number
1427
+ baudRate: number
1428
+ /** Receive/transmit queue bytes. Defaults to 4096 each. */
1429
+ rxBufferSize?: number
1430
+ txBufferSize?: number
1431
+ }
1432
+
1433
+ /** Hardware UART, 8 data bits, no parity, one stop bit (8N1). */
1434
+ export interface UartController {
1435
+ /** Claims a free controller and pins. False on conflict/invalid configuration. */
1436
+ open(port: number, options: UartOptions): boolean
1437
+ isOpen(port: number): boolean
1438
+ close(port: number): void
1439
+ /** Nonblocking read into the supplied view; returns bytes read, or -1 on error. */
1440
+ read(port: number, buffer: Uint8Array): number
1441
+ /** Nonblocking whole-buffer enqueue; 0 means backpressure, -1 means error. */
1442
+ write(port: number, buffer: Uint8Array): number
1443
+ /** Queued readable bytes, or -1 when unavailable. */
1444
+ available(port: number): number
1445
+ /** Writable queue capacity, or -1 when unavailable. */
1446
+ writable(port: number): number
1447
+ }
1448
+
1449
+ export declare const Uart: UartController
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
+
1325
1477
  export interface LedController {
1326
1478
  // A WS2812/NeoPixel strand on one pin — what "the onboard LED" is on most
1327
1479
  // modern ESP32 modules.
@@ -1523,6 +1675,29 @@ export interface AccelerometerController {
1523
1675
 
1524
1676
  export declare const Accelerometer: AccelerometerController
1525
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
+ /** Mosaico indexes: 0=right (0x11), 1=left (0x12). */
1697
+ read(index: number): MagnetometerReading
1698
+ }
1699
+ export declare const Magnetometer: MagnetometerController
1700
+
1526
1701
  export type CameraFacing = 'front' | 'back' | 'external'
1527
1702
  export type CameraFlashMode = 'off' | 'on' | 'auto' | 'torch'
1528
1703
 
@@ -1787,7 +1962,7 @@ export interface InputElementProps {
1787
1962
  keyup?: (keyCode: number) => void
1788
1963
  }
1789
1964
 
1790
- export interface NativeInputElementProps extends InputElementProps, DataAttributes {
1965
+ export interface NativeInputElementProps extends Omit<InputElementProps, 'input' | 'keydown' | 'keyup'>, NativeEventAttributes, DataAttributes {
1791
1966
  id?: string
1792
1967
  ref?: GeaElement | null
1793
1968
  }
@@ -1802,7 +1977,7 @@ export interface TextAreaElementProps {
1802
1977
  onBlur?: () => void
1803
1978
  }
1804
1979
 
1805
- export interface NativeTextAreaElementProps extends TextAreaElementProps, DataAttributes {
1980
+ export interface NativeTextAreaElementProps extends TextAreaElementProps, NativeEventAttributes, DataAttributes {
1806
1981
  id?: string
1807
1982
  disabled?: boolean
1808
1983
  readOnly?: boolean
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geastack/core",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
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.22",
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
  }