@desk-framework/frame-test
Advanced tools
Comparing version 4.0.0-dev.18 to 4.0.0-dev.19
@@ -57,4 +57,4 @@ import { ConfigOptions, app, } from "@desk-framework/frame-core"; | ||
// create test renderer | ||
app.theme = new TestTheme(); | ||
app.renderer = new TestRenderer(options); | ||
app.theme = new TestTheme(); | ||
// create no-op viewport context | ||
@@ -61,0 +61,0 @@ app.viewport = new TestViewportContext(); |
@@ -14,20 +14,20 @@ import { UIButton, UICell, UIContainer, UIImage, UILabel, UISeparator, UISpacer, UITextField, UIToggle, } from "@desk-framework/frame-core"; | ||
return (target instanceof UICell | ||
? new UICellRenderer() | ||
? new UICellRenderer(target) | ||
: target instanceof UIContainer | ||
? new UIContainerRenderer() | ||
? new UIContainerRenderer(target) | ||
: target instanceof UILabel | ||
? new UILabelRenderer() | ||
? new UILabelRenderer(target) | ||
: target instanceof UIButton | ||
? new UIButtonRenderer() | ||
? new UIButtonRenderer(target) | ||
: target instanceof UIImage | ||
? new UIImageRenderer() | ||
? new UIImageRenderer(target) | ||
: target instanceof UISeparator | ||
? new UISeparatorRenderer() | ||
? new UISeparatorRenderer(target) | ||
: target instanceof UISpacer | ||
? new UISpacerRenderer() | ||
? new UISpacerRenderer(target) | ||
: target instanceof UITextField | ||
? new UITextFieldRenderer() | ||
? new UITextFieldRenderer(target) | ||
: target instanceof UIToggle | ||
? new UIToggleRenderer() | ||
? new UIToggleRenderer(target) | ||
: undefined); | ||
} |
@@ -1,2 +0,2 @@ | ||
import { ManagedEvent, Observer, UIStyle, app, } from "@desk-framework/frame-core"; | ||
import { ManagedEvent, ManagedObject, UIStyle, app, } from "@desk-framework/frame-core"; | ||
/** UI component event names that are used for basic platform events */ | ||
@@ -70,36 +70,38 @@ const _eventNames = { | ||
} | ||
/** @internal Abstract observer class for all `UIComponent` instances, to create output and call render callback; implemented for all types of UI components */ | ||
export class TestBaseObserver extends Observer { | ||
observe(observed) { | ||
/** @internal Abstract observer class for all `UIComponent` instances, to create output and call render callback; implemented for all types of UI components, created upon rendering, and attached to enable property bindings */ | ||
export class TestBaseObserver { | ||
observed; | ||
constructor(observed) { | ||
this.observed = observed; | ||
this._thisRenderedEvent = new ManagedEvent("Rendered", observed, undefined, undefined, undefined, true); | ||
return super.observe(observed).observePropertyAsync("hidden", "position"); | ||
this.observeProperties("hidden", "position"); | ||
observed.listen((e) => { | ||
let handler = this["on" + e.name]; | ||
if (typeof handler === "function") | ||
handler.call(this, e); | ||
}); | ||
} | ||
_thisRenderedEvent; | ||
/** Set up one or more property listeners, to call {@link propertyChange} on this observer */ | ||
observeProperties(...properties) { | ||
ManagedObject.observe(this.observed, properties, (_, p, v) => this.propertyChange(p, v)); | ||
} | ||
/** Handler for base property changes; must be overridden to handle other UI component properties */ | ||
async handlePropertyChange(property, value, event) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "hidden": | ||
this._hidden = this.observed.hidden; | ||
if (!this._hidden) | ||
this.updateStyle(this.element); | ||
if (this.updateCallback) { | ||
this.updateCallback = this.updateCallback.call(undefined, this._hidden ? undefined : this.output); | ||
} | ||
return; | ||
case "position": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
propertyChange(property, value) { | ||
if (!this.element) | ||
return; | ||
switch (property) { | ||
case "hidden": | ||
this.scheduleHide(value); | ||
return; | ||
case "position": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
} | ||
/** Rendered element, if any; set by `onRender` handler based on return value of `getOutput()` method */ | ||
element; | ||
/** Rendered output, if any; set by `onRender` handler based on return value of `getOutput()` method */ | ||
output; | ||
/** Rendered element, if any; set by `onRender` handler based on return value of `getOutput()` method */ | ||
element; | ||
/** Updates the specified output element with all properties of the UI component; called automatically before rendering (after `getOutput`), but can also be called when state properties change */ | ||
update(element) { | ||
if (!this.observed) | ||
return; | ||
this._hidden = this.observed.hidden; | ||
@@ -123,2 +125,4 @@ this._asyncContentUp = undefined; | ||
app.renderer.schedule(() => { | ||
if (this.observed.isUnlinked()) | ||
return; | ||
this._asyncUp = false; | ||
@@ -138,2 +142,16 @@ if (this._asyncContentUp) | ||
_asyncStyleUp; | ||
/** Schedules an asynchronous update to show or hide the output */ | ||
scheduleHide(hidden) { | ||
app.renderer?.schedule(() => { | ||
let elt = this.element; | ||
if (!elt) | ||
return; | ||
this._hidden = hidden; | ||
if (!hidden) | ||
this.updateStyle(elt); | ||
if (this._updateCallback) { | ||
this._updateCallback = this._updateCallback.call(undefined, hidden ? undefined : this.output); | ||
} | ||
}); | ||
} | ||
_hidden; | ||
@@ -143,3 +161,3 @@ /** Handles platform events, invoked in response to `element.sendPlatformEvent()` */ | ||
let baseEvent = _eventNames[name]; | ||
if (baseEvent && this.observed && !this.observed.isUnlinked()) { | ||
if (baseEvent && !this.observed.isUnlinked()) { | ||
let event = new ManagedEvent(baseEvent, this.observed, data, undefined, undefined, baseEvent === "MouseEnter" || baseEvent === "MouseLeave"); | ||
@@ -166,3 +184,5 @@ this.observed.emit(event); | ||
// call render callback with new element | ||
this.updateCallback = event.data.render.call(undefined, this._hidden ? undefined : this.output, () => { | ||
this._updateCallback = event.data.render.call(undefined, this._hidden ? undefined : this.output, () => { | ||
if (this.observed.isUnlinked()) | ||
return; | ||
// try to focus if requested | ||
@@ -174,9 +194,6 @@ if (this._requestedFocus && this.element && !this._hidden) { | ||
// emit Rendered event | ||
if (this.observed && !this.observed.isUnlinked()) { | ||
this.observed.emit(this._thisRenderedEvent); | ||
} | ||
this.observed.emit(this._thisRenderedEvent); | ||
}); | ||
} | ||
} | ||
updateCallback; | ||
/** Focus current element if possible */ | ||
@@ -222,2 +239,4 @@ onRequestFocus(event) { | ||
} | ||
_updateCallback; | ||
_thisRenderedEvent; | ||
} |
@@ -1,2 +0,2 @@ | ||
import { Observer, RenderContext, View } from "@desk-framework/frame-core"; | ||
import { RenderContext, View } from "@desk-framework/frame-core"; | ||
import { OutputAssertion, OutputSelectFilter } from "../app/OutputAssertion.js"; | ||
@@ -51,3 +51,3 @@ import type { TestContextOptions } from "../app/TestContext.js"; | ||
/** Attaches a renderer to the the provided UI component (called internally) */ | ||
createObserver<T extends View>(target: T): Observer<T> | undefined; | ||
createObserver(target: View): unknown; | ||
/** | ||
@@ -54,0 +54,0 @@ * Clears all existing output |
@@ -1,2 +0,2 @@ | ||
import { RenderContext, ui, } from "@desk-framework/frame-core"; | ||
import { RenderContext, ui } from "@desk-framework/frame-core"; | ||
import { TestOutputElement } from "../app/TestOutputElement.js"; | ||
@@ -6,24 +6,23 @@ import { TestBaseObserver, applyElementStyle, getBaseStyleClass, } from "./TestBaseObserver.js"; | ||
export class UIButtonRenderer extends TestBaseObserver { | ||
observe(observed) { | ||
return super | ||
.observe(observed) | ||
.observePropertyAsync("label", "icon", "chevron", "disabled", "width", "pressed", "style"); | ||
constructor(observed) { | ||
super(observed); | ||
this.observeProperties("label", "icon", "chevron", "disabled", "width", "pressed", "style"); | ||
} | ||
async handlePropertyChange(property, value, event) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "label": | ||
case "icon": | ||
case "chevron": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "disabled": | ||
case "pressed": | ||
case "width": | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
propertyChange(property, value) { | ||
if (!this.element) | ||
return; | ||
switch (property) { | ||
case "label": | ||
case "icon": | ||
case "chevron": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "disabled": | ||
case "pressed": | ||
case "width": | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
super.propertyChange(property, value); | ||
} | ||
@@ -41,4 +40,2 @@ handlePlatformEvent(name, data) { | ||
getOutput() { | ||
if (!this.observed) | ||
throw ReferenceError(); | ||
let elt = new TestOutputElement("button"); | ||
@@ -51,27 +48,24 @@ let output = new RenderContext.Output(this.observed, elt); | ||
updateStyle(element) { | ||
// set state | ||
let button = this.observed; | ||
if (button) { | ||
// set state | ||
element.disabled = button.disabled; | ||
element.pressed = button.pressed; | ||
// set styles | ||
element.styleClass = | ||
getBaseStyleClass(button.style) || | ||
(button.primary ? ui.style.BUTTON_PRIMARY : ui.style.BUTTON); | ||
applyElementStyle(element, [ | ||
button.style, | ||
button.width !== undefined | ||
? { width: button.width, minWidth: 0 } | ||
: undefined, | ||
], button.position); | ||
} | ||
element.disabled = !!button.disabled; | ||
element.pressed = !!button.pressed; | ||
// set styles | ||
element.styleClass = | ||
getBaseStyleClass(button.style) || | ||
(button.primary ? ui.style.BUTTON_PRIMARY : ui.style.BUTTON); | ||
applyElementStyle(element, [ | ||
button.style, | ||
button.width !== undefined | ||
? { width: button.width, minWidth: 0 } | ||
: undefined, | ||
], button.position); | ||
} | ||
updateContent(element) { | ||
if (!this.observed) | ||
return; | ||
element.text = String(this.observed.label || ""); | ||
element.icon = String(this.observed.icon || ""); | ||
element.chevron = String(this.observed.chevron || ""); | ||
let button = this.observed; | ||
element.text = String(button.label || ""); | ||
element.icon = String(button.icon || ""); | ||
element.chevron = String(button.chevron || ""); | ||
element.focusable = true; | ||
} | ||
} |
@@ -6,26 +6,25 @@ import { ui } from "@desk-framework/frame-core"; | ||
export class UICellRenderer extends UIContainerRenderer { | ||
observe(observed) { | ||
return super.observe(observed).observePropertyAsync( | ||
constructor(observed) { | ||
super(observed); | ||
this.observeProperties( | ||
// note some properties are handled by container (e.g. padding) | ||
"textDirection", "margin", "borderRadius", "background", "textColor", "opacity", "style"); | ||
} | ||
async handlePropertyChange(property, value, event) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "textDirection": | ||
case "margin": | ||
case "borderRadius": | ||
case "background": | ||
case "textColor": | ||
case "opacity": | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
propertyChange(property, value) { | ||
if (!this.element) | ||
return; | ||
switch (property) { | ||
case "textDirection": | ||
case "margin": | ||
case "borderRadius": | ||
case "background": | ||
case "textColor": | ||
case "opacity": | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
super.propertyChange(property, value); | ||
} | ||
getOutput() { | ||
if (!this.observed) | ||
throw ReferenceError(); | ||
let output = super.getOutput(); | ||
@@ -38,4 +37,2 @@ if (this.observed.allowFocus || this.observed.allowKeyboardFocus) | ||
let cell = this.observed; | ||
if (!cell) | ||
return; | ||
super.updateContent(element); | ||
@@ -47,4 +44,2 @@ if (cell.allowFocus || cell.allowKeyboardFocus) | ||
let cell = this.observed; | ||
if (!cell) | ||
return; | ||
// NOTE: margin, textDirection aren't applied in test renderer | ||
@@ -51,0 +46,0 @@ super.updateStyle(element, getBaseStyleClass(cell.style) || ui.style.CELL, [ |
@@ -6,38 +6,41 @@ import { RenderContext, UICell, UIColumn, UIRow, UIScrollContainer, app, } from "@desk-framework/frame-core"; | ||
export class UIContainerRenderer extends TestBaseObserver { | ||
observe(observed) { | ||
let result = super | ||
.observe(observed) | ||
.observePropertyAsync("content", "layout", "padding"); | ||
constructor(observed) { | ||
super(observed); | ||
this.observeProperties("layout", "padding"); | ||
if (observed instanceof UIRow) { | ||
result.observePropertyAsync("height", "align"); | ||
this.observeProperties("height", "align"); | ||
} | ||
if (observed instanceof UIColumn) { | ||
result.observePropertyAsync("width", "align"); | ||
this.observeProperties("width", "align"); | ||
} | ||
return result; | ||
// observe content changes | ||
observed.content.listen((e) => { | ||
if (this.element && e.source === observed.content) { | ||
this.scheduleUpdate(this.element); | ||
} | ||
}); | ||
// observe unlink, to stop content updater right away | ||
observed.listen({ | ||
unlinked: () => { | ||
if (this.contentUpdater) | ||
this.contentUpdater.stop(); | ||
this.contentUpdater = undefined; | ||
}, | ||
}); | ||
} | ||
handleUnlink() { | ||
if (this.contentUpdater) | ||
this.contentUpdater.stop(); | ||
this.contentUpdater = undefined; | ||
super.handleUnlink(); | ||
} | ||
async handlePropertyChange(property, value, event) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "content": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "layout": | ||
case "padding": | ||
case "align": // for rows and columns | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
propertyChange(property, value) { | ||
if (!this.element) | ||
return; | ||
switch (property) { | ||
case "layout": | ||
case "padding": | ||
case "align": // for rows and columns | ||
case "height": // for rows | ||
case "width": // for columns | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
super.propertyChange(property, value); | ||
} | ||
getOutput() { | ||
if (!this.observed) | ||
throw ReferenceError(); | ||
let type; | ||
@@ -70,4 +73,2 @@ if (this.observed.accessibleRole === "form") | ||
let container = this.observed; | ||
if (!container) | ||
return; | ||
if (!this.contentUpdater) { | ||
@@ -83,23 +84,21 @@ this.contentUpdater = new ContentUpdater(container, element).setAsyncRendering(container.asyncContentRendering); | ||
let container = this.observed; | ||
if (container) { | ||
let layout = container.layout; | ||
if (container instanceof UIRow) { | ||
styles = [{ height: container.height, padding: container.padding }]; | ||
if (container.align) { | ||
layout = { ...layout, distribution: container.align }; | ||
} | ||
let layout = container.layout; | ||
if (container instanceof UIRow) { | ||
styles = [{ height: container.height, padding: container.padding }]; | ||
if (container.align) { | ||
layout = { ...layout, distribution: container.align }; | ||
} | ||
else if (container instanceof UIColumn) { | ||
styles = [{ width: container.width, padding: container.padding }]; | ||
if (container.align) { | ||
layout = { ...layout, gravity: container.align }; | ||
} | ||
} | ||
else if (container instanceof UIColumn) { | ||
styles = [{ width: container.width, padding: container.padding }]; | ||
if (container.align) { | ||
layout = { ...layout, gravity: container.align }; | ||
} | ||
else if (container instanceof UIScrollContainer) { | ||
styles = [{ padding: container.padding }]; | ||
} | ||
// apply styles | ||
element.styleClass = BaseStyle; | ||
applyElementStyle(element, styles, container.position, layout); | ||
} | ||
else if (container instanceof UIScrollContainer) { | ||
styles = [{ padding: container.padding }]; | ||
} | ||
// apply styles | ||
element.styleClass = BaseStyle; | ||
applyElementStyle(element, styles, container.position, layout); | ||
} | ||
@@ -106,0 +105,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import { RenderContext, ui, } from "@desk-framework/frame-core"; | ||
import { RenderContext, ui } from "@desk-framework/frame-core"; | ||
import { TestOutputElement } from "../app/TestOutputElement.js"; | ||
@@ -6,21 +6,20 @@ import { TestBaseObserver, applyElementStyle, getBaseStyleClass, } from "./TestBaseObserver.js"; | ||
export class UIImageRenderer extends TestBaseObserver { | ||
observe(observed) { | ||
return super.observe(observed).observePropertyAsync("url", "style"); | ||
constructor(observed) { | ||
super(observed); | ||
this.observeProperties("url", "style"); | ||
} | ||
async handlePropertyChange(property, value, event) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "url": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
propertyChange(property, value) { | ||
if (!this.element) | ||
return; | ||
switch (property) { | ||
case "url": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
super.propertyChange(property, value); | ||
} | ||
getOutput() { | ||
if (!this.observed) | ||
throw ReferenceError(); | ||
let elt = new TestOutputElement("image"); | ||
@@ -35,12 +34,8 @@ let output = new RenderContext.Output(this.observed, elt); | ||
let image = this.observed; | ||
if (image) { | ||
element.styleClass = getBaseStyleClass(image.style) || ui.style.IMAGE; | ||
applyElementStyle(element, [image.style, { width: image.width, height: image.height }], image.position); | ||
} | ||
element.styleClass = getBaseStyleClass(image.style) || ui.style.IMAGE; | ||
applyElementStyle(element, [image.style, { width: image.width, height: image.height }], image.position); | ||
} | ||
updateContent(element) { | ||
if (!this.observed) | ||
return; | ||
element.imageUrl = String(this.observed.url || ""); | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
import { RenderContext, ui, } from "@desk-framework/frame-core"; | ||
import { RenderContext, ui } from "@desk-framework/frame-core"; | ||
import { TestOutputElement } from "../app/TestOutputElement.js"; | ||
@@ -6,29 +6,26 @@ import { TestBaseObserver, applyElementStyle, getBaseStyleClass, } from "./TestBaseObserver.js"; | ||
export class UILabelRenderer extends TestBaseObserver { | ||
observe(observed) { | ||
return super | ||
.observe(observed) | ||
.observePropertyAsync("text", "icon", "bold", "italic", "color", "width", "dim", "style"); | ||
constructor(observed) { | ||
super(observed); | ||
this.observeProperties("text", "icon", "bold", "italic", "color", "width", "dim", "style"); | ||
} | ||
async handlePropertyChange(property, value, event) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "text": | ||
case "icon": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "bold": | ||
case "italic": | ||
case "color": | ||
case "width": | ||
case "dim": | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
propertyChange(property, value) { | ||
if (!this.element) | ||
return; | ||
switch (property) { | ||
case "text": | ||
case "icon": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "bold": | ||
case "italic": | ||
case "color": | ||
case "width": | ||
case "dim": | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
super.propertyChange(property, value); | ||
} | ||
getOutput() { | ||
if (!this.observed) | ||
throw ReferenceError(); | ||
let elt = new TestOutputElement("label"); | ||
@@ -43,27 +40,23 @@ let output = new RenderContext.Output(this.observed, elt); | ||
let label = this.observed; | ||
if (label) { | ||
element.styleClass = | ||
getBaseStyleClass(label.style) || | ||
(label.title | ||
? ui.style.LABEL_TITLE | ||
: label.small | ||
? ui.style.LABEL_SMALL | ||
: ui.style.LABEL); | ||
applyElementStyle(element, [ | ||
label.style, | ||
{ | ||
width: label.width, | ||
bold: label.bold, | ||
italic: label.italic, | ||
textColor: label.color, | ||
opacity: label.dim === true ? 0.5 : label.dim === false ? 1 : label.dim, | ||
lineBreakMode: label.wrap ? "pre-wrap" : undefined, | ||
userSelect: label.selectable || undefined, | ||
}, | ||
], label.position); | ||
} | ||
element.styleClass = | ||
getBaseStyleClass(label.style) || | ||
(label.title | ||
? ui.style.LABEL_TITLE | ||
: label.small | ||
? ui.style.LABEL_SMALL | ||
: ui.style.LABEL); | ||
applyElementStyle(element, [ | ||
label.style, | ||
{ | ||
width: label.width, | ||
bold: label.bold, | ||
italic: label.italic, | ||
textColor: label.color, | ||
opacity: label.dim === true ? 0.5 : label.dim === false ? 1 : label.dim, | ||
lineBreakMode: label.wrap ? "pre-wrap" : undefined, | ||
userSelect: label.selectable || undefined, | ||
}, | ||
], label.position); | ||
} | ||
updateContent(element) { | ||
if (!this.observed) | ||
return; | ||
element.text = String(this.observed.text); | ||
@@ -70,0 +63,0 @@ element.icon = String(this.observed.icon || ""); |
@@ -1,2 +0,2 @@ | ||
import { RenderContext, } from "@desk-framework/frame-core"; | ||
import { RenderContext } from "@desk-framework/frame-core"; | ||
import { TestOutputElement } from "../app/TestOutputElement.js"; | ||
@@ -6,22 +6,19 @@ import { TestBaseObserver, applyElementStyle } from "./TestBaseObserver.js"; | ||
export class UISeparatorRenderer extends TestBaseObserver { | ||
observe(observed) { | ||
return super | ||
.observe(observed) | ||
.observePropertyAsync("color", "margin", "thickness"); | ||
constructor(observed) { | ||
super(observed); | ||
this.observeProperties("color", "margin", "thickness"); | ||
} | ||
async handlePropertyChange(property, value, event) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "color": | ||
case "margin": | ||
case "thickness": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
propertyChange(property, value) { | ||
if (!this.element) | ||
return; | ||
switch (property) { | ||
case "color": | ||
case "margin": | ||
case "thickness": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
super.propertyChange(property, value); | ||
} | ||
getOutput() { | ||
if (!this.observed) | ||
throw ReferenceError(); | ||
let elt = new TestOutputElement("separator"); | ||
@@ -35,12 +32,10 @@ let output = new RenderContext.Output(this.observed, elt); | ||
let sep = this.observed; | ||
if (sep) { | ||
// NOTE: margin is ignored in test renderer | ||
applyElementStyle(element, [ | ||
{ | ||
borderColor: sep.color, | ||
borderThickness: sep.thickness, | ||
}, | ||
], sep.position); | ||
} | ||
// NOTE: margin is ignored in test renderer | ||
applyElementStyle(element, [ | ||
{ | ||
borderColor: sep.color, | ||
borderThickness: sep.thickness, | ||
}, | ||
], sep.position); | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
import { RenderContext, } from "@desk-framework/frame-core"; | ||
import { RenderContext } from "@desk-framework/frame-core"; | ||
import { TestOutputElement } from "../app/TestOutputElement.js"; | ||
@@ -6,23 +6,20 @@ import { TestBaseObserver, applyElementStyle } from "./TestBaseObserver.js"; | ||
export class UISpacerRenderer extends TestBaseObserver { | ||
observe(observed) { | ||
return super | ||
.observe(observed) | ||
.observePropertyAsync("width", "height", "minWidth", "minHeight"); | ||
constructor(observed) { | ||
super(observed); | ||
this.observeProperties("width", "height", "minWidth", "minHeight"); | ||
} | ||
async handlePropertyChange(property, value, event) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "width": | ||
case "height": | ||
case "minWidth": | ||
case "minHeight": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
propertyChange(property, value) { | ||
if (!this.element) | ||
return; | ||
switch (property) { | ||
case "width": | ||
case "height": | ||
case "minWidth": | ||
case "minHeight": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
super.propertyChange(property, value); | ||
} | ||
getOutput() { | ||
if (!this.observed) | ||
throw ReferenceError(); | ||
let elt = new TestOutputElement("spacer"); | ||
@@ -36,18 +33,16 @@ let output = new RenderContext.Output(this.observed, elt); | ||
let spacer = this.observed; | ||
if (spacer) { | ||
let { width, height, minWidth, minHeight } = spacer; | ||
let hasMinimum = minWidth !== undefined || minHeight !== undefined; | ||
let hasFixed = width !== undefined || height !== undefined; | ||
applyElementStyle(element, [ | ||
{ | ||
width, | ||
height, | ||
minWidth, | ||
minHeight, | ||
grow: hasFixed ? 0 : 1, | ||
shrink: hasMinimum ? 0 : 1, | ||
}, | ||
], spacer.position); | ||
} | ||
let { width, height, minWidth, minHeight } = spacer; | ||
let hasMinimum = minWidth !== undefined || minHeight !== undefined; | ||
let hasFixed = width !== undefined || height !== undefined; | ||
applyElementStyle(element, [ | ||
{ | ||
width, | ||
height, | ||
minWidth, | ||
minHeight, | ||
grow: hasFixed ? 0 : 1, | ||
shrink: hasMinimum ? 0 : 1, | ||
}, | ||
], spacer.position); | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
import { RenderContext, ui, } from "@desk-framework/frame-core"; | ||
import { RenderContext, ui } from "@desk-framework/frame-core"; | ||
import { TestOutputElement } from "../app/TestOutputElement.js"; | ||
@@ -6,27 +6,26 @@ import { TestBaseObserver, applyElementStyle, getBaseStyleClass, } from "./TestBaseObserver.js"; | ||
export class UITextFieldRenderer extends TestBaseObserver { | ||
observe(observed) { | ||
return super | ||
.observe(observed) | ||
.observePropertyAsync("placeholder", "value", "disabled", "readOnly", "width", "style"); | ||
constructor(observed) { | ||
super(observed); | ||
this.observeProperties("placeholder", "value", "disabled", "readOnly", "width", "style"); | ||
} | ||
async handlePropertyChange(property, value, event) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "placeholder": | ||
case "value": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "disabled": | ||
case "readOnly": | ||
case "width": | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
propertyChange(property, value) { | ||
if (!this.element) | ||
return; | ||
switch (property) { | ||
case "placeholder": | ||
case "value": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "disabled": | ||
case "readOnly": | ||
case "width": | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
super.propertyChange(property, value); | ||
} | ||
handlePlatformEvent(name, data) { | ||
// update 'value' first, reflecting the element value | ||
if (this.element && this.observed) { | ||
if (this.element) { | ||
this.observed.value = this.element.value || ""; | ||
@@ -38,4 +37,2 @@ } | ||
// NOTE: ignoring multiline flag here | ||
if (!this.observed) | ||
throw ReferenceError(); | ||
let elt = new TestOutputElement("textfield"); | ||
@@ -49,20 +46,16 @@ let output = new RenderContext.Output(this.observed, elt); | ||
let textField = this.observed; | ||
if (textField) { | ||
// set state | ||
element.disabled = textField.disabled; | ||
element.readOnly = textField.readOnly; | ||
// set styles | ||
element.styleClass = | ||
getBaseStyleClass(textField.style) || ui.style.TEXTFIELD; | ||
applyElementStyle(element, [ | ||
textField.style, | ||
textField.width !== undefined | ||
? { width: textField.width, minWidth: 0 } | ||
: undefined, | ||
], textField.position); | ||
} | ||
// set state | ||
element.disabled = textField.disabled; | ||
element.readOnly = textField.readOnly; | ||
// set styles | ||
element.styleClass = | ||
getBaseStyleClass(textField.style) || ui.style.TEXTFIELD; | ||
applyElementStyle(element, [ | ||
textField.style, | ||
textField.width !== undefined | ||
? { width: textField.width, minWidth: 0 } | ||
: undefined, | ||
], textField.position); | ||
} | ||
updateContent(element) { | ||
if (!this.observed) | ||
return; | ||
element.text = String(this.observed.placeholder || ""); | ||
@@ -69,0 +62,0 @@ let value = this.observed.value; |
@@ -1,2 +0,2 @@ | ||
import { RenderContext, ui, } from "@desk-framework/frame-core"; | ||
import { RenderContext, ui } from "@desk-framework/frame-core"; | ||
import { TestOutputElement } from "../app/TestOutputElement.js"; | ||
@@ -6,22 +6,21 @@ import { TestBaseObserver, applyElementStyle, getBaseStyleClass, } from "./TestBaseObserver.js"; | ||
export class UIToggleRenderer extends TestBaseObserver { | ||
observe(observed) { | ||
return super | ||
.observe(observed) | ||
.observePropertyAsync("label", "state", "disabled", "style", "labelStyle"); | ||
constructor(observed) { | ||
super(observed); | ||
this.observeProperties("label", "state", "disabled", "style", "labelStyle"); | ||
} | ||
async handlePropertyChange(property, value, event) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "label": | ||
case "state": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "disabled": | ||
case "style": | ||
case "labelStyle": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
propertyChange(property, value) { | ||
if (!this.element) | ||
return; | ||
switch (property) { | ||
case "label": | ||
case "state": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "disabled": | ||
case "style": | ||
case "labelStyle": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
super.propertyChange(property, value); | ||
} | ||
@@ -37,4 +36,2 @@ handlePlatformEvent(name, data) { | ||
getOutput() { | ||
if (!this.observed) | ||
throw ReferenceError(); | ||
let elt = new TestOutputElement("toggle"); | ||
@@ -48,13 +45,9 @@ let output = new RenderContext.Output(this.observed, elt); | ||
let toggle = this.observed; | ||
if (toggle) { | ||
// set disabled state | ||
element.disabled = toggle.disabled; | ||
// set styles | ||
element.styleClass = getBaseStyleClass(toggle.style) || ui.style.TOGGLE; | ||
applyElementStyle(element, [toggle.style], toggle.position); | ||
} | ||
// set disabled state | ||
element.disabled = toggle.disabled; | ||
// set styles | ||
element.styleClass = getBaseStyleClass(toggle.style) || ui.style.TOGGLE; | ||
applyElementStyle(element, [toggle.style], toggle.position); | ||
} | ||
updateContent(element) { | ||
if (!this.observed) | ||
return; | ||
element.text = String(this.observed.label || ""); | ||
@@ -61,0 +54,0 @@ element.checked = !!this.observed.state; |
{ | ||
"name": "@desk-framework/frame-test", | ||
"version": "4.0.0-dev.18", | ||
"version": "4.0.0-dev.19", | ||
"publishConfig": { | ||
@@ -32,3 +32,3 @@ "tag": "next" | ||
"peerDependencies": { | ||
"@desk-framework/frame-core": "4.0.0-dev.18" | ||
"@desk-framework/frame-core": "4.0.0-dev.19" | ||
}, | ||
@@ -35,0 +35,0 @@ "devDependencies": { |
@@ -78,4 +78,4 @@ import { | ||
// create test renderer | ||
app.renderer = new TestRenderer(options); | ||
app.theme = new TestTheme(); | ||
(app as any).renderer = new TestRenderer(options); | ||
@@ -82,0 +82,0 @@ // create no-op viewport context |
import { | ||
Observer, | ||
UIButton, | ||
@@ -23,28 +22,27 @@ UICell, | ||
import { UIToggleRenderer } from "./UIToggleRenderer.js"; | ||
import { TestBaseObserver } from "./TestBaseObserver.js"; | ||
/** @internal */ | ||
export function makeObserver<T extends View>( | ||
target: T, | ||
): Observer<T> | undefined { | ||
export function makeObserver(target: View): TestBaseObserver<any> | undefined { | ||
return ( | ||
target instanceof UICell | ||
? new UICellRenderer() | ||
? new UICellRenderer(target) | ||
: target instanceof UIContainer | ||
? new UIContainerRenderer() | ||
? new UIContainerRenderer(target) | ||
: target instanceof UILabel | ||
? new UILabelRenderer() | ||
? new UILabelRenderer(target) | ||
: target instanceof UIButton | ||
? new UIButtonRenderer() | ||
? new UIButtonRenderer(target) | ||
: target instanceof UIImage | ||
? new UIImageRenderer() | ||
? new UIImageRenderer(target) | ||
: target instanceof UISeparator | ||
? new UISeparatorRenderer() | ||
? new UISeparatorRenderer(target) | ||
: target instanceof UISpacer | ||
? new UISpacerRenderer() | ||
? new UISpacerRenderer(target) | ||
: target instanceof UITextField | ||
? new UITextFieldRenderer() | ||
? new UITextFieldRenderer(target) | ||
: target instanceof UIToggle | ||
? new UIToggleRenderer() | ||
? new UIToggleRenderer(target) | ||
: undefined | ||
) as any; | ||
} |
import { | ||
ManagedEvent, | ||
Observer, | ||
ManagedObject, | ||
RenderContext, | ||
@@ -87,7 +87,5 @@ UIComponent, | ||
/** @internal Abstract observer class for all `UIComponent` instances, to create output and call render callback; implemented for all types of UI components */ | ||
export abstract class TestBaseObserver< | ||
TUIComponent extends UIComponent, | ||
> extends Observer<TUIComponent> { | ||
override observe(observed: TUIComponent) { | ||
/** @internal Abstract observer class for all `UIComponent` instances, to create output and call render callback; implemented for all types of UI components, created upon rendering, and attached to enable property bindings */ | ||
export abstract class TestBaseObserver<TUIComponent extends UIComponent> { | ||
constructor(public observed: TUIComponent) { | ||
this._thisRenderedEvent = new ManagedEvent( | ||
@@ -101,45 +99,46 @@ "Rendered", | ||
); | ||
return super.observe(observed).observePropertyAsync("hidden", "position"); | ||
this.observeProperties("hidden", "position"); | ||
observed.listen((e) => { | ||
let handler = (this as any)["on" + e.name]; | ||
if (typeof handler === "function") handler.call(this, e); | ||
}); | ||
} | ||
private _thisRenderedEvent?: ManagedEvent; | ||
/** Set up one or more property listeners, to call {@link propertyChange} on this observer */ | ||
protected observeProperties(...properties: (keyof this["observed"])[]) { | ||
ManagedObject.observe(this.observed, properties, (_, p, v) => | ||
this.propertyChange(p as any, v), | ||
); | ||
} | ||
/** Handler for base property changes; must be overridden to handle other UI component properties */ | ||
protected override async handlePropertyChange( | ||
property: string, | ||
value: any, | ||
event?: ManagedEvent, | ||
) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "hidden": | ||
this._hidden = this.observed.hidden; | ||
if (!this._hidden) this.updateStyle(this.element); | ||
if (this.updateCallback) { | ||
this.updateCallback = this.updateCallback.call( | ||
undefined, | ||
this._hidden ? undefined : this.output, | ||
); | ||
} | ||
return; | ||
case "position": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
protected propertyChange(property: string, value: any) { | ||
if (!this.element) return; | ||
switch (property) { | ||
case "hidden": | ||
this.scheduleHide(value); | ||
return; | ||
case "position": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
} | ||
/** Rendered element, if any; set by `onRender` handler based on return value of `getOutput()` method */ | ||
element?: TestOutputElement; | ||
/** Rendered output, if any; set by `onRender` handler based on return value of `getOutput()` method */ | ||
output?: RenderContext.Output<TestOutputElement>; | ||
/** Rendered element, if any; set by `onRender` handler based on return value of `getOutput()` method */ | ||
element?: TestOutputElement; | ||
/** Creates test output (with element to render) for the observed UI component; called before rendering, must be overridden to create instances of `TestOutputElement` */ | ||
abstract getOutput(): RenderContext.Output & { element: TestOutputElement }; | ||
/** Updates the specified output element with content: either from properties (e.g. text content) or from other UI components; called automatically by `update()`, but can also be called when state properties change; must be overridden */ | ||
abstract updateContent(element: TestOutputElement): void; | ||
/** Updates the specified output element with all style properties; called automatically by `update()`, but can also be called when state properties change; must be overridden to update test output element styles */ | ||
abstract updateStyle(element: TestOutputElement): void; | ||
/** Updates the specified output element with all properties of the UI component; called automatically before rendering (after `getOutput`), but can also be called when state properties change */ | ||
update(element: TestOutputElement) { | ||
if (!this.observed) return; | ||
this._hidden = this.observed.hidden; | ||
@@ -165,2 +164,3 @@ this._asyncContentUp = undefined; | ||
app.renderer.schedule(() => { | ||
if (this.observed.isUnlinked()) return; | ||
this._asyncUp = false; | ||
@@ -180,8 +180,18 @@ if (this._asyncContentUp) this.updateContent(this._asyncContentUp); | ||
/** Updates the specified output element with content: either from properties (e.g. text content) or from other UI components; called automatically by `update()`, but can also be called when state properties change; must be overridden */ | ||
abstract updateContent(element: TestOutputElement): void; | ||
/** Schedules an asynchronous update to show or hide the output */ | ||
scheduleHide(hidden?: boolean) { | ||
app.renderer?.schedule(() => { | ||
let elt = this.element; | ||
if (!elt) return; | ||
this._hidden = hidden; | ||
if (!hidden) this.updateStyle(elt); | ||
if (this._updateCallback) { | ||
this._updateCallback = this._updateCallback.call( | ||
undefined, | ||
hidden ? undefined : this.output, | ||
); | ||
} | ||
}); | ||
} | ||
/** Updates the specified output element with all style properties; called automatically by `update()`, but can also be called when state properties change; must be overridden to update test output element styles */ | ||
abstract updateStyle(element: TestOutputElement): void; | ||
private _hidden?: boolean; | ||
@@ -192,3 +202,3 @@ | ||
let baseEvent = _eventNames[name]; | ||
if (baseEvent && this.observed && !this.observed.isUnlinked()) { | ||
if (baseEvent && !this.observed.isUnlinked()) { | ||
let event = new ManagedEvent( | ||
@@ -229,6 +239,8 @@ baseEvent, | ||
// call render callback with new element | ||
this.updateCallback = event.data.render.call( | ||
this._updateCallback = event.data.render.call( | ||
undefined, | ||
this._hidden ? undefined : this.output, | ||
() => { | ||
if (this.observed.isUnlinked()) return; | ||
// try to focus if requested | ||
@@ -241,5 +253,3 @@ if (this._requestedFocus && this.element && !this._hidden) { | ||
// emit Rendered event | ||
if (this.observed && !this.observed.isUnlinked()) { | ||
this.observed.emit(this._thisRenderedEvent); | ||
} | ||
this.observed.emit(this._thisRenderedEvent); | ||
}, | ||
@@ -250,4 +260,2 @@ ); | ||
updateCallback?: RenderContext.RenderCallback; | ||
/** Focus current element if possible */ | ||
@@ -299,2 +307,5 @@ onRequestFocus(event: ManagedEvent) { | ||
} | ||
private _updateCallback?: RenderContext.RenderCallback; | ||
private _thisRenderedEvent?: ManagedEvent; | ||
} |
import { | ||
AsyncTaskQueue, | ||
Observer, | ||
RenderContext, | ||
@@ -145,3 +144,3 @@ View, | ||
/** Attaches a renderer to the the provided UI component (called internally) */ | ||
createObserver<T extends View>(target: T): Observer<T> | undefined { | ||
createObserver(target: View): unknown { | ||
return makeObserver(target); | ||
@@ -148,0 +147,0 @@ } |
@@ -1,7 +0,2 @@ | ||
import { | ||
ManagedEvent, | ||
RenderContext, | ||
UIButton, | ||
ui, | ||
} from "@desk-framework/frame-core"; | ||
import { RenderContext, UIButton, ui } from "@desk-framework/frame-core"; | ||
import { TestOutputElement } from "../app/TestOutputElement.js"; | ||
@@ -16,37 +11,31 @@ import { | ||
export class UIButtonRenderer extends TestBaseObserver<UIButton> { | ||
override observe(observed: UIButton) { | ||
return super | ||
.observe(observed) | ||
.observePropertyAsync( | ||
"label", | ||
"icon", | ||
"chevron", | ||
"disabled", | ||
"width", | ||
"pressed", | ||
"style", | ||
); | ||
constructor(observed: UIButton) { | ||
super(observed); | ||
this.observeProperties( | ||
"label", | ||
"icon", | ||
"chevron", | ||
"disabled", | ||
"width", | ||
"pressed", | ||
"style", | ||
); | ||
} | ||
protected override async handlePropertyChange( | ||
property: string, | ||
value: any, | ||
event?: ManagedEvent, | ||
) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "label": | ||
case "icon": | ||
case "chevron": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "disabled": | ||
case "pressed": | ||
case "width": | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
protected override propertyChange(property: string, value: any) { | ||
if (!this.element) return; | ||
switch (property) { | ||
case "label": | ||
case "icon": | ||
case "chevron": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "disabled": | ||
case "pressed": | ||
case "width": | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
super.propertyChange(property, value); | ||
} | ||
@@ -71,3 +60,2 @@ | ||
getOutput() { | ||
if (!this.observed) throw ReferenceError(); | ||
let elt = new TestOutputElement("button"); | ||
@@ -81,32 +69,30 @@ let output = new RenderContext.Output(this.observed, elt); | ||
override updateStyle(element: TestOutputElement) { | ||
// set state | ||
let button = this.observed; | ||
if (button) { | ||
// set state | ||
element.disabled = button.disabled; | ||
element.pressed = button.pressed; | ||
element.disabled = !!button.disabled; | ||
element.pressed = !!button.pressed; | ||
// set styles | ||
element.styleClass = | ||
getBaseStyleClass(button.style) || | ||
(button.primary ? ui.style.BUTTON_PRIMARY : ui.style.BUTTON); | ||
applyElementStyle( | ||
element, | ||
[ | ||
button.style, | ||
button.width !== undefined | ||
? { width: button.width, minWidth: 0 } | ||
: undefined, | ||
], | ||
button.position, | ||
); | ||
} | ||
// set styles | ||
element.styleClass = | ||
getBaseStyleClass(button.style) || | ||
(button.primary ? ui.style.BUTTON_PRIMARY : ui.style.BUTTON); | ||
applyElementStyle( | ||
element, | ||
[ | ||
button.style, | ||
button.width !== undefined | ||
? { width: button.width, minWidth: 0 } | ||
: undefined, | ||
], | ||
button.position, | ||
); | ||
} | ||
updateContent(element: TestOutputElement) { | ||
if (!this.observed) return; | ||
element.text = String(this.observed.label || ""); | ||
element.icon = String(this.observed.icon || ""); | ||
element.chevron = String(this.observed.chevron || ""); | ||
let button = this.observed; | ||
element.text = String(button.label || ""); | ||
element.icon = String(button.icon || ""); | ||
element.chevron = String(button.chevron || ""); | ||
element.focusable = true; | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
import { ManagedEvent, UICell, ui } from "@desk-framework/frame-core"; | ||
import { UICell, ui } from "@desk-framework/frame-core"; | ||
import { TestOutputElement } from "../app/TestOutputElement.js"; | ||
@@ -8,4 +8,5 @@ import { getBaseStyleClass } from "./TestBaseObserver.js"; | ||
export class UICellRenderer extends UIContainerRenderer<UICell> { | ||
override observe(observed: UICell) { | ||
return super.observe(observed).observePropertyAsync( | ||
constructor(observed: UICell) { | ||
super(observed); | ||
this.observeProperties( | ||
// note some properties are handled by container (e.g. padding) | ||
@@ -22,25 +23,19 @@ "textDirection", | ||
protected override async handlePropertyChange( | ||
property: string, | ||
value: any, | ||
event?: ManagedEvent, | ||
) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "textDirection": | ||
case "margin": | ||
case "borderRadius": | ||
case "background": | ||
case "textColor": | ||
case "opacity": | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
protected override propertyChange(property: string, value: any) { | ||
if (!this.element) return; | ||
switch (property) { | ||
case "textDirection": | ||
case "margin": | ||
case "borderRadius": | ||
case "background": | ||
case "textColor": | ||
case "opacity": | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
super.propertyChange(property, value); | ||
} | ||
override getOutput() { | ||
if (!this.observed) throw ReferenceError(); | ||
let output = super.getOutput(); | ||
@@ -54,3 +49,2 @@ if (this.observed.allowFocus || this.observed.allowKeyboardFocus) | ||
let cell = this.observed; | ||
if (!cell) return; | ||
super.updateContent(element); | ||
@@ -62,3 +56,2 @@ if (cell.allowFocus || cell.allowKeyboardFocus) element.focusable = true; | ||
let cell = this.observed; | ||
if (!cell) return; | ||
@@ -65,0 +58,0 @@ // NOTE: margin, textDirection aren't applied in test renderer |
import { | ||
ManagedEvent, | ||
RenderContext, | ||
@@ -20,43 +19,43 @@ UICell, | ||
> extends TestBaseObserver<TContainer> { | ||
override observe(observed: UIContainer) { | ||
let result = super | ||
.observe(observed as any) | ||
.observePropertyAsync("content", "layout", "padding"); | ||
constructor(observed: TContainer) { | ||
super(observed); | ||
this.observeProperties("layout", "padding"); | ||
if (observed instanceof UIRow) { | ||
result.observePropertyAsync("height" as any, "align" as any); | ||
this.observeProperties("height" as any, "align" as any); | ||
} | ||
if (observed instanceof UIColumn) { | ||
result.observePropertyAsync("width" as any, "align" as any); | ||
this.observeProperties("width" as any, "align" as any); | ||
} | ||
return result; | ||
} | ||
override handleUnlink() { | ||
if (this.contentUpdater) this.contentUpdater.stop(); | ||
this.contentUpdater = undefined; | ||
super.handleUnlink(); | ||
// observe content changes | ||
observed.content.listen((e) => { | ||
if (this.element && e.source === observed.content) { | ||
this.scheduleUpdate(this.element); | ||
} | ||
}); | ||
// observe unlink, to stop content updater right away | ||
observed.listen({ | ||
unlinked: () => { | ||
if (this.contentUpdater) this.contentUpdater.stop(); | ||
this.contentUpdater = undefined; | ||
}, | ||
}); | ||
} | ||
protected override async handlePropertyChange( | ||
property: string, | ||
value: any, | ||
event?: ManagedEvent, | ||
) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "content": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "layout": | ||
case "padding": | ||
case "align": // for rows and columns | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
protected override propertyChange(property: string, value: any) { | ||
if (!this.element) return; | ||
switch (property) { | ||
case "layout": | ||
case "padding": | ||
case "align": // for rows and columns | ||
case "height": // for rows | ||
case "width": // for columns | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
super.propertyChange(property, value); | ||
} | ||
getOutput() { | ||
if (!this.observed) throw ReferenceError(); | ||
let type: TestOutputElement.TypeString; | ||
@@ -86,4 +85,2 @@ if (this.observed.accessibleRole === "form") type = "form"; | ||
let container = this.observed; | ||
if (!container) return; | ||
if (!this.contentUpdater) { | ||
@@ -109,22 +106,20 @@ this.contentUpdater = new ContentUpdater( | ||
let container = this.observed; | ||
if (container) { | ||
let layout = container.layout; | ||
if (container instanceof UIRow) { | ||
styles = [{ height: container.height, padding: container.padding }]; | ||
if (container.align) { | ||
layout = { ...layout, distribution: container.align }; | ||
} | ||
} else if (container instanceof UIColumn) { | ||
styles = [{ width: container.width, padding: container.padding }]; | ||
if (container.align) { | ||
layout = { ...layout, gravity: container.align }; | ||
} | ||
} else if (container instanceof UIScrollContainer) { | ||
styles = [{ padding: container.padding }]; | ||
let layout = container.layout; | ||
if (container instanceof UIRow) { | ||
styles = [{ height: container.height, padding: container.padding }]; | ||
if (container.align) { | ||
layout = { ...layout, distribution: container.align }; | ||
} | ||
} else if (container instanceof UIColumn) { | ||
styles = [{ width: container.width, padding: container.padding }]; | ||
if (container.align) { | ||
layout = { ...layout, gravity: container.align }; | ||
} | ||
} else if (container instanceof UIScrollContainer) { | ||
styles = [{ padding: container.padding }]; | ||
} | ||
// apply styles | ||
element.styleClass = BaseStyle; | ||
applyElementStyle(element, styles, container.position, layout); | ||
} | ||
// apply styles | ||
element.styleClass = BaseStyle; | ||
applyElementStyle(element, styles, container.position, layout); | ||
} | ||
@@ -131,0 +126,0 @@ } |
@@ -1,7 +0,2 @@ | ||
import { | ||
ManagedEvent, | ||
RenderContext, | ||
UIImage, | ||
ui, | ||
} from "@desk-framework/frame-core"; | ||
import { RenderContext, UIImage, ui } from "@desk-framework/frame-core"; | ||
import { TestOutputElement } from "../app/TestOutputElement.js"; | ||
@@ -16,26 +11,21 @@ import { | ||
export class UIImageRenderer extends TestBaseObserver<UIImage> { | ||
override observe(observed: UIImage) { | ||
return super.observe(observed).observePropertyAsync("url", "style"); | ||
constructor(observed: UIImage) { | ||
super(observed); | ||
this.observeProperties("url", "style"); | ||
} | ||
protected override async handlePropertyChange( | ||
property: string, | ||
value: any, | ||
event?: ManagedEvent, | ||
) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "url": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
protected override propertyChange(property: string, value: any) { | ||
if (!this.element) return; | ||
switch (property) { | ||
case "url": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
super.propertyChange(property, value); | ||
} | ||
getOutput() { | ||
if (!this.observed) throw ReferenceError(); | ||
let elt = new TestOutputElement("image"); | ||
@@ -51,16 +41,13 @@ let output = new RenderContext.Output(this.observed, elt); | ||
let image = this.observed; | ||
if (image) { | ||
element.styleClass = getBaseStyleClass(image.style) || ui.style.IMAGE; | ||
applyElementStyle( | ||
element, | ||
[image.style, { width: image.width, height: image.height }], | ||
image.position, | ||
); | ||
} | ||
element.styleClass = getBaseStyleClass(image.style) || ui.style.IMAGE; | ||
applyElementStyle( | ||
element, | ||
[image.style, { width: image.width, height: image.height }], | ||
image.position, | ||
); | ||
} | ||
updateContent(element: TestOutputElement) { | ||
if (!this.observed) return; | ||
element.imageUrl = String(this.observed.url || ""); | ||
} | ||
} |
@@ -1,7 +0,2 @@ | ||
import { | ||
ManagedEvent, | ||
RenderContext, | ||
UILabel, | ||
ui, | ||
} from "@desk-framework/frame-core"; | ||
import { RenderContext, UILabel, ui } from "@desk-framework/frame-core"; | ||
import { TestOutputElement } from "../app/TestOutputElement.js"; | ||
@@ -16,43 +11,36 @@ import { | ||
export class UILabelRenderer extends TestBaseObserver<UILabel> { | ||
override observe(observed: UILabel) { | ||
return super | ||
.observe(observed) | ||
.observePropertyAsync( | ||
"text", | ||
"icon", | ||
"bold", | ||
"italic", | ||
"color", | ||
"width", | ||
"dim", | ||
"style", | ||
); | ||
constructor(observed: UILabel) { | ||
super(observed); | ||
this.observeProperties( | ||
"text", | ||
"icon", | ||
"bold", | ||
"italic", | ||
"color", | ||
"width", | ||
"dim", | ||
"style", | ||
); | ||
} | ||
protected override async handlePropertyChange( | ||
property: string, | ||
value: any, | ||
event?: ManagedEvent, | ||
) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "text": | ||
case "icon": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "bold": | ||
case "italic": | ||
case "color": | ||
case "width": | ||
case "dim": | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
protected override propertyChange(property: string, value: any) { | ||
if (!this.element) return; | ||
switch (property) { | ||
case "text": | ||
case "icon": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "bold": | ||
case "italic": | ||
case "color": | ||
case "width": | ||
case "dim": | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
super.propertyChange(property, value); | ||
} | ||
getOutput() { | ||
if (!this.observed) throw ReferenceError(); | ||
let elt = new TestOutputElement("label"); | ||
@@ -68,32 +56,29 @@ let output = new RenderContext.Output(this.observed, elt); | ||
let label = this.observed; | ||
if (label) { | ||
element.styleClass = | ||
getBaseStyleClass(label.style) || | ||
(label.title | ||
? ui.style.LABEL_TITLE | ||
: label.small | ||
? ui.style.LABEL_SMALL | ||
: ui.style.LABEL); | ||
applyElementStyle( | ||
element, | ||
[ | ||
label.style, | ||
{ | ||
width: label.width, | ||
bold: label.bold, | ||
italic: label.italic, | ||
textColor: label.color, | ||
opacity: | ||
label.dim === true ? 0.5 : label.dim === false ? 1 : label.dim, | ||
lineBreakMode: label.wrap ? "pre-wrap" : undefined, | ||
userSelect: label.selectable || undefined, | ||
}, | ||
], | ||
label.position, | ||
); | ||
} | ||
element.styleClass = | ||
getBaseStyleClass(label.style) || | ||
(label.title | ||
? ui.style.LABEL_TITLE | ||
: label.small | ||
? ui.style.LABEL_SMALL | ||
: ui.style.LABEL); | ||
applyElementStyle( | ||
element, | ||
[ | ||
label.style, | ||
{ | ||
width: label.width, | ||
bold: label.bold, | ||
italic: label.italic, | ||
textColor: label.color, | ||
opacity: | ||
label.dim === true ? 0.5 : label.dim === false ? 1 : label.dim, | ||
lineBreakMode: label.wrap ? "pre-wrap" : undefined, | ||
userSelect: label.selectable || undefined, | ||
}, | ||
], | ||
label.position, | ||
); | ||
} | ||
updateContent(element: TestOutputElement) { | ||
if (!this.observed) return; | ||
element.text = String(this.observed.text); | ||
@@ -100,0 +85,0 @@ element.icon = String(this.observed.icon || ""); |
@@ -1,6 +0,2 @@ | ||
import { | ||
ManagedEvent, | ||
RenderContext, | ||
UISeparator, | ||
} from "@desk-framework/frame-core"; | ||
import { RenderContext, UISeparator } from "@desk-framework/frame-core"; | ||
import { TestOutputElement } from "../app/TestOutputElement.js"; | ||
@@ -11,27 +7,20 @@ import { TestBaseObserver, applyElementStyle } from "./TestBaseObserver.js"; | ||
export class UISeparatorRenderer extends TestBaseObserver<UISeparator> { | ||
override observe(observed: UISeparator) { | ||
return super | ||
.observe(observed) | ||
.observePropertyAsync("color", "margin", "thickness"); | ||
constructor(observed: UISeparator) { | ||
super(observed); | ||
this.observeProperties("color", "margin", "thickness"); | ||
} | ||
protected override async handlePropertyChange( | ||
property: string, | ||
value: any, | ||
event?: ManagedEvent, | ||
) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "color": | ||
case "margin": | ||
case "thickness": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
protected override propertyChange(property: string, value: any) { | ||
if (!this.element) return; | ||
switch (property) { | ||
case "color": | ||
case "margin": | ||
case "thickness": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
super.propertyChange(property, value); | ||
} | ||
getOutput() { | ||
if (!this.observed) throw ReferenceError(); | ||
let elt = new TestOutputElement("separator"); | ||
@@ -47,16 +36,14 @@ let output = new RenderContext.Output(this.observed, elt); | ||
let sep = this.observed; | ||
if (sep) { | ||
// NOTE: margin is ignored in test renderer | ||
applyElementStyle( | ||
element, | ||
[ | ||
{ | ||
borderColor: sep.color, | ||
borderThickness: sep.thickness, | ||
}, | ||
], | ||
sep.position, | ||
); | ||
} | ||
// NOTE: margin is ignored in test renderer | ||
applyElementStyle( | ||
element, | ||
[ | ||
{ | ||
borderColor: sep.color, | ||
borderThickness: sep.thickness, | ||
}, | ||
], | ||
sep.position, | ||
); | ||
} | ||
} |
@@ -1,6 +0,2 @@ | ||
import { | ||
ManagedEvent, | ||
RenderContext, | ||
UISpacer, | ||
} from "@desk-framework/frame-core"; | ||
import { RenderContext, UISpacer } from "@desk-framework/frame-core"; | ||
import { TestOutputElement } from "../app/TestOutputElement.js"; | ||
@@ -11,28 +7,21 @@ import { TestBaseObserver, applyElementStyle } from "./TestBaseObserver.js"; | ||
export class UISpacerRenderer extends TestBaseObserver<UISpacer> { | ||
override observe(observed: UISpacer) { | ||
return super | ||
.observe(observed) | ||
.observePropertyAsync("width", "height", "minWidth", "minHeight"); | ||
constructor(observed: UISpacer) { | ||
super(observed); | ||
this.observeProperties("width", "height", "minWidth", "minHeight"); | ||
} | ||
protected override async handlePropertyChange( | ||
property: string, | ||
value: any, | ||
event?: ManagedEvent, | ||
) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "width": | ||
case "height": | ||
case "minWidth": | ||
case "minHeight": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
protected override propertyChange(property: string, value: any) { | ||
if (!this.element) return; | ||
switch (property) { | ||
case "width": | ||
case "height": | ||
case "minWidth": | ||
case "minHeight": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
super.propertyChange(property, value); | ||
} | ||
getOutput() { | ||
if (!this.observed) throw ReferenceError(); | ||
let elt = new TestOutputElement("spacer"); | ||
@@ -48,22 +37,20 @@ let output = new RenderContext.Output(this.observed, elt); | ||
let spacer = this.observed; | ||
if (spacer) { | ||
let { width, height, minWidth, minHeight } = spacer; | ||
let hasMinimum = minWidth !== undefined || minHeight !== undefined; | ||
let hasFixed = width !== undefined || height !== undefined; | ||
applyElementStyle( | ||
element, | ||
[ | ||
{ | ||
width, | ||
height, | ||
minWidth, | ||
minHeight, | ||
grow: hasFixed ? 0 : 1, | ||
shrink: hasMinimum ? 0 : 1, | ||
}, | ||
], | ||
spacer.position, | ||
); | ||
} | ||
let { width, height, minWidth, minHeight } = spacer; | ||
let hasMinimum = minWidth !== undefined || minHeight !== undefined; | ||
let hasFixed = width !== undefined || height !== undefined; | ||
applyElementStyle( | ||
element, | ||
[ | ||
{ | ||
width, | ||
height, | ||
minWidth, | ||
minHeight, | ||
grow: hasFixed ? 0 : 1, | ||
shrink: hasMinimum ? 0 : 1, | ||
}, | ||
], | ||
spacer.position, | ||
); | ||
} | ||
} |
@@ -1,7 +0,2 @@ | ||
import { | ||
ManagedEvent, | ||
RenderContext, | ||
UITextField, | ||
ui, | ||
} from "@desk-framework/frame-core"; | ||
import { RenderContext, UITextField, ui } from "@desk-framework/frame-core"; | ||
import { TestOutputElement } from "../app/TestOutputElement.js"; | ||
@@ -16,35 +11,29 @@ import { | ||
export class UITextFieldRenderer extends TestBaseObserver<UITextField> { | ||
override observe(observed: UITextField) { | ||
return super | ||
.observe(observed) | ||
.observePropertyAsync( | ||
"placeholder", | ||
"value", | ||
"disabled", | ||
"readOnly", | ||
"width", | ||
"style", | ||
); | ||
constructor(observed: UITextField) { | ||
super(observed); | ||
this.observeProperties( | ||
"placeholder", | ||
"value", | ||
"disabled", | ||
"readOnly", | ||
"width", | ||
"style", | ||
); | ||
} | ||
protected override async handlePropertyChange( | ||
property: string, | ||
value: any, | ||
event?: ManagedEvent, | ||
) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "placeholder": | ||
case "value": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "disabled": | ||
case "readOnly": | ||
case "width": | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
protected override propertyChange(property: string, value: any) { | ||
if (!this.element) return; | ||
switch (property) { | ||
case "placeholder": | ||
case "value": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "disabled": | ||
case "readOnly": | ||
case "width": | ||
case "style": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
super.propertyChange(property, value); | ||
} | ||
@@ -57,3 +46,3 @@ | ||
// update 'value' first, reflecting the element value | ||
if (this.element && this.observed) { | ||
if (this.element) { | ||
this.observed.value = this.element.value || ""; | ||
@@ -67,3 +56,2 @@ } | ||
// NOTE: ignoring multiline flag here | ||
if (!this.observed) throw ReferenceError(); | ||
let elt = new TestOutputElement("textfield"); | ||
@@ -78,25 +66,22 @@ let output = new RenderContext.Output(this.observed, elt); | ||
let textField = this.observed; | ||
if (textField) { | ||
// set state | ||
element.disabled = textField.disabled; | ||
element.readOnly = textField.readOnly; | ||
// set state | ||
element.disabled = textField.disabled; | ||
element.readOnly = textField.readOnly; | ||
// set styles | ||
element.styleClass = | ||
getBaseStyleClass(textField.style) || ui.style.TEXTFIELD; | ||
applyElementStyle( | ||
element, | ||
[ | ||
textField.style, | ||
textField.width !== undefined | ||
? { width: textField.width, minWidth: 0 } | ||
: undefined, | ||
], | ||
textField.position, | ||
); | ||
} | ||
// set styles | ||
element.styleClass = | ||
getBaseStyleClass(textField.style) || ui.style.TEXTFIELD; | ||
applyElementStyle( | ||
element, | ||
[ | ||
textField.style, | ||
textField.width !== undefined | ||
? { width: textField.width, minWidth: 0 } | ||
: undefined, | ||
], | ||
textField.position, | ||
); | ||
} | ||
updateContent(element: TestOutputElement) { | ||
if (!this.observed) return; | ||
element.text = String(this.observed.placeholder || ""); | ||
@@ -103,0 +88,0 @@ |
@@ -1,7 +0,2 @@ | ||
import { | ||
ManagedEvent, | ||
RenderContext, | ||
UIToggle, | ||
ui, | ||
} from "@desk-framework/frame-core"; | ||
import { RenderContext, UIToggle, ui } from "@desk-framework/frame-core"; | ||
import { TestOutputElement } from "../app/TestOutputElement.js"; | ||
@@ -16,33 +11,21 @@ import { | ||
export class UIToggleRenderer extends TestBaseObserver<UIToggle> { | ||
override observe(observed: UIToggle) { | ||
return super | ||
.observe(observed) | ||
.observePropertyAsync( | ||
"label", | ||
"state", | ||
"disabled", | ||
"style", | ||
"labelStyle", | ||
); | ||
constructor(observed: UIToggle) { | ||
super(observed); | ||
this.observeProperties("label", "state", "disabled", "style", "labelStyle"); | ||
} | ||
protected override async handlePropertyChange( | ||
property: string, | ||
value: any, | ||
event?: ManagedEvent, | ||
) { | ||
if (this.observed && this.element) { | ||
switch (property) { | ||
case "label": | ||
case "state": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "disabled": | ||
case "style": | ||
case "labelStyle": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
protected override propertyChange(property: string, value: any) { | ||
if (!this.element) return; | ||
switch (property) { | ||
case "label": | ||
case "state": | ||
this.scheduleUpdate(this.element); | ||
return; | ||
case "disabled": | ||
case "style": | ||
case "labelStyle": | ||
this.scheduleUpdate(undefined, this.element); | ||
return; | ||
} | ||
await super.handlePropertyChange(property, value, event); | ||
super.propertyChange(property, value); | ||
} | ||
@@ -64,3 +47,2 @@ | ||
getOutput() { | ||
if (!this.observed) throw ReferenceError(); | ||
let elt = new TestOutputElement("toggle"); | ||
@@ -75,14 +57,12 @@ let output = new RenderContext.Output(this.observed, elt); | ||
let toggle = this.observed; | ||
if (toggle) { | ||
// set disabled state | ||
element.disabled = toggle.disabled; | ||
// set styles | ||
element.styleClass = getBaseStyleClass(toggle.style) || ui.style.TOGGLE; | ||
applyElementStyle(element, [toggle.style], toggle.position); | ||
} | ||
// set disabled state | ||
element.disabled = toggle.disabled; | ||
// set styles | ||
element.styleClass = getBaseStyleClass(toggle.style) || ui.style.TOGGLE; | ||
applyElementStyle(element, [toggle.style], toggle.position); | ||
} | ||
updateContent(element: TestOutputElement) { | ||
if (!this.observed) return; | ||
element.text = String(this.observed.label || ""); | ||
@@ -89,0 +69,0 @@ element.checked = !!this.observed.state; |
Sorry, the diff of this file is not supported yet
392288
9820