From 69fd5eea8861671426e6f49bf2e2f36ea4a9bf81 Mon Sep 17 00:00:00 2001 From: maxwell-jordan Date: Mon, 15 Jun 2026 17:57:59 -0700 Subject: [PATCH 1/7] PressTarget --- ui/press-target.mod/press-target.js | 301 +++++++++++++++++++++++++ ui/press-target.mod/press-target.mjson | 109 +++++++++ 2 files changed, 410 insertions(+) create mode 100644 ui/press-target.mod/press-target.js create mode 100644 ui/press-target.mod/press-target.mjson diff --git a/ui/press-target.mod/press-target.js b/ui/press-target.mod/press-target.js new file mode 100644 index 000000000..ce519c9e4 --- /dev/null +++ b/ui/press-target.mod/press-target.js @@ -0,0 +1,301 @@ + /*global require, exports*/ + +/** + * @module mod/ui/press-target + * @requires mod/ui/component + * @requires mod/composer/press-composer + * @requires collections/map + */ +var Component = require("../component").Component, + PressComposer = require("../../composer/press-composer").PressComposer, + Map = require("core/collections/map"); + +/** + * @class PressTarget + * @extends Component + */ +var PressTarget = exports.PressTarget = Component.specialize( /** @lends PressTarget.prototype # */ { + + /** + * Dispatched when the button is activated through a mouse click, finger + * tap, or when focused and the spacebar is pressed. + * + * @event PressTarget#action + * @property {Map} detail - The detail object as defined in {@link AbstractControl#detail} + */ + + /** + * Dispatched when the button is pressed for a period of time, set by + * {@link PressTarget#holdThreshold}. + * + * @event PressTarget#longAction + * @property {Map} detail - The detail object as defined in {@link PressTarget#detail} + */ + + /** + * @private + * @property {Map} value + * @default null + */ + _detail: { + value: null + }, + + /** + * The data property of the action event. + * + * @returns {Map} + */ + detail: { + get: function () { + if (this._detail === null || this._detail === undefined) { + this._detail = new Map(); + } + return this._detail; + } + }, + + /** + * Creates an action event with custom data. + * + * @function + * @returns {CustomEvent} + */ + createActionEvent: { + value: function () { + var actionEvent = document.createEvent("CustomEvent"), + eventDetail; + + eventDetail = this._detail; + actionEvent.initCustomEvent("action", true, true, eventDetail); + return actionEvent; + } + }, + + /** + * @function + * @fires PressTarget#action + */ + dispatchActionEvent: { + value: function () { + return this.dispatchEvent(this.createActionEvent()); + } + }, + + /** + * Convenience property to toggle enabled state. + */ + disabled: { + get: function () { + return !this.enabled; + }, + set: function (value) { + if (typeof value === "boolean") { + this.enabled = !value; + } + } + }, + + /** + * @constructs + */ + constructor: { + value: function PressTarget() { + this._pressComposer = new PressComposer(); + this.addComposer(this._pressComposer); + this._pressComposer.defineBinding("longPressThreshold", {"<-": "holdThreshold", source: this}); + + //classList management + this.defineBinding("classList.has('mod--disabled')", {"<-": "!enabled"}); + this.defineBinding("classList.has('mod--active')", {"<-": "active"}); + } + }, + + /** + * Enables or disables the Button from user input. When this property is + * set to `false`, the "mod--disabled" CSS style is applied to the + * button's DOM element during the next draw cycle. When set to `true` the + * "mod--disabled" CSS class is removed from the element's class + * list. + * @property {boolean} value + */ + enabled: { + value: true + }, + + /** + * @private + */ + _preventFocus: { + value: false + }, + + /** + * Specifies whether the button should receive focus or not. + * + * @property {boolean} + * @default false + */ + preventFocus: { + get: function () { + return this._preventFocus; + }, + set: function (value) { + this._preventFocus = !!value; + this.needsDraw = true; + } + }, + + acceptsActiveTarget: { + value: function () { + return ! this._preventFocus; + } + }, + + willBecomeActiveTarget: { + value: function (previousActiveTarget) { + + } + }, + + + + /** + * The amount of time in milliseconds the user must press and hold the + * button a `longAction` event is dispatched. The default is 1 second. + * @property {number} value + * @default 1000 + */ + holdThreshold: { + value: 1000 + }, + + /** + * @property {PressComposer} value + * @default null + * @private + */ + _pressComposer: { + value: null + }, + + /** + * @private + */ + _active: { + value: false + }, + + /** + * This property is true when the button is being interacted with, either + * through mouse click or touch event, otherwise false. + * + * @property {boolean} + * @default false + */ + active: { + get: function () { + return this._active; + }, + set: function (value) { + this._active = value; + this.needsDraw = true; + } + }, + + prepareForActivationEvents: { + value: function () { + this._pressComposer.addEventListener("pressStart", this, false); + this._pressComposer.addEventListener("press", this, false); + this._pressComposer.addEventListener("pressCancel", this, false); + } + }, + + // Optimisation + addEventListener: { + value: function (type, listener, useCapture) { + Component.prototype.addEventListener.call(this, type, listener, useCapture); + if (type === "longAction") { + this._pressComposer.addEventListener("longPress", this, false); + } + } + }, + + // Handlers + + /** + * Called when the user starts interacting with the component. + * + * @private + */ + handlePressStart: { + value: function (event) { + this.active = true; + + if (event.touch) { + // Prevent default on touchmove so that if we are inside a scroller, + // it scrolls and not the webpage + document.addEventListener("touchmove", this, false); + } + + if (!this._preventFocus) { + this._element.focus(); + } + } + }, + + /** + * Called when the user has interacted with the button. + * + * @private + */ + handlePress: { + value: function (event) { + this.active = false; + this.dispatchActionEvent(); + document.removeEventListener("touchmove", this, false); + } + }, + + handleKeyup: { + value: function (event) { + // action event on spacebar + if (event.keyCode === 32) { + this.active = false; + this.dispatchActionEvent(); + } + } + }, + + handleLongPress: { + value: function (event) { + // When we fire the "longAction" event we don't want to fire the + // "action" event as well. + this._pressComposer.cancelPress(); + + var longActionEvent = document.createEvent("CustomEvent"); + longActionEvent.initCustomEvent("longAction", true, true, null); + this.dispatchEvent(longActionEvent); + } + }, + + /** + * Called when all interaction is over. + * @private + */ + handlePressCancel: { + value: function (event) { + this.active = false; + document.removeEventListener("touchmove", this, false); + } + }, + + /** + * @private + */ + handleTouchmove: { + value: function (event) { + event.preventDefault(); + } + } +}); diff --git a/ui/press-target.mod/press-target.mjson b/ui/press-target.mod/press-target.mjson new file mode 100644 index 000000000..eae504651 --- /dev/null +++ b/ui/press-target.mod/press-target.mjson @@ -0,0 +1,109 @@ +{ + "objectDescriptor_pressTarget_enabled": { + "prototype": "core/meta/property-descriptor", + "values": { + "name": "enabled", + "objectDescriptor": { + "@": "root" + }, + "valueType": "boolean" + } + }, + "objectDescriptor_pressTarget_preventFocus": { + "prototype": "core/meta/property-descriptor", + "values": { + "name": "preventFocus", + "objectDescriptor": { + "@": "root" + }, + "valueType": "boolean" + } + }, + "objectDescriptor_pressTarget_holdThreshold": { + "prototype": "core/meta/property-descriptor", + "values": { + "name": "holdThreshold", + "objectDescriptor": { + "@": "root" + }, + "valueType": "number" + } + }, + "objectDescriptor_pressTarget_active": { + "prototype": "core/meta/property-descriptor", + "values": { + "name": "active", + "objectDescriptor": { + "@": "root" + }, + "valueType": "boolean" + } + }, + "objectDescriptor_pressTarget_detail": { + "prototype": "core/meta/property-descriptor", + "values": { + "name": "detail", + "objectDescriptor": { + "@": "root" + }, + "valueType": "object", + "readOnly": true, + "collectionValueType": "dict" + } + }, + "objectDescriptor_component_reference": { + "object": "ui/component.mjson" + }, + "root": { + "prototype": "core/meta/module-object-descriptor", + "values": { + "name": "PressTarget", + "parent": { + "@": "objectDescriptor_component_reference" + }, + "propertyDescriptors": [ + { + "@": "objectDescriptor_pressTarget_enabled" + }, + { + "@": "objectDescriptor_pressTarget_preventFocus" + }, + { + "@": "objectDescriptor_pressTarget_holdThreshold" + }, + { + "@": "objectDescriptor_pressTarget_active" + }, + { + "@": "objectDescriptor_pressTarget_detail" + } + ], + "propertyDescriptorGroups": { + "PressTarget": [ + { + "@": "objectDescriptor_pressTarget_enabled" + }, + { + "@": "objectDescriptor_pressTarget_preventFocus" + }, + { + "@": "objectDescriptor_pressTarget_holdThreshold" + }, + { + "@": "objectDescriptor_pressTarget_active" + }, + { + "@": "objectDescriptor_pressTarget_detail" + } + ] + }, + "objectDescriptorModule": { + "%": "ui/press-target.mod/press-target.mjson" + }, + "exportName": "PressTarget", + "module": { + "%": "ui/press-target.mod" + } + } + } +} From a9611de371bb1019c03ed0a2186c074a0f7fa32e Mon Sep 17 00:00:00 2001 From: maxwell-jordan Date: Thu, 18 Jun 2026 10:25:56 -0700 Subject: [PATCH 2/7] ActionTarget --- .../action-target.js} | 32 ++++----------- .../action-target.mjson} | 40 +++++++++---------- ui/button.mod/button.js | 3 +- 3 files changed, 29 insertions(+), 46 deletions(-) rename ui/{press-target.mod/press-target.js => action-target.mod/action-target.js} (86%) rename ui/{press-target.mod/press-target.mjson => action-target.mod/action-target.mjson} (65%) diff --git a/ui/press-target.mod/press-target.js b/ui/action-target.mod/action-target.js similarity index 86% rename from ui/press-target.mod/press-target.js rename to ui/action-target.mod/action-target.js index ce519c9e4..11e0d921b 100644 --- a/ui/press-target.mod/press-target.js +++ b/ui/action-target.mod/action-target.js @@ -1,36 +1,21 @@ /*global require, exports*/ /** - * @module mod/ui/press-target + * @module mod/ui/action-target * @requires mod/ui/component * @requires mod/composer/press-composer * @requires collections/map */ +const { Control } = require("ui/control"); var Component = require("../component").Component, PressComposer = require("../../composer/press-composer").PressComposer, Map = require("core/collections/map"); /** - * @class PressTarget - * @extends Component + * @class ActionTarget + * @extends Control */ -var PressTarget = exports.PressTarget = Component.specialize( /** @lends PressTarget.prototype # */ { - - /** - * Dispatched when the button is activated through a mouse click, finger - * tap, or when focused and the spacebar is pressed. - * - * @event PressTarget#action - * @property {Map} detail - The detail object as defined in {@link AbstractControl#detail} - */ - - /** - * Dispatched when the button is pressed for a period of time, set by - * {@link PressTarget#holdThreshold}. - * - * @event PressTarget#longAction - * @property {Map} detail - The detail object as defined in {@link PressTarget#detail} - */ +var ActionTarget = exports.ActionTarget = Control.specialize( /** @lends ActionTarget.prototype # */ { /** * @private @@ -74,7 +59,7 @@ var PressTarget = exports.PressTarget = Component.specialize( /** @lends PressTa /** * @function - * @fires PressTarget#action + * @fires ActionTarget#action */ dispatchActionEvent: { value: function () { @@ -100,14 +85,11 @@ var PressTarget = exports.PressTarget = Component.specialize( /** @lends PressTa * @constructs */ constructor: { - value: function PressTarget() { + value: function ActionTarget() { this._pressComposer = new PressComposer(); this.addComposer(this._pressComposer); this._pressComposer.defineBinding("longPressThreshold", {"<-": "holdThreshold", source: this}); - //classList management - this.defineBinding("classList.has('mod--disabled')", {"<-": "!enabled"}); - this.defineBinding("classList.has('mod--active')", {"<-": "active"}); } }, diff --git a/ui/press-target.mod/press-target.mjson b/ui/action-target.mod/action-target.mjson similarity index 65% rename from ui/press-target.mod/press-target.mjson rename to ui/action-target.mod/action-target.mjson index eae504651..b4c52fffe 100644 --- a/ui/press-target.mod/press-target.mjson +++ b/ui/action-target.mod/action-target.mjson @@ -1,5 +1,5 @@ { - "objectDescriptor_pressTarget_enabled": { + "objectDescriptor_actionTarget_enabled": { "prototype": "core/meta/property-descriptor", "values": { "name": "enabled", @@ -9,7 +9,7 @@ "valueType": "boolean" } }, - "objectDescriptor_pressTarget_preventFocus": { + "objectDescriptor_actionTarget_preventFocus": { "prototype": "core/meta/property-descriptor", "values": { "name": "preventFocus", @@ -19,7 +19,7 @@ "valueType": "boolean" } }, - "objectDescriptor_pressTarget_holdThreshold": { + "objectDescriptor_actionTarget_holdThreshold": { "prototype": "core/meta/property-descriptor", "values": { "name": "holdThreshold", @@ -29,7 +29,7 @@ "valueType": "number" } }, - "objectDescriptor_pressTarget_active": { + "objectDescriptor_actionTarget_active": { "prototype": "core/meta/property-descriptor", "values": { "name": "active", @@ -39,7 +39,7 @@ "valueType": "boolean" } }, - "objectDescriptor_pressTarget_detail": { + "objectDescriptor_actionTarget_detail": { "prototype": "core/meta/property-descriptor", "values": { "name": "detail", @@ -57,52 +57,52 @@ "root": { "prototype": "core/meta/module-object-descriptor", "values": { - "name": "PressTarget", + "name": "ActionTarget", "parent": { "@": "objectDescriptor_component_reference" }, "propertyDescriptors": [ { - "@": "objectDescriptor_pressTarget_enabled" + "@": "objectDescriptor_actionTarget_enabled" }, { - "@": "objectDescriptor_pressTarget_preventFocus" + "@": "objectDescriptor_actionTarget_preventFocus" }, { - "@": "objectDescriptor_pressTarget_holdThreshold" + "@": "objectDescriptor_actionTarget_holdThreshold" }, { - "@": "objectDescriptor_pressTarget_active" + "@": "objectDescriptor_actionTarget_active" }, { - "@": "objectDescriptor_pressTarget_detail" + "@": "objectDescriptor_actionTarget_detail" } ], "propertyDescriptorGroups": { - "PressTarget": [ + "ActionTarget": [ { - "@": "objectDescriptor_pressTarget_enabled" + "@": "objectDescriptor_actionTarget_enabled" }, { - "@": "objectDescriptor_pressTarget_preventFocus" + "@": "objectDescriptor_actionTarget_preventFocus" }, { - "@": "objectDescriptor_pressTarget_holdThreshold" + "@": "objectDescriptor_actionTarget_holdThreshold" }, { - "@": "objectDescriptor_pressTarget_active" + "@": "objectDescriptor_actionTarget_active" }, { - "@": "objectDescriptor_pressTarget_detail" + "@": "objectDescriptor_actionTarget_detail" } ] }, "objectDescriptorModule": { - "%": "ui/press-target.mod/press-target.mjson" + "%": "ui/action-target.mod/action-target.mjson" }, - "exportName": "PressTarget", + "exportName": "ActionTarget", "module": { - "%": "ui/press-target.mod" + "%": "ui/action-target.mod" } } } diff --git a/ui/button.mod/button.js b/ui/button.mod/button.js index 5f7a01549..10aa06895 100644 --- a/ui/button.mod/button.js +++ b/ui/button.mod/button.js @@ -6,6 +6,7 @@ const { PressComposer } = require("composer/press-composer"); const { KeyComposer } = require("composer/key-composer"); const { Control } = require("ui/control"); const { Montage } = require("core/core"); +const { ActionTarget } = require("ui/action-target.mod/action-target"); // TODO: migrate away from using undefinedGet and undefinedSet @@ -46,7 +47,7 @@ const { Montage } = require("core/core"); * } * */ -const Button = (exports.Button = class Button extends Control { +const Button = (exports.Button = class Button extends ActionTarget { /** @lends module:"mod/ui/native/button.mod".Button# */ // <---- Static ----> From ecda46fdaac2c7f6abdffcea496d456047d36e53 Mon Sep 17 00:00:00 2001 From: maxwell-jordan Date: Thu, 18 Jun 2026 17:11:50 -0700 Subject: [PATCH 3/7] parent update --- ui/button.mod/button.mjson | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/button.mod/button.mjson b/ui/button.mod/button.mjson index 4f61169e0..85ad3e06c 100644 --- a/ui/button.mod/button.mjson +++ b/ui/button.mod/button.mjson @@ -141,8 +141,8 @@ "helpKey": "" } }, - "objectDescriptor_control_reference": { - "object": "ui/control.mjson" + "objectDescriptor_action_target_reference": { + "object": "ui/action-target.mod/action-target.mjson" }, "root": { "prototype": "core/meta/module-object-descriptor", @@ -150,7 +150,7 @@ "name": "button", "customPrototype": false, "parent": { - "@": "objectDescriptor_control_reference" + "@": "objectDescriptor_action_target_reference" }, "propertyDescriptors": [ { From cdcacf6398dcd8d98060d84ed0b745155fcf566f Mon Sep 17 00:00:00 2001 From: maxwell-jordan Date: Fri, 26 Jun 2026 12:19:46 -0700 Subject: [PATCH 4/7] reduce redundancies with super --- ui/button.mod/button.js | 71 ++++++----------------------------------- 1 file changed, 9 insertions(+), 62 deletions(-) diff --git a/ui/button.mod/button.js b/ui/button.mod/button.js index 10aa06895..4c77e221c 100644 --- a/ui/button.mod/button.js +++ b/ui/button.mod/button.js @@ -4,7 +4,6 @@ const { VisualOrientation } = require("core/enums/visual-orientation"); const { VisualPosition } = require("core/enums/visual-position"); const { PressComposer } = require("composer/press-composer"); const { KeyComposer } = require("composer/key-composer"); -const { Control } = require("ui/control"); const { Montage } = require("core/core"); const { ActionTarget } = require("ui/action-target.mod/action-target"); @@ -14,7 +13,7 @@ const { ActionTarget } = require("ui/action-target.mod/action-target"); * Wraps a native <button> or <input[type="button"]> HTML element. * The element's standard attributes are exposed as bindable properties. * @class module:"mod/ui/native/button.mod".Button - * @extends module:mod/ui/control.Control + * @extends module:mod/ui/action-target.mod/action-target * @fires action * @fires hold * @@ -257,11 +256,11 @@ const Button = (exports.Button = class Button extends ActionTarget { * @default 1000 */ get holdThreshold() { - return this._pressComposer.longPressThreshold; + return super.holdThreshold; } set holdThreshold(value) { - this._pressComposer.longPressThreshold = value; + super.holdThreshold = value; } __pressComposer = null; @@ -301,27 +300,11 @@ const Button = (exports.Button = class Button extends ActionTarget { * @protected */ prepareForActivationEvents() { - this._pressComposer.addEventListener("pressStart", this, false); + super.prepareForActivationEvents(); this._spaceKeyComposer.addEventListener("keyPress", this, false); this._enterKeyComposer.addEventListener("keyPress", this, false); } - /** - * Override addEventListener for optimization - * @override - * @protected - * @param {String} type - The event type - * @param {Function} listener - The event listener - * @param {boolean} useCapture - The useCapture flag - */ - addEventListener(type, listener, useCapture) { - super.addEventListener(type, listener, useCapture); - - if (type === "longAction") { - this._pressComposer.addEventListener("longPress", this, false); - } - } - // <---- Event Handlers ----> /** @@ -346,8 +329,7 @@ const Button = (exports.Button = class Button extends ActionTarget { */ handlePressStart(mutableEvent) { if (!this._promise) { - this.active = true; - this._addEventListeners(); + super.handlePressStart(mutableEvent); } } @@ -359,9 +341,7 @@ const Button = (exports.Button = class Button extends ActionTarget { */ handlePress(mutableEvent) { if (!this._promise) { - this.active = false; - this.dispatchActionEvent(event.details); - this._removeEventListeners(); + super.handlePress(mutableEvent); } } @@ -373,16 +353,7 @@ const Button = (exports.Button = class Button extends ActionTarget { */ handleLongPress(mutableEvent) { if (!this._promise) { - // When we fire the "hold" event we don't want to fire the - // "action" event as well. - this._pressComposer.cancelPress(); - this._removeEventListeners(); - - const longActionEvent = document.createEvent("CustomEvent"); - - // FIXME: InitCustomEvent is deprecated - longActionEvent.initCustomEvent("longAction", true, true, null); - this.dispatchEvent(longActionEvent); + super.handleLongPress(mutableEvent); } } @@ -392,8 +363,7 @@ const Button = (exports.Button = class Button extends ActionTarget { * @param {MutableEvent} mutableEvent - The event object */ handlePressCancel(mutableEvent) { - this.active = false; - this._removeEventListeners(); + super.handlePressCancel(mutableEvent); } // <---- Life Cycle ----> @@ -436,30 +406,7 @@ const Button = (exports.Button = class Button extends ActionTarget { // <---- Private ----> - /** - * Adds event listeners to the button. - * @private - */ - _addEventListeners() { - this._pressComposer.addEventListener("press", this, false); - this._pressComposer.addEventListener("pressCancel", this, false); - - // FIXME: @benoit: we should maybe have a flag for this kind of event. - // can be tricky with the event delegation for example if we don't add it. - // same issue for: the pressComposer and the translate composer. - this._pressComposer.addEventListener("longPress", this, false); - } - - /** - * Removes event listeners from the button. - * @private - */ - _removeEventListeners() { - this._pressComposer.removeEventListener("press", this, false); - this._pressComposer.removeEventListener("pressCancel", this, false); - this._pressComposer.removeEventListener("longPress", this, false); - } - + /** * Applies the current image position's styling by updating CSS classes * @private From fd5a2a1d30eadf1e49fd72a8a8869b7a714c4b51 Mon Sep 17 00:00:00 2001 From: maxwell-jordan Date: Fri, 26 Jun 2026 15:32:34 -0700 Subject: [PATCH 5/7] migrated functionality upward --- ui/action-target.mod/action-target.js | 156 +++++++++++++++++- ui/button.mod/button.js | 222 -------------------------- 2 files changed, 153 insertions(+), 225 deletions(-) diff --git a/ui/action-target.mod/action-target.js b/ui/action-target.mod/action-target.js index 11e0d921b..7823d047f 100644 --- a/ui/action-target.mod/action-target.js +++ b/ui/action-target.mod/action-target.js @@ -8,6 +8,7 @@ */ const { Control } = require("ui/control"); var Component = require("../component").Component, + KeyComposer = require("../../composer/key-composer").KeyComposer, PressComposer = require("../../composer/press-composer").PressComposer, Map = require("core/collections/map"); @@ -81,15 +82,98 @@ var ActionTarget = exports.ActionTarget = Control.specialize( /** @lends ActionT } }, + debounceOptions: { + value: { + leading: true, + trailing: false + } + }, + + _debounceThreshold: { + value: 300 + }, + + _debounced: { + value: false + }, + + debounceThreshold: { + get: function () { + return this._debounceThreshold; + }, + set: function (value) { + this._debounceThreshold = Number(value); + + if (this._debounced) { + this.dispatchActionEvent = this.debounce( + this.dispatchActionEvent.bind(this), + this._debounceThreshold, + this.debounceOptions + ); + } + } + }, + + debounced: { + get: function () { + return this._debounced; + }, + set: function (value) { + this._debounced = Boolean(value); + + if (this._debounced) { + this.dispatchActionEvent = this.debounce( + this.dispatchActionEvent.bind(this), + this._debounceThreshold, + this.debounceOptions + ); + } else { + this.dispatchActionEvent = this.constructor.prototype.dispatchActionEvent; + } + } + }, + + _promise: { + value: undefined + }, + + promise: { + get: function () { + return this._promise; + }, + set: function (promise) { + var shouldClearPendingState, + currentTrackedPromise; + + if (this._promise === promise) { + return; + } + + shouldClearPendingState = !!this._promise; + this._promise = promise; + + if (promise) { + this.classList.add("mod--pending"); + currentTrackedPromise = promise; + + promise.finally(function () { + if (this._promise === currentTrackedPromise) { + this.classList.remove("mod--pending"); + this._promise = undefined; + } + }.bind(this)); + } else if (shouldClearPendingState) { + this.classList.remove("mod--pending"); + } + } + }, + /** * @constructs */ constructor: { value: function ActionTarget() { - this._pressComposer = new PressComposer(); - this.addComposer(this._pressComposer); this._pressComposer.defineBinding("longPressThreshold", {"<-": "holdThreshold", source: this}); - } }, @@ -157,10 +241,49 @@ var ActionTarget = exports.ActionTarget = Control.specialize( /** @lends ActionT * @default null * @private */ + __pressComposer: { + value: null + }, + _pressComposer: { + get: function () { + if (!this.__pressComposer) { + this.__pressComposer = new PressComposer(); + this.addComposer(this.__pressComposer); + } + + return this.__pressComposer; + } + }, + + __spaceKeyComposer: { + value: null + }, + + _spaceKeyComposer: { + get: function () { + if (!this.__spaceKeyComposer) { + this.__spaceKeyComposer = KeyComposer.createKey(this, "space", "space"); + } + + return this.__spaceKeyComposer; + } + }, + + __enterKeyComposer: { value: null }, + _enterKeyComposer: { + get: function () { + if (!this.__enterKeyComposer) { + this.__enterKeyComposer = KeyComposer.createKey(this, "enter", "enter"); + } + + return this.__enterKeyComposer; + } + }, + /** * @private */ @@ -190,6 +313,8 @@ var ActionTarget = exports.ActionTarget = Control.specialize( /** @lends ActionT this._pressComposer.addEventListener("pressStart", this, false); this._pressComposer.addEventListener("press", this, false); this._pressComposer.addEventListener("pressCancel", this, false); + this._spaceKeyComposer.addEventListener("keyPress", this, false); + this._enterKeyComposer.addEventListener("keyPress", this, false); } }, @@ -212,6 +337,10 @@ var ActionTarget = exports.ActionTarget = Control.specialize( /** @lends ActionT */ handlePressStart: { value: function (event) { + if (this._promise) { + return; + } + this.active = true; if (event.touch) { @@ -233,12 +362,29 @@ var ActionTarget = exports.ActionTarget = Control.specialize( /** @lends ActionT */ handlePress: { value: function (event) { + if (this._promise) { + return; + } + this.active = false; this.dispatchActionEvent(); document.removeEventListener("touchmove", this, false); } }, + handleKeyPress: { + value: function (event) { + if (this._promise) { + return; + } + + if (event.identifier === "space" || event.identifier === "enter") { + this.active = false; + this.dispatchActionEvent(); + } + } + }, + handleKeyup: { value: function (event) { // action event on spacebar @@ -251,6 +397,10 @@ var ActionTarget = exports.ActionTarget = Control.specialize( /** @lends ActionT handleLongPress: { value: function (event) { + if (this._promise) { + return; + } + // When we fire the "longAction" event we don't want to fire the // "action" event as well. this._pressComposer.cancelPress(); diff --git a/ui/button.mod/button.js b/ui/button.mod/button.js index 4c77e221c..997f2a3de 100644 --- a/ui/button.mod/button.js +++ b/ui/button.mod/button.js @@ -2,9 +2,6 @@ const { VisualOrientation } = require("core/enums/visual-orientation"); const { VisualPosition } = require("core/enums/visual-position"); -const { PressComposer } = require("composer/press-composer"); -const { KeyComposer } = require("composer/key-composer"); -const { Montage } = require("core/core"); const { ActionTarget } = require("ui/action-target.mod/action-target"); // TODO: migrate away from using undefinedGet and undefinedSet @@ -57,72 +54,6 @@ const Button = (exports.Button = class Button extends ActionTarget { // <---- Properties ----> - static { - /** - * Options for debouncing the action event. - * Immediately invokes the function and then ignores any calls made - * within the threshold period. - * @type {Object} - * @default { leading: true, trailing: false } - */ - Montage.defineProperties(this.prototype, { - debounceOptions: { - value: { - leading: true, - trailing: false, - }, - }, - - _debounceThreshold: { value: 300 }, // milliseconds - - _debounced: { value: false }, - }); - } - - get debounceThreshold() { - return this._debounceThreshold; - } - - /** - * The debounce threshold in milliseconds. - * @type {number} - * @default 300 - */ - set debounceThreshold(value) { - this._debounceThreshold = Number(value); - - if (this._debounced) { - this.dispatchActionEvent = this.debounce( - this.dispatchActionEvent.bind(this), - this._debounceThreshold, - this.debounceOptions - ); - } - } - - get debounced() { - return this._debounced; - } - - /** - * Indicates whether the action event is debounced. - * @type {boolean} - * @default false - */ - set debounced(value) { - this._debounced = Boolean(value); - - if (this._debounced) { - this.dispatchActionEvent = this.debounce( - this.dispatchActionEvent.bind(this), - this._debounceThreshold, - this.debounceOptions - ); - } else { - this.dispatchActionEvent = Button.prototype.dispatchActionEvent; - } - } - _visualPosition = VisualPosition.start; get visualPosition() { @@ -213,159 +144,6 @@ const Button = (exports.Button = class Button extends ActionTarget { } } - _promise = undefined; - - get promise() { - return this._promise; - } - - set promise(promise) { - // Only proceed if the new promise is different from the current one - if (this._promise === promise) return; - - const shouldClearPendingState = !!this._promise; - this._promise = promise; - - if (promise) { - // Set up pending state when promise is set - this.classList.add("mod--pending"); - - // Store reference to track this specific promise - const currentTrackedPromise = promise; - - // Clear state when promise resolves/rejects - // TODO: we should propably add an error state?... - promise.finally(() => { - // Only clear if this is still the active promise - if (this._promise === currentTrackedPromise) { - this.classList.remove("mod--pending"); - this._promise = undefined; - } - }); - } else if (shouldClearPendingState) { - // Clear pending state when the current promise is set to null - this.classList.remove("mod--pending"); - } - } - - /** - * The amount of time in milliseconds the user must press and hold - * the button a hold event is dispatched. - * The default is 1 second. - * @type {number} - * @default 1000 - */ - get holdThreshold() { - return super.holdThreshold; - } - - set holdThreshold(value) { - super.holdThreshold = value; - } - - __pressComposer = null; - - get _pressComposer() { - if (!this.__pressComposer) { - this.__pressComposer = new PressComposer(); - this.addComposer(this.__pressComposer); - } - - return this.__pressComposer; - } - - __spaceKeyComposer = null; - - get _spaceKeyComposer() { - if (!this.__spaceKeyComposer) { - this.__spaceKeyComposer = KeyComposer.createKey(this, "space", "space"); - } - - return this.__spaceKeyComposer; - } - - __enterKeyComposer = null; - - get _enterKeyComposer() { - if (!this.__enterKeyComposer) { - this.__enterKeyComposer = KeyComposer.createKey(this, "enter", "enter"); - } - - return this.__enterKeyComposer; - } - - /** - * Prepares the component for activation events. - * @override - * @protected - */ - prepareForActivationEvents() { - super.prepareForActivationEvents(); - this._spaceKeyComposer.addEventListener("keyPress", this, false); - this._enterKeyComposer.addEventListener("keyPress", this, false); - } - - // <---- Event Handlers ----> - - /** - * Dispatching the action event on spacebar & enter when the button is focused. - * @param {MutableEvent} mutableEvent - The event object - * @protected - * @fires action - */ - handleKeyPress(mutableEvent) { - const { identifier } = mutableEvent; - - if (identifier === "space" || identifier === "enter") { - this.active = false; - this.dispatchActionEvent(); - } - } - - /** - * Called when the user starts interacting with the component. - * @protected - * @param {MutableEvent} mutableEvent - The event object - */ - handlePressStart(mutableEvent) { - if (!this._promise) { - super.handlePressStart(mutableEvent); - } - } - - /** - * Called when the user has interacted with the button. - * @protected - * @param {MutableEvent} mutableEvent - The event object - * @fires action - */ - handlePress(mutableEvent) { - if (!this._promise) { - super.handlePress(mutableEvent); - } - } - - /** - * Called when the user has interacted with the button for a long time. - * @protected - * @param {MutableEvent} mutableEvent - The event object - * @fires longAction - */ - handleLongPress(mutableEvent) { - if (!this._promise) { - super.handleLongPress(mutableEvent); - } - } - - /** - * Called when all interaction is over. - * @protected - * @param {MutableEvent} mutableEvent - The event object - */ - handlePressCancel(mutableEvent) { - super.handlePressCancel(mutableEvent); - } - // <---- Life Cycle ----> enterDocument(firstDraw) { From b37e59673ea6ffef29d1dcede792c816dc6b72ad Mon Sep 17 00:00:00 2001 From: maxwell-jordan Date: Tue, 7 Jul 2026 18:04:32 -0700 Subject: [PATCH 6/7] action-target-spec --- test/spec/ui/action-target-spec.js | 1 + test/spec/ui/button-spec.js | 155 --------------- .../spec/action-target-spec.js | 185 ++++++++++++++++++ 3 files changed, 186 insertions(+), 155 deletions(-) create mode 100644 test/spec/ui/action-target-spec.js create mode 100644 ui/action-target.mod/spec/action-target-spec.js diff --git a/test/spec/ui/action-target-spec.js b/test/spec/ui/action-target-spec.js new file mode 100644 index 000000000..cfbc40cee --- /dev/null +++ b/test/spec/ui/action-target-spec.js @@ -0,0 +1 @@ +require('mod/ui/action-target.mod/spec/action-target-spec'); \ No newline at end of file diff --git a/test/spec/ui/button-spec.js b/test/spec/ui/button-spec.js index 0635b22aa..e9c0fb248 100644 --- a/test/spec/ui/button-spec.js +++ b/test/spec/ui/button-spec.js @@ -52,161 +52,6 @@ describe("test/ui/button-spec", function () { expect(aButton.needsDraw).toBeTruthy(); }); }); - describe("disabled property", function () { - var aButton; - beforeEach(function () { - aButton = new Button(); - aButton.element = MockDOM.element(); - aButton.originalElement = aButton.element; - }); - it("should be enabled after the corresponding property change", function () { - aButton.enabled = true; - aButton.enterDocument(true); - aButton._draw(); - aButton.draw(); - expect(aButton.element.disabled).toBeFalsy(); - }); - it("should be disabled after the corresponding property change", function () { - aButton.enabled = false; - aButton.enterDocument(true); - aButton._draw(); - aButton.draw(); - expect(aButton.element.disabled).toBeTruthy(); - }); - }); - describe("active target", function () { - var aButton, anElement; - beforeEach(function () { - aButton = new Button(); - anElement = MockDOM.element(); - }); - it("should set tabindex if needed", function () { - anElement.tagName = "DIV"; - spyOn(anElement, "setAttribute"); - spyOn(anElement, "removeAttribute"); - - aButton.element = anElement; - aButton._draw(); - aButton.draw(); - expect(anElement.setAttribute).toHaveBeenCalledWith("tabindex", "0"); - aButton.preventFocus = true; - aButton._draw(); - aButton.draw(); - expect(anElement.removeAttribute).toHaveBeenCalledWith("tabindex"); - }); - it("shouldn't set tabindex if not needed", function () { - anElement.tagName = "BUTTON"; - spyOn(anElement, "setAttribute"); - - aButton.element = anElement; - aButton._draw(); - aButton.draw(); - expect(anElement.setAttribute).not.toHaveBeenCalledWith("tabindex", "-1"); - }); - }); - - describe("converter", function () { - beforeEach(function () { - aButton = new Button(); - aButton.element = MockDOM.element(); - aButton.originalElement = aButton.element; - aButton.element.firstChild = MockDOM.element(); - aButton.converter = { - convert: function (v) { - return v.replace(/fail/gi, "pass"); - } - }; - }); - it("shouldn't go into infinite loop", function () { - aButton.label = "fail"; - aButton.enterDocument(true); - aButton.draw(); - aButton.label = "FAIL"; - expect(aButton.label).toEqual("pass"); - }); - }); - - describe("events", function () { - var aButton, anElement, listener; - beforeEach(function () { - aButton = new Button(); - anElement = MockDOM.element(); - listener = { - handleEvent: function () {} - } - }); - it("should listen for pressStart only after prepareForActivationEvents", function () { - var listeners, - em = aButton.eventManager; - - listeners = em.registeredEventListenersForEventType_onTarget_("pressStart", aButton._pressComposer); - expect(listeners).toBeNull(); - - aButton.prepareForActivationEvents(); - - listeners = em.registeredEventListenersForEventType_onTarget_("pressStart", aButton._pressComposer); - expect(listeners).toEqual(aButton); - }); - it("should listen for longPress on PressComposer on demand", function () { - var listeners, - em = aButton.eventManager; - - listeners = em.registeredEventListenersForEventType_onTarget_("longPress", aButton._pressComposer); - expect(listeners).toBeNull(); - - aButton.addEventListener("longAction", listener, false); - - listeners = em.registeredEventListenersForEventType_onTarget_("longPress", aButton._pressComposer); - expect(listeners).toEqual(aButton); - }); - it("should fires a 'longAction' event when the PressComposer fires a longPress", function () { - var callback = jasmine.createSpy(); - aButton.addEventListener("longAction", callback, false); - aButton._pressComposer.dispatchEventNamed("longPress"); - expect(callback).toHaveBeenCalled(); - }); - describe("once prepareForActivationEvents is called", function () { - beforeEach(function () { - aButton.element = anElement; - aButton.prepareForActivationEvents(); - }); - it("should fire an 'action' event when the PressComposer fires a pressStart + press", function () { - var callback = jasmine.createSpy().and.callFake(function (event) { - expect(event.type).toEqual("action"); - }); - aButton.addEventListener("action", callback, false); - - aButton._pressComposer.dispatchEventNamed("pressStart"); - aButton._pressComposer.dispatchEventNamed("press"); - - expect(callback).toHaveBeenCalled(); - }); - it("shouldn't fire an 'action' event when the PressComposer fires a pressStart + pressCancel", function () { - var callback = jasmine.createSpy().and.callFake(function (event) { - expect(event.type).toEqual("action"); - }); - aButton.addEventListener("action", callback, false); - - aButton._pressComposer.dispatchEventNamed("pressStart"); - aButton._pressComposer.dispatchEventNamed("pressCancel"); - - expect(callback).not.toHaveBeenCalled(); - }); - it("should fire an 'action' event with the contents of the detail property", function () { - var callback = jasmine.createSpy().and.callFake(function (event) { - expect(event.detail.get("foo")).toEqual("bar"); - }); - aButton.addEventListener("action", callback, false); - - aButton.detail.set("foo", "bar"); - - aButton._pressComposer.dispatchEventNamed("pressStart"); - aButton._pressComposer.dispatchEventNamed("press"); - - expect(callback).toHaveBeenCalled(); - }); - }); - }); }); describe("objectDescriptor", function () { it("can be created", function (done) { diff --git a/ui/action-target.mod/spec/action-target-spec.js b/ui/action-target.mod/spec/action-target-spec.js new file mode 100644 index 000000000..9b58ebbbf --- /dev/null +++ b/ui/action-target.mod/spec/action-target-spec.js @@ -0,0 +1,185 @@ +var Montage = require("mod/core/core").Montage; +var ActionTarget = require("mod/ui/action-target.mod").ActionTarget; +var MockDOM = require("mocks/dom"); + +describe("test/ui/action-target-spec", function () { + describe("properties", function () { + var anActionTarget; + beforeEach(function () { + anActionTarget = new ActionTarget(); + anActionTarget.element = MockDOM.element(); + anActionTarget.prepareForActivationEvents(); + }); + it("should keep the press composer's longPressThreshold in sync with holdThreshold", function () { + anActionTarget.holdThreshold = 10; + expect(anActionTarget._pressComposer.longPressThreshold).toEqual(10); + }); + describe("disabled property", function () { + var anActionTarget; + beforeEach(function () { + anActionTarget = new Button(); + anActionTarget.element = MockDOM.element(); + anActionTarget.originalElement = anActionTarget.element; + }); + it("should be enabled after the corresponding property change", function () { + anActionTarget.enabled = true; + anActionTarget.enterDocument(true); + anActionTarget._draw(); + anActionTarget.draw(); + expect(anActionTarget.element.disabled).toBeFalsy(); + }); + it("should be disabled after the corresponding property change", function () { + anActionTarget.enabled = false; + anActionTarget.enterDocument(true); + anActionTarget._draw(); + anActionTarget.draw(); + expect(anActionTarget.element.disabled).toBeTruthy(); + }); + }); + describe("active target", function () { + var anActionTarget, anElement; + beforeEach(function () { + anActionTarget = new Button(); + anElement = MockDOM.element(); + }); + it("should set tabindex if needed", function () { + anElement.tagName = "DIV"; + spyOn(anElement, "setAttribute"); + spyOn(anElement, "removeAttribute"); + + anActionTarget.element = anElement; + anActionTarget._draw(); + anActionTarget.draw(); + expect(anElement.setAttribute).toHaveBeenCalledWith("tabindex", "0"); + anActionTarget.preventFocus = true; + anActionTarget._draw(); + anActionTarget.draw(); + expect(anElement.removeAttribute).toHaveBeenCalledWith("tabindex"); + }); + it("shouldn't set tabindex if not needed", function () { + anElement.tagName = "BUTTON"; + spyOn(anElement, "setAttribute"); + + anActionTarget.element = anElement; + anActionTarget._draw(); + anActionTarget.draw(); + expect(anElement.setAttribute).not.toHaveBeenCalledWith("tabindex", "-1"); + }); + }); + + describe("converter", function () { + beforeEach(function () { + anActionTarget = new Button(); + anActionTarget.element = MockDOM.element(); + anActionTarget.originalElement = anActionTarget.element; + anActionTarget.element.firstChild = MockDOM.element(); + anActionTarget.converter = { + convert: function (v) { + return v.replace(/fail/gi, "pass"); + } + }; + }); + it("shouldn't go into infinite loop", function () { + anActionTarget.label = "fail"; + anActionTarget.enterDocument(true); + anActionTarget.draw(); + anActionTarget.label = "FAIL"; + expect(anActionTarget.label).toEqual("pass"); + }); + }); + + describe("events", function () { + var anActionTarget, anElement, listener; + beforeEach(function () { + anActionTarget = new Button(); + anElement = MockDOM.element(); + listener = { + handleEvent: function () {} + } + }); + it("should listen for pressStart only after prepareForActivationEvents", function () { + var listeners, + em = anActionTarget.eventManager; + + listeners = em.registeredEventListenersForEventType_onTarget_("pressStart", anActionTarget._pressComposer); + expect(listeners).toBeNull(); + + anActionTarget.prepareForActivationEvents(); + + listeners = em.registeredEventListenersForEventType_onTarget_("pressStart", anActionTarget._pressComposer); + expect(listeners).toEqual(anActionTarget); + }); + it("should listen for longPress on PressComposer on demand", function () { + var listeners, + em = anActionTarget.eventManager; + + listeners = em.registeredEventListenersForEventType_onTarget_("longPress", anActionTarget._pressComposer); + expect(listeners).toBeNull(); + + anActionTarget.addEventListener("longAction", listener, false); + + listeners = em.registeredEventListenersForEventType_onTarget_("longPress", anActionTarget._pressComposer); + expect(listeners).toEqual(anActionTarget); + }); + it("should fires a 'longAction' event when the PressComposer fires a longPress", function () { + var callback = jasmine.createSpy(); + anActionTarget.addEventListener("longAction", callback, false); + anActionTarget._pressComposer.dispatchEventNamed("longPress"); + expect(callback).toHaveBeenCalled(); + }); + describe("once prepareForActivationEvents is called", function () { + beforeEach(function () { + anActionTarget.element = anElement; + anActionTarget.prepareForActivationEvents(); + }); + it("should fire an 'action' event when the PressComposer fires a pressStart + press", function () { + var callback = jasmine.createSpy().and.callFake(function (event) { + expect(event.type).toEqual("action"); + }); + anActionTarget.addEventListener("action", callback, false); + + anActionTarget._pressComposer.dispatchEventNamed("pressStart"); + anActionTarget._pressComposer.dispatchEventNamed("press"); + + expect(callback).toHaveBeenCalled(); + }); + it("shouldn't fire an 'action' event when the PressComposer fires a pressStart + pressCancel", function () { + var callback = jasmine.createSpy().and.callFake(function (event) { + expect(event.type).toEqual("action"); + }); + anActionTarget.addEventListener("action", callback, false); + + anActionTarget._pressComposer.dispatchEventNamed("pressStart"); + anActionTarget._pressComposer.dispatchEventNamed("pressCancel"); + + expect(callback).not.toHaveBeenCalled(); + }); + it("should fire an 'action' event with the contents of the detail property", function () { + var callback = jasmine.createSpy().and.callFake(function (event) { + expect(event.detail.get("foo")).toEqual("bar"); + }); + anActionTarget.addEventListener("action", callback, false); + + anActionTarget.detail.set("foo", "bar"); + + anActionTarget._pressComposer.dispatchEventNamed("pressStart"); + anActionTarget._pressComposer.dispatchEventNamed("press"); + + expect(callback).toHaveBeenCalled(); + }); + }); + }); + }); + describe("objectDescriptor", function () { + it("can be created", function (done) { + var objectDescriptorPromise = Button.objectDescriptor; + objectDescriptorPromise.then(function (objectDescriptor) { + expect(objectDescriptor).not.toBeNull(); + }, function (err) { + fail(err); + }).finally(function () { + done(); + }); + }); + }); +}); From c5628cbe88168bf734306b4a79155ddca8c9af0c Mon Sep 17 00:00:00 2001 From: maxwell-jordan Date: Wed, 8 Jul 2026 11:40:21 -0700 Subject: [PATCH 7/7] new ActionTarget() --- test/spec/ui/button-spec.js | 30 ++++++++++++++ .../spec/action-target-spec.js | 39 ++----------------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/test/spec/ui/button-spec.js b/test/spec/ui/button-spec.js index e9c0fb248..43e182369 100644 --- a/test/spec/ui/button-spec.js +++ b/test/spec/ui/button-spec.js @@ -28,6 +28,36 @@ describe("test/ui/button-spec", function () { expect(aButton.label).toEqual(""); }); }); + describe("active target", function () { + var aButton, anElement; + beforeEach(function () { + aButton = new Button(); + anElement = MockDOM.element(); + }); + it("should set tabindex if needed", function () { + anElement.tagName = "DIV"; + spyOn(anElement, "setAttribute"); + spyOn(anElement, "removeAttribute"); + + aButton.element = anElement; + aButton._draw(); + aButton.draw(); + expect(anElement.setAttribute).toHaveBeenCalledWith("tabindex", "0"); + aButton.preventFocus = true; + aButton._draw(); + aButton.draw(); + expect(anElement.removeAttribute).toHaveBeenCalledWith("tabindex"); + }); + it("shouldn't set tabindex if not needed", function () { + anElement.tagName = "BUTTON"; + spyOn(anElement, "setAttribute"); + + aButton.element = anElement; + aButton._draw(); + aButton.draw(); + expect(anElement.setAttribute).not.toHaveBeenCalledWith("tabindex", "-1"); + }); + }); describe("draw", function () { var aButton; beforeEach(function () { diff --git a/ui/action-target.mod/spec/action-target-spec.js b/ui/action-target.mod/spec/action-target-spec.js index 9b58ebbbf..3f4ea576c 100644 --- a/ui/action-target.mod/spec/action-target-spec.js +++ b/ui/action-target.mod/spec/action-target-spec.js @@ -17,7 +17,7 @@ describe("test/ui/action-target-spec", function () { describe("disabled property", function () { var anActionTarget; beforeEach(function () { - anActionTarget = new Button(); + anActionTarget = new ActionTarget(); anActionTarget.element = MockDOM.element(); anActionTarget.originalElement = anActionTarget.element; }); @@ -36,40 +36,9 @@ describe("test/ui/action-target-spec", function () { expect(anActionTarget.element.disabled).toBeTruthy(); }); }); - describe("active target", function () { - var anActionTarget, anElement; - beforeEach(function () { - anActionTarget = new Button(); - anElement = MockDOM.element(); - }); - it("should set tabindex if needed", function () { - anElement.tagName = "DIV"; - spyOn(anElement, "setAttribute"); - spyOn(anElement, "removeAttribute"); - - anActionTarget.element = anElement; - anActionTarget._draw(); - anActionTarget.draw(); - expect(anElement.setAttribute).toHaveBeenCalledWith("tabindex", "0"); - anActionTarget.preventFocus = true; - anActionTarget._draw(); - anActionTarget.draw(); - expect(anElement.removeAttribute).toHaveBeenCalledWith("tabindex"); - }); - it("shouldn't set tabindex if not needed", function () { - anElement.tagName = "BUTTON"; - spyOn(anElement, "setAttribute"); - - anActionTarget.element = anElement; - anActionTarget._draw(); - anActionTarget.draw(); - expect(anElement.setAttribute).not.toHaveBeenCalledWith("tabindex", "-1"); - }); - }); - describe("converter", function () { beforeEach(function () { - anActionTarget = new Button(); + anActionTarget = new ActionTarget(); anActionTarget.element = MockDOM.element(); anActionTarget.originalElement = anActionTarget.element; anActionTarget.element.firstChild = MockDOM.element(); @@ -91,7 +60,7 @@ describe("test/ui/action-target-spec", function () { describe("events", function () { var anActionTarget, anElement, listener; beforeEach(function () { - anActionTarget = new Button(); + anActionTarget = new ActionTarget(); anElement = MockDOM.element(); listener = { handleEvent: function () {} @@ -172,7 +141,7 @@ describe("test/ui/action-target-spec", function () { }); describe("objectDescriptor", function () { it("can be created", function (done) { - var objectDescriptorPromise = Button.objectDescriptor; + var objectDescriptorPromise = ActionTarget.objectDescriptor; objectDescriptorPromise.then(function (objectDescriptor) { expect(objectDescriptor).not.toBeNull(); }, function (err) {