onejs-react 0.1.20 → 0.1.22
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/package.json +1 -1
- package/src/__tests__/host-config.test.ts +70 -0
- package/src/__tests__/mocks.ts +6 -0
- package/src/host-config.ts +78 -15
- package/src/index.ts +1 -0
- package/src/style-parser.ts +14 -1
- package/src/types.ts +77 -2
package/package.json
CHANGED
|
@@ -108,6 +108,36 @@ describe('host-config', () => {
|
|
|
108
108
|
createInstance('unknown-type', {});
|
|
109
109
|
}).toThrow('Unknown element type: unknown-type');
|
|
110
110
|
});
|
|
111
|
+
|
|
112
|
+
it('forwards the focusable prop to the element', () => {
|
|
113
|
+
const instance = createInstance('ojs-view', { focusable: true } as TestProps);
|
|
114
|
+
expect((instance.element as unknown as { focusable: boolean }).focusable).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('forwards focusable=false explicitly', () => {
|
|
118
|
+
const instance = createInstance('ojs-button', { focusable: false } as TestProps);
|
|
119
|
+
expect((instance.element as unknown as { focusable: boolean }).focusable).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('does not set focusable when prop is omitted', () => {
|
|
123
|
+
const instance = createInstance('ojs-view', {});
|
|
124
|
+
expect((instance.element as unknown as { focusable?: boolean }).focusable).toBeUndefined();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('applies disabled=true by calling SetEnabled(false)', () => {
|
|
128
|
+
const instance = createInstance('ojs-button', { disabled: true } as TestProps);
|
|
129
|
+
expect(getMockElement(instance).enabledSelf).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('applies disabled=false by calling SetEnabled(true)', () => {
|
|
133
|
+
const instance = createInstance('ojs-button', { disabled: false } as TestProps);
|
|
134
|
+
expect(getMockElement(instance).enabledSelf).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('leaves element enabled by default when disabled prop is omitted', () => {
|
|
138
|
+
const instance = createInstance('ojs-view', {});
|
|
139
|
+
expect(getMockElement(instance).enabledSelf).toBe(true);
|
|
140
|
+
});
|
|
111
141
|
});
|
|
112
142
|
|
|
113
143
|
describe('style application', () => {
|
|
@@ -302,6 +332,46 @@ describe('host-config', () => {
|
|
|
302
332
|
expect(instance.appliedStyleKeys.has('width')).toBe(false);
|
|
303
333
|
expect(instance.appliedStyleKeys.has('height')).toBe(true);
|
|
304
334
|
});
|
|
335
|
+
|
|
336
|
+
it('updates focusable when the prop changes', () => {
|
|
337
|
+
const instance = createInstance('ojs-view', { focusable: false } as TestProps);
|
|
338
|
+
expect((instance.element as unknown as { focusable: boolean }).focusable).toBe(false);
|
|
339
|
+
|
|
340
|
+
commitUpdate(instance, 'ojs-view', { focusable: false } as TestProps, { focusable: true } as TestProps);
|
|
341
|
+
expect((instance.element as unknown as { focusable: boolean }).focusable).toBe(true);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('does not clobber focusable when the prop becomes undefined', () => {
|
|
345
|
+
// Removing the prop should leave the element's current state alone,
|
|
346
|
+
// so element-specific defaults (Button etc.) remain intact.
|
|
347
|
+
const instance = createInstance('ojs-button', { focusable: true } as TestProps);
|
|
348
|
+
expect((instance.element as unknown as { focusable: boolean }).focusable).toBe(true);
|
|
349
|
+
|
|
350
|
+
commitUpdate(instance, 'ojs-button', { focusable: true } as TestProps, {} as TestProps);
|
|
351
|
+
expect((instance.element as unknown as { focusable: boolean }).focusable).toBe(true);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('updates disabled when the prop changes', () => {
|
|
355
|
+
const instance = createInstance('ojs-button', { disabled: false } as TestProps);
|
|
356
|
+
expect(getMockElement(instance).enabledSelf).toBe(true);
|
|
357
|
+
|
|
358
|
+
commitUpdate(instance, 'ojs-button', { disabled: false } as TestProps, { disabled: true } as TestProps);
|
|
359
|
+
expect(getMockElement(instance).enabledSelf).toBe(false);
|
|
360
|
+
|
|
361
|
+
commitUpdate(instance, 'ojs-button', { disabled: true } as TestProps, { disabled: false } as TestProps);
|
|
362
|
+
expect(getMockElement(instance).enabledSelf).toBe(true);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('restores disabled to enabled when prop is removed', () => {
|
|
366
|
+
// Unlike focusable, every VisualElement starts enabled by default,
|
|
367
|
+
// so removing `disabled={true}` must call SetEnabled(true) to
|
|
368
|
+
// restore the element rather than leaving it stuck disabled.
|
|
369
|
+
const instance = createInstance('ojs-button', { disabled: true } as TestProps);
|
|
370
|
+
expect(getMockElement(instance).enabledSelf).toBe(false);
|
|
371
|
+
|
|
372
|
+
commitUpdate(instance, 'ojs-button', { disabled: true } as TestProps, {} as TestProps);
|
|
373
|
+
expect(getMockElement(instance).enabledSelf).toBe(true);
|
|
374
|
+
});
|
|
305
375
|
});
|
|
306
376
|
|
|
307
377
|
describe('className management', () => {
|
package/src/__tests__/mocks.ts
CHANGED
|
@@ -71,6 +71,7 @@ export class MockVisualElement {
|
|
|
71
71
|
text = '';
|
|
72
72
|
value: unknown = undefined;
|
|
73
73
|
label = '';
|
|
74
|
+
enabledSelf = true;
|
|
74
75
|
|
|
75
76
|
constructor(csType = 'UnityEngine.UIElements.VisualElement') {
|
|
76
77
|
this.__csHandle = Math.floor(Math.random() * 1000000);
|
|
@@ -117,6 +118,11 @@ export class MockVisualElement {
|
|
|
117
118
|
this._children = [];
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
// Enabled state
|
|
122
|
+
SetEnabled(value: boolean): void {
|
|
123
|
+
this.enabledSelf = value;
|
|
124
|
+
}
|
|
125
|
+
|
|
120
126
|
// Class list methods
|
|
121
127
|
AddToClassList(className: string): void {
|
|
122
128
|
this._classList.add(className);
|
package/src/host-config.ts
CHANGED
|
@@ -71,6 +71,10 @@ declare const CS: {
|
|
|
71
71
|
};
|
|
72
72
|
FrostedGlassElement: new () => CSObject;
|
|
73
73
|
};
|
|
74
|
+
StyleBridge: {
|
|
75
|
+
ApplyStyles: (element: CSObject, styles: Record<string, unknown>) => void;
|
|
76
|
+
AddClassesBatch: (element: CSObject, classes: string[]) => void;
|
|
77
|
+
};
|
|
74
78
|
};
|
|
75
79
|
};
|
|
76
80
|
|
|
@@ -385,47 +389,73 @@ function getRenderTextureHandle(value: RenderTextureRef): number {
|
|
|
385
389
|
return value.__rtHandle ?? value.__handle ?? -1;
|
|
386
390
|
}
|
|
387
391
|
|
|
388
|
-
// Apply style properties to element, returns the set of applied keys
|
|
392
|
+
// Apply style properties to element, returns the set of applied keys.
|
|
393
|
+
//
|
|
394
|
+
// Batched path: parsed style values are collected into a single dict and sent
|
|
395
|
+
// to CS.OneJS.StyleBridge.ApplyStyles in one __cs.invoke crossing instead of
|
|
396
|
+
// one per property. On WebGL each crossing is ~3ms (JSON marshal + reflection),
|
|
397
|
+
// so the difference is ~N invokes vs 1 invoke per element. backgroundImage
|
|
398
|
+
// stays on its individual GPU-bridge path since it's not a plain IStyle setter.
|
|
389
399
|
function applyStyle(element: CSObject, style: ViewStyle | undefined): Set<string> {
|
|
390
400
|
const appliedKeys = new Set<string>();
|
|
391
401
|
if (!style) return appliedKeys;
|
|
392
402
|
|
|
393
|
-
const
|
|
403
|
+
const batched: Record<string, unknown> = {}
|
|
404
|
+
|
|
394
405
|
for (const [key, value] of Object.entries(style)) {
|
|
395
406
|
if (value === undefined) continue;
|
|
396
407
|
|
|
397
|
-
// Handle shorthand properties
|
|
398
408
|
const expanded = STYLE_SHORTHANDS[key];
|
|
399
409
|
if (expanded) {
|
|
400
|
-
|
|
401
|
-
const parsed = parseStyleValue(expanded[0], value);
|
|
410
|
+
const parsed = resolveForBatch(parseStyleValue(expanded[0], value));
|
|
402
411
|
for (const prop of expanded) {
|
|
403
|
-
|
|
412
|
+
batched[prop] = parsed;
|
|
404
413
|
appliedKeys.add(prop);
|
|
405
414
|
}
|
|
406
415
|
} else if (key === "backgroundImage") {
|
|
407
416
|
if (value == null) {
|
|
408
417
|
CS.OneJS.GPU.GPUBridge.ClearElementBackgroundImage(element);
|
|
409
418
|
} else if (isRenderTextureHandle(value)) {
|
|
410
|
-
// GPU compute RenderTexture handles (via rt.getUnityObject())
|
|
411
419
|
const handle = getRenderTextureHandle(value);
|
|
412
420
|
if (handle >= 0) {
|
|
413
421
|
CS.OneJS.GPU.GPUBridge.SetElementBackgroundImage(element, handle);
|
|
414
422
|
}
|
|
415
423
|
} else if (typeof value === "object" && "__csHandle" in value) {
|
|
416
|
-
// C# objects: Texture2D, Sprite, VectorImage, RenderTexture
|
|
417
424
|
CS.OneJS.GPU.GPUBridge.SetElementBackgroundFromObject(element, value);
|
|
418
425
|
}
|
|
419
426
|
appliedKeys.add(key);
|
|
420
427
|
} else {
|
|
421
|
-
|
|
422
|
-
s[key] = parseStyleValue(key, value);
|
|
428
|
+
batched[key] = resolveForBatch(parseStyleValue(key, value));
|
|
423
429
|
appliedKeys.add(key);
|
|
424
430
|
}
|
|
425
431
|
}
|
|
432
|
+
|
|
433
|
+
if (Object.keys(batched).length > 0) {
|
|
434
|
+
CS.OneJS.StyleBridge.ApplyStyles(element, batched);
|
|
435
|
+
}
|
|
436
|
+
|
|
426
437
|
return appliedKeys;
|
|
427
438
|
}
|
|
428
439
|
|
|
440
|
+
// Force-resolve CS path proxies (e.g. CS.UnityEngine.UIElements.Justify.Center)
|
|
441
|
+
// to their underlying int value. parseEnumValue and parseLength's StyleKeyword
|
|
442
|
+
// cases return path proxies whose .valueOf() reads the int via GetField. The
|
|
443
|
+
// non-batched __cs.invoke path resolved these implicitly via __resolveValue;
|
|
444
|
+
// the batched path JSON.stringifies the whole dict, so path proxies serialize
|
|
445
|
+
// via toJSON to {__csTypeRef:...} which C# can't interpret as an enum value.
|
|
446
|
+
// CS object proxies (with __csHandle) keep their toJSON shape — only path
|
|
447
|
+
// proxies need coercion.
|
|
448
|
+
function resolveForBatch(value: unknown): unknown {
|
|
449
|
+
// Path proxies (e.g. CS.UnityEngine.UIElements.Justify.Center) have a
|
|
450
|
+
// function as their underlying Proxy target so they can also be invoked
|
|
451
|
+
// as constructors — so typeof is "function", not "object". Just check the
|
|
452
|
+
// __csPathProxy sentinel and rely on truthy/property semantics.
|
|
453
|
+
if (value && (value as any).__csPathProxy) {
|
|
454
|
+
return Number(value)
|
|
455
|
+
}
|
|
456
|
+
return value
|
|
457
|
+
}
|
|
458
|
+
|
|
429
459
|
// Clear style properties that are no longer in the new style
|
|
430
460
|
function clearRemovedStyles(element: CSObject, oldKeys: Set<string>, newKeys: Set<string>) {
|
|
431
461
|
const s = element.style;
|
|
@@ -458,13 +488,18 @@ function parseClassNames(className: string | undefined): Set<string> {
|
|
|
458
488
|
return result;
|
|
459
489
|
}
|
|
460
490
|
|
|
461
|
-
// Apply className(s) to element (with escaping for Tailwind/USS compatibility)
|
|
491
|
+
// Apply className(s) to element (with escaping for Tailwind/USS compatibility).
|
|
492
|
+
// Routes through StyleBridge.AddClassesBatch so a multi-class string is one
|
|
493
|
+
// __cs.invoke crossing instead of one per class.
|
|
462
494
|
function applyClassName(element: CSObject, className: string | undefined) {
|
|
463
495
|
if (!className) return;
|
|
464
496
|
|
|
465
|
-
const classes =
|
|
466
|
-
for (const cls of
|
|
467
|
-
|
|
497
|
+
const classes: string[] = [];
|
|
498
|
+
for (const cls of className.split(/\s+/)) {
|
|
499
|
+
if (cls) classes.push(escapeClassName(cls));
|
|
500
|
+
}
|
|
501
|
+
if (classes.length > 0) {
|
|
502
|
+
CS.OneJS.StyleBridge.AddClassesBatch(element, classes);
|
|
468
503
|
}
|
|
469
504
|
}
|
|
470
505
|
|
|
@@ -786,7 +821,7 @@ function applyListViewProps(element: CSListView, props: Record<string, unknown>,
|
|
|
786
821
|
|
|
787
822
|
// Props handled by the reconciler infrastructure - not forwarded to C# elements
|
|
788
823
|
const RESERVED_PROPS = new Set([
|
|
789
|
-
'children', 'key', 'ref', 'style', 'className', 'pickingMode',
|
|
824
|
+
'children', 'key', 'ref', 'style', 'className', 'pickingMode', 'focusable', 'disabled',
|
|
790
825
|
'onGenerateVisualContent',
|
|
791
826
|
...Object.keys(EVENT_PROPS),
|
|
792
827
|
]);
|
|
@@ -862,6 +897,16 @@ function createInstance(type: string, props: BaseProps): Instance {
|
|
|
862
897
|
element.pickingMode = CS.UnityEngine.UIElements.PickingMode[props.pickingMode];
|
|
863
898
|
}
|
|
864
899
|
|
|
900
|
+
// Apply focusable
|
|
901
|
+
if (props.focusable !== undefined) {
|
|
902
|
+
element.focusable = props.focusable;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// Apply disabled (inverted — SetEnabled(true) means "not disabled")
|
|
906
|
+
if (props.disabled !== undefined) {
|
|
907
|
+
element.SetEnabled(!props.disabled);
|
|
908
|
+
}
|
|
909
|
+
|
|
865
910
|
return instance;
|
|
866
911
|
}
|
|
867
912
|
|
|
@@ -901,6 +946,24 @@ function updateInstance(instance: Instance, oldProps: BaseProps, newProps: BaseP
|
|
|
901
946
|
}
|
|
902
947
|
}
|
|
903
948
|
|
|
949
|
+
// Update focusable - only set if explicitly provided, do not override
|
|
950
|
+
// element-specific defaults (Button is focusable by default, View is not)
|
|
951
|
+
// when the prop is removed.
|
|
952
|
+
if (oldProps.focusable !== newProps.focusable && newProps.focusable !== undefined) {
|
|
953
|
+
element.focusable = newProps.focusable;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// Update disabled - unlike focusable, every VisualElement starts enabled
|
|
957
|
+
// by default, so removing the prop after a previous `disabled={true}` is
|
|
958
|
+
// expected to restore the enabled state.
|
|
959
|
+
if (oldProps.disabled !== newProps.disabled) {
|
|
960
|
+
if (newProps.disabled !== undefined) {
|
|
961
|
+
element.SetEnabled(!newProps.disabled);
|
|
962
|
+
} else {
|
|
963
|
+
element.SetEnabled(true);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
904
967
|
instance.props = newProps;
|
|
905
968
|
}
|
|
906
969
|
|
package/src/index.ts
CHANGED
package/src/style-parser.ts
CHANGED
|
@@ -14,7 +14,8 @@ declare const CS: {
|
|
|
14
14
|
UIElements: {
|
|
15
15
|
Length: new (value: number, unit?: number) => CSLength;
|
|
16
16
|
LengthUnit: { Pixel: number; Percent: number };
|
|
17
|
-
StyleKeyword: { Auto: number; None: number; Initial: number };
|
|
17
|
+
StyleKeyword: { Auto: number; None: number; Initial: number; Null: number };
|
|
18
|
+
StyleMaterialDefinition: new (v: unknown) => unknown;
|
|
18
19
|
Angle: { Degrees: (v: number) => any; Radians: (v: number) => any; Turns: (v: number) => any; Gradians: (v: number) => any };
|
|
19
20
|
Translate: new (x: any, y: any) => any;
|
|
20
21
|
Rotate: new (angle: any) => any;
|
|
@@ -499,6 +500,18 @@ function parseTransformOriginStyle(value: unknown): unknown {
|
|
|
499
500
|
* @returns Parsed value suitable for Unity UI Toolkit
|
|
500
501
|
*/
|
|
501
502
|
export function parseStyleValue(key: string, value: unknown): unknown {
|
|
503
|
+
// `unityMaterial` is special: it can legitimately be `null` (meaning
|
|
504
|
+
// "reset to the panel default") so we can't short-circuit like the
|
|
505
|
+
// length/color paths below. Wrap in StyleMaterialDefinition either way.
|
|
506
|
+
if (key === "unityMaterial") {
|
|
507
|
+
if (value === undefined) return undefined
|
|
508
|
+
const SMD = CS.UnityEngine.UIElements.StyleMaterialDefinition
|
|
509
|
+
if (value === null) {
|
|
510
|
+
return new SMD(CS.UnityEngine.UIElements.StyleKeyword.Null)
|
|
511
|
+
}
|
|
512
|
+
return new SMD(value as any)
|
|
513
|
+
}
|
|
514
|
+
|
|
502
515
|
if (value === undefined || value === null) return value
|
|
503
516
|
|
|
504
517
|
// Length properties
|
package/src/types.ts
CHANGED
|
@@ -163,6 +163,17 @@ export interface ViewStyle {
|
|
|
163
163
|
/** Tint color applied to the background image */
|
|
164
164
|
unityBackgroundImageTintColor?: StyleColor;
|
|
165
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Override the shader / material used to render the element. Accepts a
|
|
168
|
+
* Unity `Material` (typical) or a `MaterialDefinition`; `null` clears the
|
|
169
|
+
* inline override so the element falls back to the panel default.
|
|
170
|
+
*
|
|
171
|
+
* Typed loosely as `object | null` because the React layer has no reason
|
|
172
|
+
* to pin the caller to a specific C# shape — the style parser constructs
|
|
173
|
+
* a `StyleMaterialDefinition` at assignment time.
|
|
174
|
+
*/
|
|
175
|
+
unityMaterial?: object | null;
|
|
176
|
+
|
|
166
177
|
// Slicing
|
|
167
178
|
/** 9-slice top inset */
|
|
168
179
|
unitySliceTop?: number;
|
|
@@ -262,9 +273,32 @@ export interface ChangeEventData<T = unknown> {
|
|
|
262
273
|
value: T;
|
|
263
274
|
}
|
|
264
275
|
|
|
276
|
+
/**
|
|
277
|
+
* Shape of the synthetic event object passed to focus / blur handlers at
|
|
278
|
+
* runtime. Matches `QuickJSBootstrap.__dispatchEvent` (see
|
|
279
|
+
* `OneJS/Resources/OneJS/QuickJSBootstrap.js.txt:980-1031`) — every synthetic
|
|
280
|
+
* event carries `target` / `currentTarget` as integer C# handles plus the
|
|
281
|
+
* propagation-control surface, and the C# bridge's `OnFocusIn` / `OnFocusOut`
|
|
282
|
+
* dispatch an empty data object, so focus events add no fields beyond the
|
|
283
|
+
* base.
|
|
284
|
+
*
|
|
285
|
+
* `relatedTarget` is intentionally absent: the C# bridge serializes `{}` for
|
|
286
|
+
* focus events and never includes it. Any code that previously depended on
|
|
287
|
+
* `e.relatedTarget` at runtime has been silently receiving `undefined`.
|
|
288
|
+
*
|
|
289
|
+
* `target` / `currentTarget` are raw C# handles, not `VisualElement` proxies.
|
|
290
|
+
* Resolve them via `CS.QuickJSNative.GetObjectByHandle(handle)` when an
|
|
291
|
+
* element reference is needed, or compare them directly against
|
|
292
|
+
* `ref.current?.__csHandle` to check element identity.
|
|
293
|
+
*/
|
|
265
294
|
export interface FocusEventData {
|
|
266
295
|
type: string;
|
|
267
|
-
|
|
296
|
+
target: number;
|
|
297
|
+
currentTarget: number;
|
|
298
|
+
preventDefault(): void;
|
|
299
|
+
stopPropagation(): void;
|
|
300
|
+
defaultPrevented: boolean;
|
|
301
|
+
propagationStopped: boolean;
|
|
268
302
|
}
|
|
269
303
|
|
|
270
304
|
export interface DragEventData {
|
|
@@ -281,9 +315,26 @@ export interface GeometryEventData {
|
|
|
281
315
|
newRect: { x: number; y: number; width: number; height: number };
|
|
282
316
|
}
|
|
283
317
|
|
|
318
|
+
/**
|
|
319
|
+
* Direction values dispatched by `NavigationMoveEvent`. Mirrors
|
|
320
|
+
* `UnityEngine.UIElements.NavigationMoveEvent.Direction`, serialized to
|
|
321
|
+
* lowercase strings by the C# bridge (see `QuickJSUIBridge.OnNavigationMove`
|
|
322
|
+
* and `QuickJSBootstrap.__NAV_DIRECTION_NAMES`). `NavigationSubmitEvent` /
|
|
323
|
+
* `NavigationCancelEvent` do not carry a direction — the field is only
|
|
324
|
+
* populated for navigation-move.
|
|
325
|
+
*/
|
|
326
|
+
export type NavigationDirection =
|
|
327
|
+
| 'none'
|
|
328
|
+
| 'left'
|
|
329
|
+
| 'up'
|
|
330
|
+
| 'right'
|
|
331
|
+
| 'down'
|
|
332
|
+
| 'next'
|
|
333
|
+
| 'previous';
|
|
334
|
+
|
|
284
335
|
export interface NavigationEventData {
|
|
285
336
|
type: string;
|
|
286
|
-
direction?:
|
|
337
|
+
direction?: NavigationDirection;
|
|
287
338
|
modifiers?: number;
|
|
288
339
|
}
|
|
289
340
|
|
|
@@ -430,6 +481,29 @@ export interface BaseProps {
|
|
|
430
481
|
*/
|
|
431
482
|
pickingMode?: 'Position' | 'Ignore';
|
|
432
483
|
|
|
484
|
+
// Focus
|
|
485
|
+
/**
|
|
486
|
+
* Whether this element can receive focus.
|
|
487
|
+
* Maps directly to `VisualElement.focusable` in Unity UI Toolkit.
|
|
488
|
+
*
|
|
489
|
+
* Default is element-specific (e.g. Button is focusable by default, View is not).
|
|
490
|
+
* Setting this to `true` makes the element a focus target for keyboard/gamepad
|
|
491
|
+
* navigation and enables `NavigationMoveEvent`/`NavigationSubmitEvent` routing.
|
|
492
|
+
*/
|
|
493
|
+
focusable?: boolean;
|
|
494
|
+
|
|
495
|
+
// Enabled state
|
|
496
|
+
/**
|
|
497
|
+
* Whether this element is disabled. When `true`, the reconciler calls
|
|
498
|
+
* `VisualElement.SetEnabled(false)` on the underlying C# element, which
|
|
499
|
+
* applies the `:disabled` USS pseudo-class, blocks pointer events, and
|
|
500
|
+
* prevents the element and its descendants from receiving focus.
|
|
501
|
+
*
|
|
502
|
+
* Removing the prop (or setting it to `false`) restores the element to
|
|
503
|
+
* `SetEnabled(true)`. Every VisualElement starts enabled by default.
|
|
504
|
+
*/
|
|
505
|
+
disabled?: boolean;
|
|
506
|
+
|
|
433
507
|
// Vector drawing
|
|
434
508
|
/**
|
|
435
509
|
* Callback for custom vector drawing via Unity's generateVisualContent.
|
|
@@ -572,6 +646,7 @@ export interface VisualElement extends RenderContainer {
|
|
|
572
646
|
visible: boolean;
|
|
573
647
|
enabledSelf: boolean;
|
|
574
648
|
enabledInHierarchy: boolean;
|
|
649
|
+
SetEnabled: (value: boolean) => void;
|
|
575
650
|
|
|
576
651
|
// Text content (for TextElement-derived types)
|
|
577
652
|
text?: string;
|