drawnui-react 0.1.0-preview.12 → 0.1.0-preview.14
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/SKIPPED.md +6 -5
- package/dist/controls/GridStructure.d.ts +4 -2
- package/dist/controls/GridStructure.js +2 -2
- package/dist/controls/SkiaButton.d.ts +2 -0
- package/dist/controls/SkiaButton.js +2 -0
- package/dist/controls/SkiaCarousel.js +4 -0
- package/dist/controls/SkiaEditor.d.ts +7 -0
- package/dist/controls/SkiaEditor.js +9 -1
- package/dist/controls/SkiaGif.js +1 -1
- package/dist/controls/SkiaLabelFps.d.ts +16 -0
- package/dist/controls/SkiaLabelFps.js +41 -0
- package/dist/controls/SkiaLayout.d.ts +32 -6
- package/dist/controls/SkiaLayout.js +171 -10
- package/dist/controls/SkiaLottie.js +1 -1
- package/dist/controls/SkiaScroll.d.ts +5 -4
- package/dist/controls/SkiaScroll.js +27 -8
- package/dist/controls/SkiaSlider.d.ts +8 -0
- package/dist/controls/SkiaSlider.js +39 -0
- package/dist/controls/SkiaSvg.js +1 -1
- package/dist/core/Accessibility.d.ts +59 -0
- package/dist/core/Accessibility.js +303 -3
- package/dist/core/Canvas.d.ts +8 -0
- package/dist/core/Canvas.js +14 -1
- package/dist/core/Gestures.d.ts +5 -1
- package/dist/core/Gestures.js +4 -1
- package/dist/core/SkiaControl.d.ts +27 -2
- package/dist/core/SkiaControl.js +38 -4
- package/dist/core/SkiaEffect.d.ts +4 -0
- package/dist/core/SkiaEffect.js +17 -3
- package/dist/core/SkiaImageManager.js +1 -1
- package/dist/core/Super.d.ts +7 -0
- package/dist/core/Super.js +15 -1
- package/dist/core/ViewsAdapter.d.ts +7 -2
- package/dist/core/ViewsAdapter.js +10 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/react/index.d.ts +4 -1
- package/dist/react/index.js +63 -9
- package/dist/react/reconciler.js +2 -1
- package/dist/vite/index.d.ts +9 -0
- package/dist/vite/index.js +36 -3
- package/package.json +4 -1
package/dist/core/Gestures.js
CHANGED
|
@@ -43,7 +43,10 @@ export class TouchActionEventArgs {
|
|
|
43
43
|
Timestamp = performance.now();
|
|
44
44
|
/** ms since the previous event of the same pointer. */
|
|
45
45
|
DeltaTimeMs = 0;
|
|
46
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Mouse wheel: Delta > 0 = wheel down / right (browser sign). IsHorizontal: the event is along the X axis (a tilting
|
|
48
|
+
* wheel, the sideways part of a two-finger touchpad swipe), as C# WheelEventArgs.IsHorizontal.
|
|
49
|
+
*/
|
|
47
50
|
Wheel = { Delta: 0 };
|
|
48
51
|
/**
|
|
49
52
|
* AppoMobi TouchActionEventArgs.Handled: set by a control that actually used the event. The canvas prevents the
|
|
@@ -3,6 +3,7 @@ import { type Color, type LayoutOptions, SKRect, ScaledSize, type SkiaCacheType,
|
|
|
3
3
|
import { type AnimatorBase, type IOverlayEffect } from "./Animators";
|
|
4
4
|
import { Easing } from "./Easing";
|
|
5
5
|
import type { Canvas } from "./Canvas";
|
|
6
|
+
import type { InputKey } from "./KeyboardManager";
|
|
6
7
|
import { ContextMenuEventArgs, ControlTappedEventArgs, GestureEventProcessingInfo, type LockTouch, SKPoint, SkiaGesturesInfo, SkiaGesturesParameters } from "./Gestures";
|
|
7
8
|
import { type CachedTexture, type IPostRendererEffect, type SkiaEffect } from "./SkiaEffect";
|
|
8
9
|
/** Mirrors DrawnUi DrawingContext: ctx.Context.Canvas / Surface, ctx.Destination (pixels), ctx.Scale. */
|
|
@@ -180,7 +181,12 @@ export declare class SkiaControl {
|
|
|
180
181
|
protected OnVisualEffectsChanged(): void;
|
|
181
182
|
/** Post renderers that can render this frame (compiled shaders); an unready effect leaves the control drawn plainly. */
|
|
182
183
|
private ActivePostRenderers;
|
|
183
|
-
/**
|
|
184
|
+
/**
|
|
185
|
+
* C# CachedImage: the cached texture (Image caches only) and the canvas rect it covers NOW. Scrolled content is
|
|
186
|
+
* re-arranged here, not drawn through a translated canvas, so a cache recorded before its control moved is blitted
|
|
187
|
+
* at the control's current aligned rect (RenderContent) and its record-time Bounds are stale: an effect sampling it
|
|
188
|
+
* there (the slides of a SkiaShaderCarousel after the page scrolled) was off by the scroll amount.
|
|
189
|
+
*/
|
|
184
190
|
get CachedImage(): CachedTexture | undefined;
|
|
185
191
|
/** Clip overlay effects to the control's shape (CreateClip). */
|
|
186
192
|
ClipEffects: boolean;
|
|
@@ -251,7 +257,11 @@ export declare class SkiaControl {
|
|
|
251
257
|
protected DefaultAccessibilityLabel(): string | undefined;
|
|
252
258
|
get AccessibilityHint(): string | undefined;
|
|
253
259
|
set AccessibilityHint(v: string | undefined);
|
|
254
|
-
/**
|
|
260
|
+
/**
|
|
261
|
+
* Whether keyboard navigation and assistive technology may focus and use this node: the explicit value, else the class
|
|
262
|
+
* default (true when the control handles Tapped), and in both cases only while a tap could reach the control
|
|
263
|
+
* (CanReceiveGesture), so Tab, Enter / Space and screen-reader activation never use a control the pointer cannot.
|
|
264
|
+
*/
|
|
255
265
|
get AccessibilityCanInteract(): boolean;
|
|
256
266
|
set AccessibilityCanInteract(v: boolean);
|
|
257
267
|
protected DefaultAccessibilityCanInteract(): boolean;
|
|
@@ -288,6 +298,21 @@ export declare class SkiaControl {
|
|
|
288
298
|
OnAccessibilityActivated(): void;
|
|
289
299
|
/** Overlay focus arrives on / leaves this node (inputs may activate their sink here). */
|
|
290
300
|
OnAccessibilityFocused(_focused: boolean): void;
|
|
301
|
+
/**
|
|
302
|
+
* Keyboard navigation hands this control the keys it may use while it holds keyboard focus (ArrowLeft / Right / Up /
|
|
303
|
+
* Down, Home, End, PageUp, PageDown, DOM `event.code` names). Return true when the key was used; the default uses none,
|
|
304
|
+
* so the key moves focus inside the arrow-key group around the control.
|
|
305
|
+
*/
|
|
306
|
+
OnAccessibilityKey(_key: InputKey): boolean;
|
|
307
|
+
/**
|
|
308
|
+
* Whether a pointer gesture of this kind would reach this control, by the rules the gesture dispatch applies: the
|
|
309
|
+
* control and every ancestor draw, none of them is InputTransparent, no ancestor keeps this gesture from its children
|
|
310
|
+
* (LockChildrenGestures, PassNone included) and the control accepts input (AcceptsInput). Opacity does not count, as
|
|
311
|
+
* for the pointer. Keyboard navigation uses it: Tapped for Tab and Enter / Space, Panning for the arrow keys.
|
|
312
|
+
*/
|
|
313
|
+
CanReceiveGesture(gesture: SkiaGesturesParameters["Type"]): boolean;
|
|
314
|
+
/** False while the control ignores gestures itself, e.g. a disabled button. Default true. */
|
|
315
|
+
protected AcceptsInput(): boolean;
|
|
291
316
|
NotifyAccessibilityFocused(focused: boolean): void;
|
|
292
317
|
/** DrawnUi SmartMax: larger of two sizes, an infinite one loses. */
|
|
293
318
|
protected static SmartMax(a: number, b: number): number;
|
package/dist/core/SkiaControl.js
CHANGED
|
@@ -235,10 +235,18 @@ export class SkiaControl {
|
|
|
235
235
|
return [];
|
|
236
236
|
return this.EffectPostRenderers.filter((e) => e.NeedApply);
|
|
237
237
|
}
|
|
238
|
-
/**
|
|
238
|
+
/**
|
|
239
|
+
* C# CachedImage: the cached texture (Image caches only) and the canvas rect it covers NOW. Scrolled content is
|
|
240
|
+
* re-arranged here, not drawn through a translated canvas, so a cache recorded before its control moved is blitted
|
|
241
|
+
* at the control's current aligned rect (RenderContent) and its record-time Bounds are stale: an effect sampling it
|
|
242
|
+
* there (the slides of a SkiaShaderCarousel after the page scrolled) was off by the scroll amount.
|
|
243
|
+
*/
|
|
239
244
|
get CachedImage() {
|
|
240
245
|
const c = this.RenderObject;
|
|
241
|
-
|
|
246
|
+
if (!c?.Image)
|
|
247
|
+
return undefined;
|
|
248
|
+
const r = this.AlignedCacheRect(c.Scale);
|
|
249
|
+
return { Image: c.Image, Bounds: SKRect.Create(r.Left, r.Top, c.Bounds.Width, c.Bounds.Height) };
|
|
242
250
|
}
|
|
243
251
|
/** Clip overlay effects to the control's shape (CreateClip). */
|
|
244
252
|
ClipEffects = true;
|
|
@@ -417,8 +425,12 @@ export class SkiaControl {
|
|
|
417
425
|
this.accessibilityHint = v;
|
|
418
426
|
this.AccessibilityChanged();
|
|
419
427
|
} }
|
|
420
|
-
/**
|
|
421
|
-
|
|
428
|
+
/**
|
|
429
|
+
* Whether keyboard navigation and assistive technology may focus and use this node: the explicit value, else the class
|
|
430
|
+
* default (true when the control handles Tapped), and in both cases only while a tap could reach the control
|
|
431
|
+
* (CanReceiveGesture), so Tab, Enter / Space and screen-reader activation never use a control the pointer cannot.
|
|
432
|
+
*/
|
|
433
|
+
get AccessibilityCanInteract() { return (this.accessibilityCanInteract ?? this.DefaultAccessibilityCanInteract()) && this.CanReceiveGesture("Tapped"); }
|
|
422
434
|
set AccessibilityCanInteract(v) { if (this.accessibilityCanInteract !== v) {
|
|
423
435
|
this.accessibilityCanInteract = v;
|
|
424
436
|
this.AccessibilityChanged();
|
|
@@ -503,6 +515,28 @@ export class SkiaControl {
|
|
|
503
515
|
}
|
|
504
516
|
/** Overlay focus arrives on / leaves this node (inputs may activate their sink here). */
|
|
505
517
|
OnAccessibilityFocused(_focused) { }
|
|
518
|
+
/**
|
|
519
|
+
* Keyboard navigation hands this control the keys it may use while it holds keyboard focus (ArrowLeft / Right / Up /
|
|
520
|
+
* Down, Home, End, PageUp, PageDown, DOM `event.code` names). Return true when the key was used; the default uses none,
|
|
521
|
+
* so the key moves focus inside the arrow-key group around the control.
|
|
522
|
+
*/
|
|
523
|
+
OnAccessibilityKey(_key) { return false; }
|
|
524
|
+
/**
|
|
525
|
+
* Whether a pointer gesture of this kind would reach this control, by the rules the gesture dispatch applies: the
|
|
526
|
+
* control and every ancestor draw, none of them is InputTransparent, no ancestor keeps this gesture from its children
|
|
527
|
+
* (LockChildrenGestures, PassNone included) and the control accepts input (AcceptsInput). Opacity does not count, as
|
|
528
|
+
* for the pointer. Keyboard navigation uses it: Tapped for Tab and Enter / Space, Panning for the arrow keys.
|
|
529
|
+
*/
|
|
530
|
+
CanReceiveGesture(gesture) {
|
|
531
|
+
if (!this.IsVisible || this.IsDisposed || this.InputTransparent || !this.AcceptsInput())
|
|
532
|
+
return false;
|
|
533
|
+
for (let p = this.Parent; p; p = p.Parent)
|
|
534
|
+
if (!p.IsVisible || p.IsDisposed || p.InputTransparent || p.CheckChildrenGesturesLocked(gesture))
|
|
535
|
+
return false;
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
/** False while the control ignores gestures itself, e.g. a disabled button. Default true. */
|
|
539
|
+
AcceptsInput() { return true; }
|
|
506
540
|
NotifyAccessibilityFocused(focused) { this.Superview?.AccessibilityManager.NotifyFocused(focused ? this : undefined); }
|
|
507
541
|
/** DrawnUi SmartMax: larger of two sizes, an infinite one loses. */
|
|
508
542
|
static SmartMax(a, b) {
|
|
@@ -125,6 +125,10 @@ export declare class SkiaShaderEffect extends SkiaEffect implements IPostRendere
|
|
|
125
125
|
protected ApplyShaderSource(): void;
|
|
126
126
|
/** Snapshot of what the parent painted (C# CreateSnapshot): the surface the pixels are on, mapped through its origin. */
|
|
127
127
|
protected CreateSnapshot(ctx: DrawingContext, destination: SKRect): CachedTexture | undefined;
|
|
128
|
+
/** Where the parent was when the texture was frozen (Once): the texture moves with it, as its cache does. */
|
|
129
|
+
private frozenAt?;
|
|
130
|
+
private ParentOrigin;
|
|
131
|
+
private FollowParent;
|
|
128
132
|
/** C# GetPrimaryTexture: the parent's cache, a frozen snapshot (Once) or nothing (Never). */
|
|
129
133
|
protected GetPrimaryTexture(_ctx: DrawingContext, _destination: SKRect): CachedTexture | undefined;
|
|
130
134
|
/** Image → shader with the effect's sampling, translated so texel (0,0) = bounds top-left; cached per image. */
|
package/dist/core/SkiaEffect.js
CHANGED
|
@@ -2,7 +2,7 @@ import { SkiaValueAnimator } from "./Animators";
|
|
|
2
2
|
import { Easing } from "./Easing";
|
|
3
3
|
import { SkiaImageManager } from "./SkiaImageManager";
|
|
4
4
|
import { Super } from "./Super";
|
|
5
|
-
import { Thickness } from "./Types";
|
|
5
|
+
import { SKRect, Thickness } from "./Types";
|
|
6
6
|
/**
|
|
7
7
|
* Mirrors DrawnUi SkiaEffect: something attached to a control through `VisualEffects`. `Update()` re-renders the
|
|
8
8
|
* parent (C# invalidates the parent's cache and redraws), `GetEffectMargin` reports what it paints outside the box.
|
|
@@ -129,6 +129,7 @@ export class SkiaShaderEffect extends SkiaEffect {
|
|
|
129
129
|
this.DisposeImage(this.frozen.Image);
|
|
130
130
|
this.frozen = undefined;
|
|
131
131
|
this.frozenOwned = false;
|
|
132
|
+
this.frozenAt = undefined;
|
|
132
133
|
}
|
|
133
134
|
DisposeImage(image) {
|
|
134
135
|
const sv = this.Parent?.Superview;
|
|
@@ -158,6 +159,18 @@ export class SkiaShaderEffect extends SkiaEffect {
|
|
|
158
159
|
return undefined;
|
|
159
160
|
return { Image: image, Bounds: destination, Origin: { X: ox, Y: oy } };
|
|
160
161
|
}
|
|
162
|
+
/** Where the parent was when the texture was frozen (Once): the texture moves with it, as its cache does. */
|
|
163
|
+
frozenAt;
|
|
164
|
+
ParentOrigin() { const r = this.Parent?.DrawingRect; return { X: r?.Left ?? 0, Y: r?.Top ?? 0 }; }
|
|
165
|
+
FollowParent(texture) {
|
|
166
|
+
if (!texture || !this.frozenAt)
|
|
167
|
+
return texture;
|
|
168
|
+
const now = this.ParentOrigin(), dx = now.X - this.frozenAt.X, dy = now.Y - this.frozenAt.Y;
|
|
169
|
+
if (dx === 0 && dy === 0)
|
|
170
|
+
return texture;
|
|
171
|
+
const b = texture.Bounds;
|
|
172
|
+
return { Image: texture.Image, Bounds: new SKRect(b.Left + dx, b.Top + dy, b.Right + dx, b.Bottom + dy), Origin: texture.Origin && { X: texture.Origin.X + dx, Y: texture.Origin.Y + dy } };
|
|
173
|
+
}
|
|
161
174
|
/** C# GetPrimaryTexture: the parent's cache, a frozen snapshot (Once) or nothing (Never). */
|
|
162
175
|
GetPrimaryTexture(_ctx, _destination) {
|
|
163
176
|
switch (this.UseBackground) {
|
|
@@ -175,9 +188,10 @@ export class SkiaShaderEffect extends SkiaEffect {
|
|
|
175
188
|
this.frozen = snapshot;
|
|
176
189
|
this.frozenOwned = owned;
|
|
177
190
|
this.AquiredBackground = true;
|
|
191
|
+
this.frozenAt = this.ParentOrigin();
|
|
178
192
|
}
|
|
179
193
|
}
|
|
180
|
-
return this.frozen;
|
|
194
|
+
return this.FollowParent(this.frozen);
|
|
181
195
|
default:
|
|
182
196
|
return this.Parent?.CachedImage;
|
|
183
197
|
}
|
|
@@ -228,7 +242,7 @@ export class SkiaShaderEffect extends SkiaEffect {
|
|
|
228
242
|
static async FetchText(url) {
|
|
229
243
|
let p = sourceCache.get(url);
|
|
230
244
|
if (!p) {
|
|
231
|
-
p = fetch(url).then((r) => { if (!r.ok)
|
|
245
|
+
p = fetch(Super.ResolveAssetUrl(url)).then((r) => { if (!r.ok)
|
|
232
246
|
throw new Error(`${r.status} ${url}`); return r.text(); });
|
|
233
247
|
sourceCache.set(url, p);
|
|
234
248
|
}
|
|
@@ -39,7 +39,7 @@ export class SkiaImageManager {
|
|
|
39
39
|
return inflight;
|
|
40
40
|
}
|
|
41
41
|
async Fetch(source, signal) {
|
|
42
|
-
const response = await fetch(source, signal ? { signal } : undefined);
|
|
42
|
+
const response = await fetch(Super.ResolveAssetUrl(source), signal ? { signal } : undefined);
|
|
43
43
|
if (!response.ok)
|
|
44
44
|
throw new Error(`DrawnUi: ${response.status} loading image '${source}'`);
|
|
45
45
|
const image = Super.CK.MakeImageFromEncoded(await response.arrayBuffer());
|
package/dist/core/Super.d.ts
CHANGED
|
@@ -52,6 +52,13 @@ export declare class Super {
|
|
|
52
52
|
static UseDrawnUi(): DrawnUiBuilder;
|
|
53
53
|
/** Master switch: false makes every control render uncached (DrawnUi Super.CacheEnabled). */
|
|
54
54
|
static CacheEnabled: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Address every engine loader fetches a source from (images, sprites, fonts, SVG, GIF, Lottie, shaders); caches
|
|
57
|
+
* stay keyed by the source itself. Web-only, no C# counterpart. Default: a file listed in
|
|
58
|
+
* `globalThis.DrawnUiAssetStamps` (written into index.html by the `drawnUiAssetStamps()` Vite plugin) gets
|
|
59
|
+
* `?v=<content hash>`, so a browser never keeps an old copy of a changed file. Replace it to map sources elsewhere.
|
|
60
|
+
*/
|
|
61
|
+
static ResolveAssetUrl: (source: string) => string;
|
|
55
62
|
private static readonly colorCache;
|
|
56
63
|
/**
|
|
57
64
|
* Color string -> CanvasKit color. Hex follows the MAUI/DrawnUi convention: #RGB, #ARGB, #RRGGBB, #AARRGGBB
|
package/dist/core/Super.js
CHANGED
|
@@ -46,7 +46,7 @@ export class DrawnUiBuilder {
|
|
|
46
46
|
for (const f of this.fonts.Fonts) {
|
|
47
47
|
let face = loaded.get(f.Source);
|
|
48
48
|
if (!face) {
|
|
49
|
-
const data = await (await fetch(f.Source)).arrayBuffer();
|
|
49
|
+
const data = await (await fetch(Super.ResolveAssetUrl(f.Source))).arrayBuffer();
|
|
50
50
|
face = Super.CK.Typeface.MakeFreeTypeFaceFromData(data) ?? undefined;
|
|
51
51
|
if (!face)
|
|
52
52
|
throw new Error(`DrawnUi: cannot load font '${f.Source}'`);
|
|
@@ -122,6 +122,20 @@ export class Super {
|
|
|
122
122
|
static UseDrawnUi() { return new DrawnUiBuilder(); }
|
|
123
123
|
/** Master switch: false makes every control render uncached (DrawnUi Super.CacheEnabled). */
|
|
124
124
|
static CacheEnabled = true;
|
|
125
|
+
/**
|
|
126
|
+
* Address every engine loader fetches a source from (images, sprites, fonts, SVG, GIF, Lottie, shaders); caches
|
|
127
|
+
* stay keyed by the source itself. Web-only, no C# counterpart. Default: a file listed in
|
|
128
|
+
* `globalThis.DrawnUiAssetStamps` (written into index.html by the `drawnUiAssetStamps()` Vite plugin) gets
|
|
129
|
+
* `?v=<content hash>`, so a browser never keeps an old copy of a changed file. Replace it to map sources elsewhere.
|
|
130
|
+
*/
|
|
131
|
+
static ResolveAssetUrl = (source) => {
|
|
132
|
+
const stamps = globalThis.DrawnUiAssetStamps;
|
|
133
|
+
if (!stamps)
|
|
134
|
+
return source;
|
|
135
|
+
const q = source.indexOf("?");
|
|
136
|
+
const v = stamps[(q < 0 ? source : source.slice(0, q)).replace(/^\.?\//, "")];
|
|
137
|
+
return v ? `${source}${q < 0 ? "?" : "&"}v=${v}` : source;
|
|
138
|
+
};
|
|
125
139
|
static colorCache = new Map();
|
|
126
140
|
/**
|
|
127
141
|
* Color string -> CanvasKit color. Hex follows the MAUI/DrawnUi convention: #RGB, #ARGB, #RRGGBB, #AARRGGBB
|
|
@@ -16,6 +16,8 @@ export declare class ViewsAdapter {
|
|
|
16
16
|
Created: number;
|
|
17
17
|
constructor(parent: SkiaControl);
|
|
18
18
|
get PoolSize(): number;
|
|
19
|
+
/** The items views are bound to. */
|
|
20
|
+
get Items(): readonly unknown[];
|
|
19
21
|
get InUseCount(): number;
|
|
20
22
|
/** New template / items / mode: every realized view is dropped (DrawnUi ApplyItemsSource). */
|
|
21
23
|
Initialize(template: (() => SkiaControl) | undefined, items: readonly unknown[], recycling: RecyclingTemplate): void;
|
|
@@ -26,8 +28,11 @@ export declare class ViewsAdapter {
|
|
|
26
28
|
ShiftIndices(by: number, items: readonly unknown[]): void;
|
|
27
29
|
/** The realized view for an index, if any (no creation). */
|
|
28
30
|
GetViewForIndex(index: number): SkiaControl | undefined;
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
+
/**
|
|
32
|
+
* View bound to items[index]: existing, recycled from the pool, or freshly created. `quiet`: bound before it is
|
|
33
|
+
* attached, so the rebind invalidates the view alone and not the layout, which measures or draws it right away.
|
|
34
|
+
*/
|
|
35
|
+
GetOrCreateViewForIndex(index: number, quiet?: boolean): SkiaControl | undefined;
|
|
31
36
|
/** Index left the visible range: recycled views go back to the pool, non-recycled ones stay realized. */
|
|
32
37
|
ReleaseViewAt(index: number): void;
|
|
33
38
|
/** Releases every realized index outside [first, last]. */
|
|
@@ -16,6 +16,8 @@ export class ViewsAdapter {
|
|
|
16
16
|
this.parent = parent;
|
|
17
17
|
}
|
|
18
18
|
get PoolSize() { return this.pool.length; }
|
|
19
|
+
/** The items views are bound to. */
|
|
20
|
+
get Items() { return this.items; }
|
|
19
21
|
get InUseCount() { return this.inUse.size; }
|
|
20
22
|
/** New template / items / mode: every realized view is dropped (DrawnUi ApplyItemsSource). */
|
|
21
23
|
Initialize(template, items, recycling) {
|
|
@@ -52,8 +54,11 @@ export class ViewsAdapter {
|
|
|
52
54
|
}
|
|
53
55
|
/** The realized view for an index, if any (no creation). */
|
|
54
56
|
GetViewForIndex(index) { return this.inUse.get(index); }
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
+
/**
|
|
58
|
+
* View bound to items[index]: existing, recycled from the pool, or freshly created. `quiet`: bound before it is
|
|
59
|
+
* attached, so the rebind invalidates the view alone and not the layout, which measures or draws it right away.
|
|
60
|
+
*/
|
|
61
|
+
GetOrCreateViewForIndex(index, quiet = false) {
|
|
57
62
|
const existing = this.inUse.get(index);
|
|
58
63
|
if (existing)
|
|
59
64
|
return existing;
|
|
@@ -64,9 +69,11 @@ export class ViewsAdapter {
|
|
|
64
69
|
view = this.template();
|
|
65
70
|
this.Created++;
|
|
66
71
|
}
|
|
67
|
-
|
|
72
|
+
if (!quiet)
|
|
73
|
+
view.Parent = this.parent;
|
|
68
74
|
view.ContextIndex = index;
|
|
69
75
|
view.BindingContext = this.items[index];
|
|
76
|
+
view.Parent = this.parent;
|
|
70
77
|
this.inUse.set(index, view);
|
|
71
78
|
return view;
|
|
72
79
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export * from "./core/Accessibility";
|
|
|
12
12
|
export * from "./controls/SkiaLabel";
|
|
13
13
|
export * from "./controls/TextSpan";
|
|
14
14
|
export * from "./controls/SkiaRichLabel";
|
|
15
|
+
export * from "./controls/SkiaLabelFps";
|
|
15
16
|
export * from "./core/ControlStyle";
|
|
16
17
|
export * from "./core/ImageEffects";
|
|
17
18
|
export * from "./controls/SkiaToggle";
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export * from "./core/Accessibility";
|
|
|
12
12
|
export * from "./controls/SkiaLabel";
|
|
13
13
|
export * from "./controls/TextSpan";
|
|
14
14
|
export * from "./controls/SkiaRichLabel";
|
|
15
|
+
export * from "./controls/SkiaLabelFps";
|
|
15
16
|
export * from "./core/ControlStyle";
|
|
16
17
|
export * from "./core/ImageEffects";
|
|
17
18
|
export * from "./controls/SkiaToggle";
|
package/dist/react/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { SkiaControl } from "../core/SkiaControl";
|
|
|
4
4
|
import type { SkiaLabel as SkiaLabelCtrl } from "../controls/SkiaLabel";
|
|
5
5
|
import type { TextSpan as TextSpanCtrl } from "../controls/TextSpan";
|
|
6
6
|
import type { SkiaRichLabel as SkiaRichLabelCtrl } from "../controls/SkiaRichLabel";
|
|
7
|
+
import type { SkiaLabelFps as SkiaLabelFpsCtrl } from "../controls/SkiaLabelFps";
|
|
7
8
|
import type { SkiaSwitch as SkiaSwitchCtrl } from "../controls/SkiaSwitch";
|
|
8
9
|
import type { SkiaCheckbox as SkiaCheckboxCtrl } from "../controls/SkiaCheckbox";
|
|
9
10
|
import type { SkiaRadioButton as SkiaRadioButtonCtrl } from "../controls/SkiaRadioButton";
|
|
@@ -22,7 +23,7 @@ import type { SkiaScrollBar as SkiaScrollBarCtrl } from "../controls/SkiaScrollB
|
|
|
22
23
|
import type { RefreshIndicator as RefreshIndicatorCtrl } from "../controls/RefreshIndicator";
|
|
23
24
|
import type { SkiaSvg as SkiaSvgCtrl } from "../controls/SkiaSvg";
|
|
24
25
|
import type { SkiaBackdrop as SkiaBackdropCtrl } from "../controls/SkiaBackdrop";
|
|
25
|
-
import
|
|
26
|
+
import { SkiaEditor as SkiaEditorCtrl } from "../controls/SkiaEditor";
|
|
26
27
|
import type { SkiaSprite as SkiaSpriteCtrl } from "../controls/SkiaSprite";
|
|
27
28
|
import type { SkiaSpriteSet as SkiaSpriteSetCtrl } from "../controls/SkiaSpriteSet";
|
|
28
29
|
import type { SkiaLottie as SkiaLottieCtrl } from "../controls/SkiaLottie";
|
|
@@ -61,6 +62,8 @@ export declare const SkiaGrid: FC<LayoutProps<SkiaLayoutCtrl>>;
|
|
|
61
62
|
/** Grid drawing gradient separator lines in its spacing (C# SkiaDecoratedGrid). */
|
|
62
63
|
export declare const SkiaDecoratedGrid: FC<LayoutProps<SkiaDecoratedGridCtrl>>;
|
|
63
64
|
export declare const SkiaLabel: FC<LayoutProps<SkiaLabelCtrl>>;
|
|
65
|
+
/** Canvas FPS counter (C# SkiaLabelFps); its value changes only on frames running animators request anyway. */
|
|
66
|
+
export declare const SkiaLabelFps: FC<LeafProps<SkiaLabelFpsCtrl>>;
|
|
64
67
|
/** Markdown label (C# SkiaRichLabel): Text is markdown, rendered as spans; LinkTapped for [text](url). */
|
|
65
68
|
export declare const SkiaRichLabel: FC<LeafProps<SkiaRichLabelCtrl>>;
|
|
66
69
|
/** Child of <SkiaLabel>: a styled fragment (C# TextSpan). */
|
package/dist/react/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { SkiaAccessibilityManager } from "../core/Accessibility";
|
|
4
|
+
import { TextInputProxy } from "../core/TextInputProxy";
|
|
3
5
|
import { Canvas as CanvasView } from "../core/Canvas";
|
|
6
|
+
import { SkiaEditor as SkiaEditorCtrl } from "../controls/SkiaEditor";
|
|
4
7
|
import { SkiaScroll as SkiaScrollCtrl } from "../controls/SkiaScroll";
|
|
5
8
|
import { createDrawnRoot } from "./reconciler";
|
|
6
9
|
/** Typed JSX tags resolved by the reconciler Registry. */
|
|
@@ -13,6 +16,8 @@ export const SkiaGrid = "SkiaGrid";
|
|
|
13
16
|
/** Grid drawing gradient separator lines in its spacing (C# SkiaDecoratedGrid). */
|
|
14
17
|
export const SkiaDecoratedGrid = "SkiaDecoratedGrid";
|
|
15
18
|
export const SkiaLabel = "SkiaLabel";
|
|
19
|
+
/** Canvas FPS counter (C# SkiaLabelFps); its value changes only on frames running animators request anyway. */
|
|
20
|
+
export const SkiaLabelFps = "SkiaLabelFps";
|
|
16
21
|
/** Markdown label (C# SkiaRichLabel): Text is markdown, rendered as spans; LinkTapped for [text](url). */
|
|
17
22
|
export const SkiaRichLabel = "SkiaRichLabel";
|
|
18
23
|
/** Child of <SkiaLabel>: a styled fragment (C# TextSpan). */
|
|
@@ -114,19 +119,50 @@ const A11Y_CSS = `
|
|
|
114
119
|
.drawnui-a11y-text>span{position:absolute;white-space:pre;color:transparent;user-select:text;-webkit-user-select:text;pointer-events:auto;cursor:text;line-height:1;font-kerning:none;font-feature-settings:"kern" 0,"liga" 0,"calt" 0;text-rendering:geometricPrecision}
|
|
115
120
|
.drawnui-a11y-text>span::selection{background:rgba(110,168,254,.55);color:transparent}
|
|
116
121
|
`;
|
|
122
|
+
const NAVIGATION_KEYS = new Set(["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End", "PageUp", "PageDown"]);
|
|
117
123
|
/**
|
|
118
|
-
* Invisible ARIA elements mirroring the accessible drawn controls, positioned over the canvas.
|
|
119
|
-
* Improvement over DrawnUi.Blazor: `pointer-events:none`, so hover and every pointer gesture still reach the
|
|
120
|
-
*
|
|
124
|
+
* Invisible ARIA elements mirroring the accessible drawn controls, positioned over the canvas (the DrawnUi.Blazor
|
|
125
|
+
* overlay). Improvement over DrawnUi.Blazor: `pointer-events:none`, so hover and every pointer gesture still reach the
|
|
126
|
+
* canvas. Keyboard and screen-reader input arrive as DOM events: Enter / Space activate (a Tapped), the navigation keys
|
|
127
|
+
* go to the focused control and then to its arrow-key group; a group is one Tab stop (roving tabindex).
|
|
121
128
|
*/
|
|
122
129
|
function AccessibilityOverlay({ view }) {
|
|
123
130
|
const [nodes, setNodes] = useState(() => view.AccessibilityManager.Snapshot);
|
|
131
|
+
// keyboard focus moved by the manager (arrow keys in a group): focus that node's element once it is rendered
|
|
132
|
+
const [focusRequest, setFocusRequest] = useState();
|
|
133
|
+
// the Tab stop of a group moves with keyboard focus: re-render on focus
|
|
134
|
+
const [, setFocusTick] = useState(0);
|
|
124
135
|
useEffect(() => {
|
|
125
136
|
const mgr = view.AccessibilityManager;
|
|
126
137
|
setNodes(mgr.Snapshot);
|
|
127
138
|
const off = mgr.OnChanged(() => setNodes(mgr.Snapshot));
|
|
128
139
|
const offLive = mgr.OnLiveRegionUpdated(() => { mgr.ForceRebuildOnNextFrame(); view.Update(); });
|
|
129
|
-
|
|
140
|
+
const offFocus = mgr.OnKeyboardFocusRequested((node) => { setNodes(mgr.Snapshot); setFocusRequest({ id: node.AccessibilityId }); });
|
|
141
|
+
return () => { off(); offLive(); offFocus(); };
|
|
142
|
+
}, [view]);
|
|
143
|
+
const elementOf = (id) => overlayRef.current?.querySelector(`[data-a11y-id="${id}"]`) ?? undefined;
|
|
144
|
+
useLayoutEffect(() => {
|
|
145
|
+
if (!focusRequest)
|
|
146
|
+
return;
|
|
147
|
+
setFocusRequest(undefined);
|
|
148
|
+
elementOf(focusRequest.id)?.focus({ preventScroll: true });
|
|
149
|
+
}, [focusRequest]);
|
|
150
|
+
// Tab / Shift+Tab in a text field leave it for the next node, also after a click into it: DOM focus goes to the
|
|
151
|
+
// field's own element (without taking the caret again) and the browser's Tab moves on from there
|
|
152
|
+
const tabbingOut = useRef(0);
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
const onKey = (e) => {
|
|
155
|
+
const editor = SkiaEditorCtrl.Focused;
|
|
156
|
+
if (e.key !== "Tab" || !editor || editor.Superview !== view || !TextInputProxy.IsProxyTarget(e.target))
|
|
157
|
+
return;
|
|
158
|
+
const el = elementOf(editor.AccessibilityId);
|
|
159
|
+
if (!el)
|
|
160
|
+
return;
|
|
161
|
+
tabbingOut.current = editor.AccessibilityId;
|
|
162
|
+
el.focus({ preventScroll: true });
|
|
163
|
+
};
|
|
164
|
+
document.addEventListener("keydown", onKey, true);
|
|
165
|
+
return () => document.removeEventListener("keydown", onKey, true);
|
|
130
166
|
}, [view]);
|
|
131
167
|
// selectable text spans take pointer events: the wheel over them is re-dispatched to the canvas so drawn scrolls keep working
|
|
132
168
|
const overlayRef = useRef(null);
|
|
@@ -161,11 +197,29 @@ function AccessibilityOverlay({ view }) {
|
|
|
161
197
|
const pin = (e) => { e.currentTarget.scrollTop = 0; e.currentTarget.scrollLeft = 0; };
|
|
162
198
|
return (_jsxs("div", { className: "drawnui-a11y-overlay", onScroll: pin, ref: overlayRef, children: [_jsx("style", { children: A11Y_CSS }), nodes.map((n) => {
|
|
163
199
|
const pos = { left: n.Rect.Left, top: n.Rect.Top, width: n.Rect.Width, height: n.Rect.Height };
|
|
164
|
-
const activate = () => n.Source
|
|
165
|
-
return n.CanInteract ? (_jsx("div", { role: n.Role, "aria-label": n.Label, title: n.Hint, "aria-pressed": n.Role === "button" ? n.IsPressed : undefined, "aria-checked": n.Role === "switch" || n.Role === "checkbox" || n.Role === "radio" ? n.IsPressed : undefined, "aria-live": n.Live, tabIndex: 0, className: "drawnui-a11y-node", style: { ...pos, fontSize: 0, userSelect: "none" }, onClick: activate, onKeyDown: (e) => {
|
|
166
|
-
e.
|
|
167
|
-
|
|
168
|
-
|
|
200
|
+
const activate = () => SkiaAccessibilityManager.Activate(n.Source);
|
|
201
|
+
return n.CanInteract ? (_jsx("div", { "data-a11y-id": n.Id, role: n.Role, "aria-label": n.Label, title: n.Hint, "aria-pressed": n.Role === "button" ? n.IsPressed : undefined, "aria-checked": n.Role === "switch" || n.Role === "checkbox" || n.Role === "radio" ? n.IsPressed : undefined, "aria-live": n.Live, tabIndex: view.AccessibilityManager.IsTabStop(n.Source) ? 0 : -1, className: "drawnui-a11y-node", style: { ...pos, fontSize: 0, userSelect: "none" }, onClick: activate, onKeyDown: (e) => {
|
|
202
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
203
|
+
e.preventDefault();
|
|
204
|
+
activate();
|
|
205
|
+
}
|
|
206
|
+
else if (NAVIGATION_KEYS.has(e.key) && SkiaAccessibilityManager.Key(n.Source, e.key))
|
|
207
|
+
e.preventDefault();
|
|
208
|
+
}, onFocus: (e) => {
|
|
209
|
+
pin({ currentTarget: e.currentTarget.parentElement });
|
|
210
|
+
SkiaScrollCtrl.EnsureVisible(n.Source); // inside every enclosing scroll
|
|
211
|
+
if (tabbingOut.current === n.Id)
|
|
212
|
+
tabbingOut.current = 0; // leaving a text field: it keeps no caret
|
|
213
|
+
else
|
|
214
|
+
n.Source.OnAccessibilityFocused(true); // a text field takes the caret, as after a click
|
|
215
|
+
n.Source.NotifyAccessibilityFocused(true);
|
|
216
|
+
setFocusTick((t) => t + 1);
|
|
217
|
+
}, onBlur: (e) => {
|
|
218
|
+
if (TextInputProxy.IsProxyTarget(e.relatedTarget))
|
|
219
|
+
return; // a text field moved DOM focus to its own input
|
|
220
|
+
n.Source.OnAccessibilityFocused(false);
|
|
221
|
+
n.Source.NotifyAccessibilityFocused(false);
|
|
222
|
+
}, children: n.Label }, n.Id)) : n.TextLines ? (
|
|
169
223
|
// AccessibilityTextSelectable: one positioned span per drawn line, in the drawn font, so the browser selects and copies it like HTML
|
|
170
224
|
_jsx("div", { role: n.Role, title: n.Hint, "aria-live": n.Live, className: "drawnui-a11y-node drawnui-a11y-text", style: pos, children: n.TextLines.map((l, i) => (_jsxs("span", { "data-w": l.Width, style: { left: l.Left, top: l.Top, height: l.Height, fontFamily: l.FontFamily, fontWeight: l.FontWeight, fontSize: l.FontSize, lineHeight: `${l.Height}px` }, children: [l.Text, i < n.TextLines.length - 1 ? "\n" : ""] }, i))) }, n.Id)) : (
|
|
171
225
|
// static text is exposed as real (transparent) text content; aria-label only for roles that need a name
|
package/dist/react/reconciler.js
CHANGED
|
@@ -4,6 +4,7 @@ import { ConcurrentRoot, DefaultEventPriority } from "react-reconciler/constants
|
|
|
4
4
|
import { SkiaLabel } from "../controls/SkiaLabel";
|
|
5
5
|
import { TextSpan } from "../controls/TextSpan";
|
|
6
6
|
import { SkiaRichLabel } from "../controls/SkiaRichLabel";
|
|
7
|
+
import { SkiaLabelFps } from "../controls/SkiaLabelFps";
|
|
7
8
|
import { SkiaSwitch } from "../controls/SkiaSwitch";
|
|
8
9
|
import { SkiaCheckbox } from "../controls/SkiaCheckbox";
|
|
9
10
|
import { SkiaRadioButton } from "../controls/SkiaRadioButton";
|
|
@@ -31,7 +32,7 @@ import { SkiaScroll } from "../controls/SkiaScroll";
|
|
|
31
32
|
import { SkiaFrame, SkiaShape } from "../controls/SkiaShape";
|
|
32
33
|
/** JSX tag name -> engine class. Add a control here to expose it to React. */
|
|
33
34
|
export const Registry = {
|
|
34
|
-
SkiaLayout, SkiaStack, SkiaRow, SkiaLayer, SkiaWrap, SkiaGrid, SkiaDecoratedGrid, SkiaBackdrop, SkiaEditor, SkiaSprite, SkiaSpriteSet, SkiaLabel, SkiaRichLabel, TextSpan, SkiaHotspot, SkiaButton, SkiaImage, SkiaImageTiles, SkiaSvg, SkiaLottie, SkiaGif, SkiaScroll, SkiaScrollBar, RefreshIndicator, SkiaShape, SkiaFrame,
|
|
35
|
+
SkiaLayout, SkiaStack, SkiaRow, SkiaLayer, SkiaWrap, SkiaGrid, SkiaDecoratedGrid, SkiaBackdrop, SkiaEditor, SkiaSprite, SkiaSpriteSet, SkiaLabel, SkiaLabelFps, SkiaRichLabel, TextSpan, SkiaHotspot, SkiaButton, SkiaImage, SkiaImageTiles, SkiaSvg, SkiaLottie, SkiaGif, SkiaScroll, SkiaScrollBar, RefreshIndicator, SkiaShape, SkiaFrame,
|
|
35
36
|
SkiaSwitch, SkiaCheckbox, SkiaRadioButton, SkiaProgress, SkiaSlider, SkiaCarousel, SkiaShaderCarousel, SkiaDrawer,
|
|
36
37
|
};
|
|
37
38
|
const SKIP = new Set(["children", "key", "ref"]);
|
package/dist/vite/index.d.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
|
+
/**
|
|
3
|
+
* Versioned addresses for the files of the public folder (fonts, images, sprites, shaders, Lottie, favicon). Their
|
|
4
|
+
* names never change, and a browser or CDN may keep them for hours, so after a publish a returning visitor could
|
|
5
|
+
* get new code with an old file. On build every public file gets a short content hash: index.html receives the map
|
|
6
|
+
* as `globalThis.DrawnUiAssetStamps` (read by `Super.ResolveAssetUrl`, which every engine loader fetches through)
|
|
7
|
+
* and its own `href` / `src` links to public files get `?v=<hash>`. A changed file gets a new address, an unchanged
|
|
8
|
+
* one keeps its address and stays cached. Bundled files (hashed names) need nothing. The dev server is left alone.
|
|
9
|
+
*/
|
|
10
|
+
export declare function drawnUiAssetStamps(): Plugin;
|
|
2
11
|
export interface DrawnUiStaticOptions {
|
|
3
12
|
/**
|
|
4
13
|
* Pages to generate, as URL hashes / paths appended to the site root ("" = the root page, "#/shapes"...).
|
package/dist/vite/index.js
CHANGED
|
@@ -1,9 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Vite helpers for DrawnUi.React apps: `import { drawnUiStatic } from "drawnui-react/vite"`.
|
|
2
|
+
* Vite helpers for DrawnUi.React apps: `import { drawnUiStatic, drawnUiAssetStamps } from "drawnui-react/vite"`.
|
|
3
3
|
* Build-time only; the runtime bundle never imports this module and the frame loop knows nothing about it.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { join, resolve } from "node:path";
|
|
8
|
+
/**
|
|
9
|
+
* Versioned addresses for the files of the public folder (fonts, images, sprites, shaders, Lottie, favicon). Their
|
|
10
|
+
* names never change, and a browser or CDN may keep them for hours, so after a publish a returning visitor could
|
|
11
|
+
* get new code with an old file. On build every public file gets a short content hash: index.html receives the map
|
|
12
|
+
* as `globalThis.DrawnUiAssetStamps` (read by `Super.ResolveAssetUrl`, which every engine loader fetches through)
|
|
13
|
+
* and its own `href` / `src` links to public files get `?v=<hash>`. A changed file gets a new address, an unchanged
|
|
14
|
+
* one keeps its address and stays cached. Bundled files (hashed names) need nothing. The dev server is left alone.
|
|
15
|
+
*/
|
|
16
|
+
export function drawnUiAssetStamps() {
|
|
17
|
+
let stamps = {};
|
|
18
|
+
return {
|
|
19
|
+
name: "drawnui-asset-stamps",
|
|
20
|
+
apply: "build",
|
|
21
|
+
configResolved(config) {
|
|
22
|
+
const dir = config.publicDir;
|
|
23
|
+
if (!dir)
|
|
24
|
+
return;
|
|
25
|
+
for (const rel of readdirSync(dir, { recursive: true })) {
|
|
26
|
+
const file = join(dir, rel);
|
|
27
|
+
if (statSync(file).isFile())
|
|
28
|
+
stamps[rel.replace(/\\/g, "/")] = createHash("sha256").update(readFileSync(file)).digest("hex").slice(0, 10);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
transformIndexHtml(html) {
|
|
32
|
+
const stamped = html.replace(/\b(href|src)="([^"?#:]+)"/g, (m, attr, url) => {
|
|
33
|
+
const v = stamps[url.replace(/^\.?\//, "")];
|
|
34
|
+
return v ? `${attr}="${url}?v=${v}"` : m;
|
|
35
|
+
});
|
|
36
|
+
return { html: stamped, tags: [{ tag: "script", children: `globalThis.DrawnUiAssetStamps=${JSON.stringify(stamps)}`, injectTo: "head-prepend" }] };
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
7
40
|
/**
|
|
8
41
|
* Generates visible, semantic HTML for crawlers and AI agents from the app's own accessibility tree, at build time.
|
|
9
42
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drawnui-react",
|
|
3
|
-
"version": "0.1.0-preview.
|
|
3
|
+
"version": "0.1.0-preview.14",
|
|
4
4
|
"description": "DrawnUi for React: the DrawnUi (.NET) drawn-UI engine in TypeScript on CanvasKit (Skia WASM). Same controls, same PascalCase API as DrawnUi.Net.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nick Kovalsky (AppoMobi)",
|
|
@@ -53,6 +53,9 @@
|
|
|
53
53
|
"typecheck": "tsc --noEmit",
|
|
54
54
|
"build": "tsc --noEmit && node dev/build-samples.mjs",
|
|
55
55
|
"sync:skills": "node dev/sync-skills.mjs",
|
|
56
|
+
"check:recycling": "node dev/check.mjs dev/check-recycling.ts",
|
|
57
|
+
"check:keyboard": "node dev/check.mjs dev/check-keyboard.ts",
|
|
58
|
+
"check:wheel": "node dev/check.mjs dev/check-wheel.ts",
|
|
56
59
|
"site:extras": "node dev/site-extras.mjs dist/demo",
|
|
57
60
|
"build:lib": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
|
|
58
61
|
"prepublishOnly": "npm run build:lib",
|