From a55b6a1411e9a39575ab1dee1775c33b025a6c9c Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 7 Sep 2023 22:58:20 +0200 Subject: [PATCH 001/162] util.HashHistory: second() #4865 --- src/util/HashHistory.mjs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/util/HashHistory.mjs b/src/util/HashHistory.mjs index 8f325ebca..32d74a3cf 100644 --- a/src/util/HashHistory.mjs +++ b/src/util/HashHistory.mjs @@ -41,14 +41,14 @@ class HashHistory extends Base { * @returns {Object} */ first() { - return this.stack[0]; + return this.stack[0] || null } /** * @returns {Number} */ getCount() { - return this.stack.length; + return this.stack.length } /** @@ -65,12 +65,19 @@ class HashHistory extends Base { stack.unshift(data); if (stack.length > me.maxItems) { - stack.pop(); + stack.pop() } me.fire('change', data, stack[1] || null) } } + + /** + * @returns {Object} + */ + second() { + return this.stack[1] || null + } } let instance = Neo.applyClassConfig(HashHistory); From 16c92464186e143150f90bf2bbe0dc9be36877f3 Mon Sep 17 00:00:00 2001 From: Nige White Date: Sun, 10 Sep 2023 21:04:42 +0200 Subject: [PATCH 002/162] Floating component auto-aligning (#4851) * WIP * component.Base: keeping render in sync with the latest change * hotfix candidate for the width: null delta update * WIP * WIP * WIP * WIP * floating needs z-index * renaming the align_ config for grid & table header buttons * examples cleanup * moved the floating / alignment rules into a new cmp base scss file * component.Base: method & config ordering, removed the async for update for now * core.Base: method order * main.DomAccess: method order --------- Co-authored-by: tobiu --- examples/ConfigurationViewport.mjs | 11 +- .../dialog/MainContainerController.mjs | 36 +- examples/dialog/DemoDialog.mjs | 27 +- examples/form/field/select/MainContainer.mjs | 29 +- resources/scss/src/component/Base.scss | 26 + resources/scss/src/form/field/Picker.scss | 3 - resources/scss/src/menu/List.scss | 5 - src/button/Base.mjs | 27 +- src/component/Base.mjs | 219 +++++++-- src/core/Base.mjs | 20 + src/form/field/Picker.mjs | 70 +-- src/form/field/trigger/Base.mjs | 2 +- src/grid/header/Button.mjs | 20 +- src/main/DomAccess.mjs | 300 ++++++++++-- src/menu/List.mjs | 120 +---- src/table/header/Button.mjs | 44 +- src/util/Array.mjs | 22 +- src/util/Rectangle.mjs | 451 +++++++++++++++++- test/siesta/siesta-node.js | 3 +- test/siesta/siesta.js | 1 + test/siesta/tests/Rectangle.mjs | 409 ++++++++++++++++ 21 files changed, 1541 insertions(+), 304 deletions(-) create mode 100644 resources/scss/src/component/Base.scss create mode 100644 test/siesta/tests/Rectangle.mjs diff --git a/examples/ConfigurationViewport.mjs b/examples/ConfigurationViewport.mjs index 86cb830b8..f36771ca4 100644 --- a/examples/ConfigurationViewport.mjs +++ b/examples/ConfigurationViewport.mjs @@ -56,11 +56,16 @@ class ConfigurationViewport extends Viewport { * */ onConstructed() { - let me = this, + let me = this, + style = me.exampleContainerConfig?.style, theme; - me.exampleComponent = me.createExampleComponent(); + if (style) { + delete me.exampleContainerConfig.style; + } + me.configurationComponents = me.createConfigurationComponents() || []; + me.exampleComponent = me.createExampleComponent(); theme = me.exampleComponent.getTheme(); @@ -69,7 +74,7 @@ class ConfigurationViewport extends Viewport { items : [me.exampleComponent], flex : me.exampleComponentFlex, layout: 'base', - style : {overflow: 'auto', padding: '20px'}, + style : {overflow: 'auto', padding: '20px', ...style}, ...me.exampleContainerConfig }, { module: Panel, diff --git a/examples/container/dialog/MainContainerController.mjs b/examples/container/dialog/MainContainerController.mjs index cd11c257e..0bde70855 100644 --- a/examples/container/dialog/MainContainerController.mjs +++ b/examples/container/dialog/MainContainerController.mjs @@ -1,4 +1,5 @@ -import Component from '../../../src/controller/Component.mjs'; +import Component from '../../../src/controller/Component.mjs'; +import SelectField from '../../../src/form/field/Select.mjs'; /** * @class Neo.examples.container.dialog.MainContainerController @@ -13,22 +14,14 @@ class MainContainerController extends Component { className: 'Neo.examples.container.dialog.MainContainerController' } - dialog = null; - title = 'example dialog'; - height = 300; - width = 500; + dialog = null + title = 'example dialog' + height = 300 + width = 500 /** - * - * @param {*} config - */ - construct(config) { - super.construct(config); - } - - /** - * - * @param {Object} data + * + * @param {Object} data */ async onButtonClick(data) { if (!this.dialog) { @@ -42,7 +35,7 @@ class MainContainerController extends Component { height: this.height, width: this.width, iconCls: ['fa', 'fa-home'], - + headerConfig: { items: [{ ntype: 'button', @@ -54,9 +47,20 @@ class MainContainerController extends Component { items: [{ ntype: 'container', html: 'text' + }, { + module : SelectField, + labelText: 'Select', + + store: { + data: [{ + id : 0, + name: 'Option 1' + }] + } }] }) } + this.dialog.show(); console.log(data, this); diff --git a/examples/dialog/DemoDialog.mjs b/examples/dialog/DemoDialog.mjs index 31d97a612..0041eac9c 100644 --- a/examples/dialog/DemoDialog.mjs +++ b/examples/dialog/DemoDialog.mjs @@ -1,4 +1,5 @@ -import Dialog from '../../src/dialog/Base.mjs'; +import Dialog from '../../src/dialog/Base.mjs'; +import SelectField from '../../src/form/field/Select.mjs'; /** * @class Neo.examples.dialog.DemoDialog @@ -7,12 +8,32 @@ import Dialog from '../../src/dialog/Base.mjs'; class DemoDialog extends Dialog { static config = { className: 'Neo.examples.dialog.DemoWindow', + modal : true, title : 'My Dialog', wrapperStyle: { - height: '40%', width : '40%' - } + }, + + items : [{ + module : SelectField, + labelText: 'Select', + + store: { + data: (() => { + const result = []; + + for (let i = 0; i < 20; i++) { + result.push({ + id : i, + name : `Option ${i + 1}` + }); + } + + return result; + })() + } + }] } } diff --git a/examples/form/field/select/MainContainer.mjs b/examples/form/field/select/MainContainer.mjs index f5e2bab1f..3d7f0bc03 100644 --- a/examples/form/field/select/MainContainer.mjs +++ b/examples/form/field/select/MainContainer.mjs @@ -15,7 +15,12 @@ class MainContainer extends ConfigurationViewport { className : 'Neo.examples.form.field.select.MainContainer', autoMount : true, configItemLabelWidth: 160, - layout : {ntype: 'hbox', align: 'stretch'} + layout : {ntype: 'hbox', align: 'stretch'}, + exampleContainerConfig : { + style : { + position : 'relative' + } + } } createConfigurationComponents() { @@ -125,6 +130,23 @@ class MainContainer extends ConfigurationViewport { stepSize : 5, style : {marginTop: '10px'}, value : me.exampleComponent.width + }, { + module : CheckBox, + checked : false, + labelText: 'At end', + listeners: {change: ({ value }) => { + const + { exampleComponent } = this, + { style } = exampleComponent; + + Object.assign(style, { + bottom : value ? '1em' : '', + position : value ? 'absolute' : '' + }); + exampleComponent.style = style; + exampleComponent.update(); + }}, + style : {marginTop: '10px'} }]; } @@ -137,7 +159,10 @@ class MainContainer extends ConfigurationViewport { store : MainStore, value : 'Arizona', // or 'AZ' valueField : 'abbreviation', - width : 200 + width : '50%', + pickerConfig : { + minHeight : '6em' + } }) } } diff --git a/resources/scss/src/component/Base.scss b/resources/scss/src/component/Base.scss new file mode 100644 index 000000000..c80781965 --- /dev/null +++ b/resources/scss/src/component/Base.scss @@ -0,0 +1,26 @@ +.neo-floating { + top : -10000px; + left : -10000px; + position : fixed; + z-index : 1000; +} + +// Shadow at the top +.neo-aligned-top { + box-shadow : 0px -2px 10px rgba(0, 0, 0, 0.3); +} + +// Shadow at the bottom +.neo-aligned-bottom { + box-shadow : 0px 2px 10px rgba(0, 0, 0, 0.3); +} + +// Shadow at the left +.neo-aligned-left { + box-shadow : -2px 0px 10px rgba(0, 0, 0, 0.3); +} + +// Shadow at the right +.neo-aligned-right { + box-shadow : 2px 0px 10px rgba(0, 0, 0, 0.3); +} diff --git a/resources/scss/src/form/field/Picker.scss b/resources/scss/src/form/field/Picker.scss index 9d799d2ac..ac862b265 100644 --- a/resources/scss/src/form/field/Picker.scss +++ b/resources/scss/src/form/field/Picker.scss @@ -13,9 +13,6 @@ .neo-picker-container { background-color: var(--pickerfield-container-background-color); border : var(--pickerfield-container-border); - box-shadow : var(--pickerfield-container-box-shadow); - position : absolute; - z-index : 200; &:focus { outline: none; diff --git a/resources/scss/src/menu/List.scss b/resources/scss/src/menu/List.scss index 33911f64c..b644620fd 100644 --- a/resources/scss/src/menu/List.scss +++ b/resources/scss/src/menu/List.scss @@ -61,9 +61,4 @@ } } } - - &.neo-floating { - box-shadow: var(--menu-list-box-shadow); - position : absolute; - } } diff --git a/src/button/Base.mjs b/src/button/Base.mjs index 5879103df..0f14e048a 100644 --- a/src/button/Base.mjs +++ b/src/button/Base.mjs @@ -79,7 +79,11 @@ class Base extends Component { */ iconPosition_: 'left', /** - * @member {Object[]|null} menu_=null + * An array representing the configuration of the menu items. + * + * Or a configuration object which adds custom configuration to the menu to be + * created and includes an `items` property to define the menu items. + * @member {Object|Object[]|null} menu_=null */ menu_: null, /** @@ -252,20 +256,27 @@ class Base extends Component { afterSetMenu(value, oldValue) { if (value) { import('../menu/List.mjs').then(module => { - let me = this; + let me = this, + isArray = Array.isArray(value), + items = isArray ? value : value.items, + menuConfig = isArray ? {} : value; me.menuList = Neo.create({ + align : { + edgeAlign : 't0-b0', + target : this.id + }, + ...menuConfig, module : module.default, appName : me.appName, displayField : 'text', floating : true, hidden : true, - items : value, + items, parentComponent: me, - style : {left: '-5000px', top: '-5000px'}, ...me.menuListConfig - }) - }) + }); + }); } } @@ -504,12 +515,12 @@ class Base extends Component { let menuList = this.menuList, hidden = !menuList.hidden; - menuList.hidden = hidden; - if (!hidden) { + !menuList.rendered && menuList.render(true); await this.timeout(50); menuList.focus() } + menuList.hidden = hidden; } } diff --git a/src/component/Base.mjs b/src/component/Base.mjs index 2b7327dc8..cf968c2f5 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -6,11 +6,16 @@ import KeyNavigation from '../util/KeyNavigation.mjs'; import Logger from '../util/Logger.mjs'; import NeoArray from '../util/Array.mjs'; import Observable from '../core/Observable.mjs'; +import Rectangle from '../util/Rectangle.mjs'; import Style from '../util/Style.mjs'; import Util from '../core/Util.mjs'; import VDomUtil from '../util/VDom.mjs'; import VNodeUtil from '../util/VNode.mjs'; +const + lengthRE = /^\d+\w+$/, + addUnits = value => value == null ? value : isNaN(value) ? value : `${value}px`; + /** * @class Neo.component.Base * @extends Neo.core.Base @@ -41,6 +46,14 @@ class Base extends CoreBase { * @protected */ ntype: 'component', + /** + * The default alignment specification to position this Component relative to some other + * Component, or Element or Rectangle. + */ + align_ : { + edgeAlign : 't-b', + constrainTo : 'document.body' + }, /** * The name of the App this component belongs to * @member {String|null} appName_=null @@ -139,6 +152,11 @@ class Base extends CoreBase { * @member {Object} dropZoneConfig=null */ dropZoneConfig: null, + /** + * True to render this component into the viewport outside of the document flow + * @member {Boolean} floating + */ + floating: false, /** * Internal flag which will get set to true on mount * @member {Boolean} hasBeenMounted=false @@ -507,6 +525,21 @@ class Base extends CoreBase { } } + /** + * Triggered after the flex config got changed + * @param {Number|String|null} value + * @param {Number|String|null} oldValue + * @protected + */ + afterSetFlex(value, oldValue) { + if (!isNaN(value)) { + value = `${value} ${value} 0%` + } + + this.configuredFlex = value; + this.changeVdomRootKey('flex', value) + } + /** * Triggered after the hasUnmountedVdomChanges config got changed * @param {Boolean} value @@ -537,6 +570,7 @@ class Base extends CoreBase { * @protected */ afterSetHeight(value, oldValue) { + this.configuredHeight = addUnits(value); this.changeVdomRootKey('height', value) } @@ -587,6 +621,7 @@ class Base extends CoreBase { * @protected */ afterSetMaxHeight(value, oldValue) { + this.configuredMaxHeight = addUnits(value); this.changeVdomRootKey('maxHeight', value) } @@ -597,6 +632,7 @@ class Base extends CoreBase { * @protected */ afterSetMaxWidth(value, oldValue) { + this.configuredMaxWidth = addUnits(value); this.changeVdomRootKey('maxWidth', value) } @@ -607,6 +643,7 @@ class Base extends CoreBase { * @protected */ afterSetMinHeight(value, oldValue) { + this.configuredMinHeight = addUnits(value); this.changeVdomRootKey('minHeight', value) } @@ -617,6 +654,7 @@ class Base extends CoreBase { * @protected */ afterSetMinWidth(value, oldValue) { + this.configuredMinWidth = addUnits(value); this.changeVdomRootKey('minWidth', value) } @@ -642,6 +680,10 @@ class Base extends CoreBase { me.doResolveUpdateCache(); + if (me.floating) { + me.alignTo(); + } + me.fire('mounted', me.id) } } @@ -710,7 +752,7 @@ class Base extends CoreBase { } /** - * Triggered after the vdom config got changed + * Triggered after the vdom pseudo-config got changed * @param {Object} value * @param {Object|null} oldValue * @protected @@ -736,6 +778,7 @@ class Base extends CoreBase { * @protected */ afterSetWidth(value, oldValue) { + this.configuredWidth = addUnits(value); this.changeVdomRootKey('width', value) } @@ -798,6 +841,28 @@ class Base extends CoreBase { } } + /** + * Aligns the top level node inside the main thread + * @param {Object} spec={} + * @returns {Promise} + */ + async alignTo(spec={}) { + const me = this; + + await Neo.main.DomAccess.align({ + ...me.align, + ...spec, + id : me.id, + configuredFlex : me.configuredFlex, + configuredWidth : me.configuredWidth, + configuredHeight : me.configuredHeight, + configuredMinWidth : me.configuredMinWidth, + configuredMinHeight : me.configuredMinHeight, + configuredMaxWidth : me.configuredMaxWidth, + configuredMaxHeight : me.configuredMaxHeight + }); + } + /** * Triggered when accessing the cls config * @param {String[]|null} value @@ -845,6 +910,23 @@ class Base extends CoreBase { return {...Object.assign(this.vdom.style || {}, value)} } + /** + * @param {Object|String} align + * @returns {Object} + */ + beforeSetAlign(align) { + let me = this; + + // Just a simple 't-b' + if (typeof align === 'string') { + align = { + edgeAlign : align + }; + } + // merge the incoming alignment specification into the configured default + return me.merge(me.merge({}, me.constructor.config.align), align); + } + /** * Triggered before the cls config gets changed. * @param {String[]} value @@ -852,7 +934,7 @@ class Base extends CoreBase { * @protected */ beforeSetCls(value, oldValue) { - return NeoArray.union(value || [], this.baseCls) + return NeoArray.union(value || [], this.baseCls, this.getBaseClass()); } /** @@ -976,6 +1058,17 @@ class Base extends CoreBase { return (Neo.isNumber(oldValue) && oldValue > 0) ? (oldValue - 1) : 0 } + beforeSetStyle(value) { + let me = this; + + if (typeof value === 'object') { + // merge the incoming style specification into the configured default + value = me.merge(me.merge({}, me.constructor.config.style), value) + } + + return value + } + /** * Changes the value of a vdom object attribute or removes it in case it has no value * @param {String} key @@ -1024,7 +1117,7 @@ class Base extends CoreBase { } /** - * Unregisters this instance from the ComponentManager + * Unregister this instance from the ComponentManager * @param {Boolean} updateParentVdom=false true to remove the component from the parent vdom => real dom * @param {Boolean} silent=false true to update the vdom silently (useful for destroying multiple child items in a row) * todo: unregister events @@ -1097,23 +1190,30 @@ class Base extends CoreBase { opts = {vdom, vnode}, deltas; - me.isVdomUpdating = true; + if (Neo.currentWorker.isSharedWorker) { + opts.appName = me.appName + } + + /** + * If a VDOM update is in flight, this is the Promise that will resolve when + * the update is completed. + * @member {Promise} vdomUpdate + * @protected + */ + me.vdomUpdate = Neo.vdom.Helper.update(opts); // we can not set the config directly => it could already be false, // and we still want to pass it further into subtrees me._needsVdomUpdate = false; me.afterSetNeedsVdomUpdate?.(false, true) - if (Neo.currentWorker.isSharedWorker) { - opts.appName = me.appName - } - - Neo.vdom.Helper.update(opts).catch(err => { + me.vdomUpdate.catch(err => { + me.vdomUpdate = null; console.log('Error attempting to update component dom', err, me); - me.isVdomUpdating = false; reject?.() }).then(data => { + me.vdomUpdate = null; // checking if the component got destroyed before the update cycle is done if (me.id) { // console.log('Component vnode updated', data); @@ -1130,7 +1230,9 @@ class Base extends CoreBase { me.resolveVdomUpdate(resolve) } } - }) + }); + + return me.vdomUpdate; } /** @@ -1153,6 +1255,20 @@ class Base extends CoreBase { return Neo.apps[this.appName] } + /** + * Override this method to add dynamic values into this.cls + * @returns {String[]} + */ + getBaseClass() { + const result = []; + + if (this.floating) { + result.push('neo-floating'); + } + + return result; + } + /** * Find an instance stored inside a config via optionally passing an ntype. * Returns this[configName] or the closest parent component with a match. @@ -1194,10 +1310,27 @@ class Base extends CoreBase { * Convenience shortcut * @param {String[]|String} id=this.id * @param {String} appName=this.appName - * @returns {Promise<*>} - */ - getDomRect(id=this.id, appName=this.appName) { - return Neo.main.DomAccess.getBoundingClientRect({appName, id}) + * @returns {Promise} + */ + async getDomRect(id=this.id, appName=this.appName) { + const + { + x, + y, + width, + height, + minWidth, + minHeight + } = await Neo.main.DomAccess.getBoundingClientRect({appName, id}), + result = new Rectangle(x, y, width, height); + + if (minWidth) { + result.minWidth = minWidth; + } + if (minHeight) { + result.minHeight = minHeight; + } + return result; } /** @@ -1445,6 +1578,35 @@ class Base extends CoreBase { return false } + /** + * Convenience method + * @returns {Boolean} + */ + get isVdomUpdating() { + // The VDOM is being updated if we have the promise that executeVdomUpdate uses + return Boolean(this.vdomUpdate) + } + + /** + * @param {Number|String} value + * @returns {Promise} + */ + async measure(value) { + if (value != null) { + if (value.endsWith('px')) { + value = parseFloat(value); + } + else if (lengthRE.test(value)) { + value = await Neo.main.DomAccess.measure({ value, id : this.id }); + } + else if (!isNaN(value)) { + value = parseFloat(value); + } + } + + return value + } + /** * Override this method to change the order configs are applied to this instance. * @param {Object} config @@ -1633,8 +1795,8 @@ class Base extends CoreBase { /** * Promise based vdom update - * @param {Object} [vdom=this.vdom] - * @param {Neo.vdom.VNode} [vnode= this.vnode] + * @param {Object} vdom=this.vdom + * @param {Neo.vdom.VNode} vnode= this.vnode * @returns {Promise} */ promiseUpdate(vdom=this.vdom, vnode=this.vnode) { @@ -1714,7 +1876,7 @@ class Base extends CoreBase { * - or the autoMount config is set to true * @param {Boolean} [mount] Mount the DOM after the vnode got created */ - render(mount) { + async render(mount) { let me = this, autoMount = mount || me.autoMount, app = Neo.apps[me.appName], @@ -1734,20 +1896,19 @@ class Base extends CoreBase { me._needsVdomUpdate = false; me.afterSetNeedsVdomUpdate?.(false, true) - Neo.vdom.Helper.create({ + const data = await Neo.vdom.Helper.create({ appName : me.appName, autoMount, parentId : autoMount ? me.getMountedParentId() : undefined, parentIndex: autoMount ? me.getMountedParentIndex() : undefined, ...me.vdom - }).then(data => { - me.onRender(data, useVdomWorker ? autoMount : false); - me.isVdomUpdating = false; + }); + me.onRender(data, useVdomWorker ? autoMount : false); + me.isVdomUpdating = false; - autoMount && !useVdomWorker && me.mount(); + autoMount && !useVdomWorker && me.mount(); - me.resolveVdomUpdate() - }) + me.resolveVdomUpdate() } } @@ -1816,8 +1977,8 @@ class Base extends CoreBase { * hideMode: 'removeDom' uses vdom removeDom. * hideMode: 'visibility' uses css visibility. */ - show() { - let me = this; + show(align) { + const me = this; if (me.hideMode !== 'visibility') { delete me.vdom.removeDom; @@ -1958,8 +2119,8 @@ class Base extends CoreBase { /** * */ - update() { - this.afterSetVdom(this.vdom, null) + async update() { + await this.afterSetVdom(this.vdom, null) } /** diff --git a/src/core/Base.mjs b/src/core/Base.mjs index c6fa5f637..9de8696d0 100644 --- a/src/core/Base.mjs +++ b/src/core/Base.mjs @@ -331,6 +331,26 @@ class Base { } } + /** + * Merges nested objects + * @param {Object} dest={} + * @param {Object} src + * @returns {Object} + */ + merge(dest={}, src) { + for (const key in src) { + const value = src[key]; + + if (typeof value === 'object') { + dest[key] = this.merge(dest[key], value); + } + else { + dest[key] = value; + } + } + return dest; + } + /** * Override this method to change the order configs are applied to this instance. * @param {Object} config diff --git a/src/form/field/Picker.mjs b/src/form/field/Picker.mjs index 00e3aa390..16e3a8f51 100644 --- a/src/form/field/Picker.mjs +++ b/src/form/field/Picker.mjs @@ -44,10 +44,6 @@ class Picker extends Text { Enter : 'onKeyDownEnter', Escape: 'onKeyDownEscape' }, - /** - * @member {Boolean} matchPickerWidth=true - */ - matchPickerWidth: true, /** * @member {Object|null} picker=null * @protected @@ -75,9 +71,10 @@ class Picker extends Text { pickerMaxHeight: 200, /** * The width of the picker container. Defaults to px. - * @member {Number|null} pickerWidth=100 + * By default, the width of the picker matches the width of the input wrap element. + * @member {Number|null} pickerWidth=null */ - pickerWidth: 100, + pickerWidth: null, /** * @member {Boolean} showPickerOnFocus=false * @protected @@ -140,39 +137,24 @@ class Picker extends Text { super.afterSetMounted(value, oldValue); } - /** - * @param {Boolean} silent - */ - applyClientRects(silent) { - let me = this, - rects = me.clientRects, - inputRect = rects[1], - parentRect = rects[2], - triggerRect = rects[0], - vdom = me.picker.vdom, - width = me.matchPickerWidth ? inputRect.width : me.pickerWidth; - - me.pickerWidth = width; - - vdom.style = vdom.style || {}; - - Object.assign(vdom.style, { - left : `${inputRect.left}px`, - top : `${inputRect.bottom + 1}px`, - width: `${width}px` - }); - - me.picker[silent ? '_vdom' : 'vdom'] = vdom; - } - /** * @returns {Neo.container.Base} */ createPicker() { - let me = this, + const + me = this, + { pickerWidth } = me, pickerComponent = me.createPickerComponent(); return Neo.create(Container, { + parentId : 'document.body', + floating : true, + align : { + edgeAlign : pickerWidth ? 't0-b0' : 't-b', + matchSize : pickerWidth ? false : true, + axisLock : true, + target : me.getInputWrapperId() + }, appName : me.appName, cls : ['neo-picker-container', 'neo-container'], height : me.pickerHeight, @@ -180,7 +162,7 @@ class Picker extends Text { items : pickerComponent ? [pickerComponent] : [], maxHeight: me.pickerMaxHeight, vdom : {cn: [], 'aria-activedescendant': me.id, tabIndex: -1}, - width : me.pickerWidth, + width : pickerWidth, ...me.pickerConfig, // scoped to the field instance @@ -225,20 +207,6 @@ class Picker extends Text { super.destroy(...args); } - /** - * @param {Function} [callback] - * @param {Object} [callbackScope] - */ - getClientRectsThenShow(callback, callbackScope) { - let me = this, - triggerId = me.getTriggerId('picker'); - - me.getDomRect([triggerId, me.getInputWrapperId(), me.parentId]).then(data => { - me.clientRects = data; - me.showPicker(callback, callbackScope); - }); - } - /** * Returns the picker instance and creates it in case it does not exist yet * @returns {Neo.container.Base} @@ -291,7 +259,7 @@ class Picker extends Text { let me = this; - me.showPickerOnFocus && !me.pickerIsMounted && me.getClientRectsThenShow(); + me.showPickerOnFocus && !me.pickerIsMounted && me.showPicker(); } /** @@ -330,7 +298,7 @@ class Picker extends Text { * @protected */ onKeyDownEnter(data, callback, callbackScope) { - !this.pickerIsMounted && this.getClientRectsThenShow(callback, callbackScope); + !this.pickerIsMounted && this.showPicker(callback, callbackScope); } /** @@ -361,8 +329,6 @@ class Picker extends Text { if (!me.pickerIsMounting) { me.pickerIsMounting = true; - me.applyClientRects(true); - listenerId = picker.on('mounted', () => { picker.un('mounted', listenerId); @@ -389,7 +355,7 @@ class Picker extends Text { if (me.pickerIsMounted) { me.hidePicker(); } else { - me.getClientRectsThenShow(); + me.showPicker(); } } } diff --git a/src/form/field/trigger/Base.mjs b/src/form/field/trigger/Base.mjs index 5a230de49..d0b2f30e0 100644 --- a/src/form/field/trigger/Base.mjs +++ b/src/form/field/trigger/Base.mjs @@ -29,7 +29,7 @@ class Base extends Component { /** * @member {String} align_='end' */ - align_: 'end', + align: 'end', /** * @member {String[]} baseCls=['neo-field-trigger'] */ diff --git a/src/grid/header/Button.mjs b/src/grid/header/Button.mjs index 947d6e36c..a5cb07047 100644 --- a/src/grid/header/Button.mjs +++ b/src/grid/header/Button.mjs @@ -8,11 +8,11 @@ import NeoArray from '../../util/Array.mjs'; class Button extends BaseButton { /** * Valid values for align - * @member {String[]} alignValues: ['left', 'center', 'right'] + * @member {String[]} cellAlignValues: ['left', 'center', 'right'] * @protected * @static */ - static alignValues = ['left', 'center', 'right'] + static cellAlignValues = ['left', 'center', 'right'] static config = { /** @@ -25,15 +25,15 @@ class Button extends BaseButton { * @protected */ ntype: 'grid-header-button', - /** - * Alignment of the matching table cells. Valid values are left, center, right - * @member {String} align_='left' - */ - align_: 'left', /** * @member {String[]} baseCls=['neo-grid-header-button'] */ baseCls: ['neo-grid-header-button'], + /** + * Alignment of the matching table cells. Valid values are left, center, right + * @member {String} cellAlign_='left' + */ + cellAlign_: 'left', /** * @member {String} iconCls='fa fa-arrow-circle-up' */ @@ -109,13 +109,13 @@ class Button extends BaseButton { } /** - * Triggered before the align config gets changed + * Triggered before the cellAlign config gets changed * @param {String} value * @param {String} oldValue * @protected */ - beforeSetAlign(value, oldValue) { - return this.beforeSetEnumValue(value, oldValue, 'align', 'alignValues'); + beforeSetCellAlign(value, oldValue) { + return this.beforeSetEnumValue(value, oldValue, 'cellAlign', 'cellAlignValues'); } /** diff --git a/src/main/DomAccess.mjs b/src/main/DomAccess.mjs index 7ebba9e1d..fd11d1ee8 100644 --- a/src/main/DomAccess.mjs +++ b/src/main/DomAccess.mjs @@ -1,6 +1,24 @@ import Base from '../core/Base.mjs'; import DeltaUpdates from './mixin/DeltaUpdates.mjs'; import Observable from '../core/Observable.mjs'; +import Rectangle from '../util/Rectangle.mjs'; + +const + lengthRE = /^\d+\w+$/, + fontSizeProps = [ + 'font-size', + 'font-size-adjust', + 'font-style', + 'font-weight', + 'font-family', + 'font-kerning', + 'font-stretch', + 'line-height', + 'text-transform', + 'text-decoration', + 'letter-spacing', + 'word-break' + ]; /** * @class Neo.main.DomAccess @@ -44,12 +62,14 @@ class DomAccess extends Base { remote: { app: [ 'addScript', + 'align', 'applyBodyCls', 'blur', 'execCommand', 'focus', 'getAttributes', 'getBoundingClientRect', + 'measure', 'scrollBy', 'scrollIntoView', 'scrollTo', @@ -82,10 +102,13 @@ class DomAccess extends Base { construct(config) { super.construct(config); - let me = this, - node; + const + me = this, + syncAligns = me.syncAligns.bind(me); if (Neo.config.renderCountDeltas) { + let node; + setInterval(() => { node = document.getElementById('neo-delta-updates'); @@ -94,8 +117,58 @@ class DomAccess extends Base { } me.countDeltasPer250ms = 0; - }, 250); + }, 250) } + + // Set up our aligning callback which is called when things change which may + // mean that alignments need to be updated. + me.syncAligns = () => requestAnimationFrame(syncAligns); + } + + /** + * @param {Object} alignSpec + */ + addAligned(alignSpec) { + const + me = this, + { id } = alignSpec, + aligns = me._aligns || (me._aligns = new Map()), + resizeObserver = me._alignResizeObserver || (me._alignResizeObserver = new ResizeObserver(me.syncAligns)), + { constrainToElement } = alignSpec; + + // Set up listeners which monitor for changes + if (!aligns.has(id)) { + // Realign when target's layout-controlling element changes size + resizeObserver.observe(alignSpec.offsetParent); + + // Realign when align to target changes size + resizeObserver.observe(alignSpec.targetElement); + + // Realign when constraining element changes size + if (constrainToElement) { + resizeObserver.observe(constrainToElement); + } + } + + if (!me.hasDocumentScrollListener) { + document.addEventListener('scroll', me.syncAligns, { + capture: true, + passive: true + }); + + me.hasDocumentScrollListener = true; + } + + if (!me.documentMutationObserver) { + me.documentMutationObserver = new MutationObserver(me.onDocumentMutation.bind(me)); + + me.documentMutationObserver.observe(document.body, { + childList: true, + subtree : true + }) + } + + aligns.set(id, alignSpec) } /** @@ -116,6 +189,58 @@ class DomAccess extends Base { document.head.appendChild(script); } + /** + * @param {Object} data + * @returns {Promise} + */ + async align(data) { + const + me = this, + { constrainTo } = data, + subject = data.subject = me.getElement(data.id), + { style } = subject, + align = {...data}, + lastAlign = me._aligns?.get(data.id); + + if (lastAlign) { + subject.classList.remove(`neo-aligned-${lastAlign.result.position}`); + } + + // Release any constrainTo or matchSize sizing which may have been imposed + // by a previous align call. + me.resetDimensions(align); + + // The Rectangle's align spec target and constrainTo must be Rectangles + align.target = me.getBoundingClientRect({ id : data.targetElement = me.getElementOrBody(data.target) }); + data.offsetParent = data.targetElement.offsetParent + if (constrainTo) { + align.constrainTo = me.getBoundingClientRect({ id : data.constrainToElement = me.getElementOrBody(constrainTo) }); + } + + // Get an aligned clone of myRect aligned according to the align object + const + myRect = me.getBoundingClientRect(data), + result = data.result = myRect.alignTo(align); + + Object.assign(style, { + top : 0, + left : 0, + transform : `translate(${result.x}px,${result.y}px)` + }); + if (result.width !== myRect.width) { + style.width = `${result.width}px`; + } + if (result.height !== myRect.height) { + style.height = `${result.height}px`; + } + + // Place box shadow at correct edge + subject.classList.add(`neo-aligned-${result.position}`); + + // Register an alignment to be kept in sync + me.addAligned(data); + } + /** * @param {Object} data * @param {String[]} data.cls @@ -170,7 +295,7 @@ class DomAccess extends Base { * @param {Object} data * @param {Array|String} data.id either an id or an array of ids * @param {Array|String} data.attributes either an attribute or an array of attributes - * @returns {Array|Object} In case id is an array, an array of atrrbute objects is returned, otherwise an object + * @returns {Array|Object} In case id is an array, an array of attribute objects is returned, otherwise an object */ getAttributes(data) { let returnData; @@ -220,54 +345,73 @@ class DomAccess extends Base { }); } else { let node = this.getElementOrBody(data.id), - rect = {}; + rect = {}, style, minWidth, minHeight; returnData = {}; if (node) { - rect = node.getBoundingClientRect(); + rect = node.getBoundingClientRect(); + style = node.ownerDocument.defaultView.getComputedStyle(node); + minWidth = style.getPropertyValue('min-width'), + minHeight = style.getPropertyValue('min-height'); // DomRect does not support spreading => {...DomRect} => {} - Object.assign(returnData, { - bottom: rect.bottom, - height: rect.height, - left : rect.left, - right : rect.right, - top : rect.top, - width : rect.width, - x : rect.x, - y : rect.y - }); + returnData = Rectangle.clone(rect); + + // Measure minWidth/minHeight in other units like em/rem etc + // Note that 0px is what the DOM reports if no minWidth is specified + // so we do not report a minimum in these cases. + if (lengthRE.test(minWidth) && minWidth !== '0px') { + returnData.minWidth = this.measure({ value : minWidth, id : node}); + } + if (lengthRE.test(minHeight) && minHeight !== '0px') { + returnData.minHeight = this.measure({ value : minHeight, id : node }); + } } } return returnData; } + onDocumentMutation(mutations) { + const me = this; + + // If the mutations are purely align subjects being added or removed, take no action. + if (!mutations.every(({ type, addedNodes, removedNodes }) => { + if (type === 'childList') { + const nodes = [...Array.from(addedNodes), ...Array.from(removedNodes)]; + + return nodes.every(a => me.isAlignSubject(a)) + } + })) { + me.syncAligns(); + } + } + /** - * @param {String} nodeId + * @param {String|HTMLElement} nodeId * @returns {HTMLElement} * @protected */ getElement(nodeId) { - if (Neo.config.useDomIds) { - return document.getElementById(nodeId); - } - - return document.querySelector(`[data-neo-id='${nodeId}']`); + return nodeId.nodeType ? nodeId : Neo.config.useDomIds ? document.getElementById(nodeId) : document.querySelector(`[data-neo-id='${nodeId}']`); } /** - * @param {String} [nodeId='document.body'] + * @param {String|HTMLElement} [nodeId='document.body'] * @returns {HTMLElement} * @protected */ getElementOrBody(nodeId='document.body') { - if (nodeId === 'body' || nodeId === 'document.body') { - return document.body; - } + return nodeId.nodeType ? nodeId : (nodeId === 'body' || nodeId === 'document.body') ? document.body : this.getElement(nodeId); + } - return this.getElement(nodeId); + /** + * @param {HTMLElement} el + * @returns {Boolean} + */ + isAlignSubject(el) { + return [...this._aligns?.values()].some(align => align.subject === el); } /** @@ -313,7 +457,53 @@ class DomAccess extends Base { }); document.head.appendChild(link); - }); + }) + } + + /** + * @param {Object} data + * @param {String} data.id + * @param {Number|String} data.value + * @returns {Number|String} + */ + measure({ value, id }) { + const node = id.nodeType === 1 ? id : this.getElement(id); + + if (value.endsWith('%')) { + const fraction = parseFloat(value) / 100; + + return (node.offsetParent?.getBoundingClientRect().height || 0) * fraction; + } + // If it's any other CSS unit than px, it needs to be measured using the DOM + else if (isNaN(value) && !value.endsWith('px')) { + const elStyle = node.ownerDocument.defaultView.getComputedStyle(node); + + let d = this._measuringDiv; + + if (!d) { + d = this._measuringDiv = document.createElement('div'); + d.style = 'position:fixed;top:-10000px;left:-10000px'; + } + // In case a DOM update cleared it out + document.body.appendChild(d); + + // Set all the font-size, font-weight etc style properties so that + // em/ex/rem etc units will match + fontSizeProps.forEach(prop => { + d.style[prop] = elStyle[prop]; + }); + d.className = node.className; + d.style.width = value; + + // Read back the resulting computed pixel width + value = elStyle.width; + + } + // If it's a number, or ends with px, use the numeric value. + else { + value = parseFloat(value); + } + return value; } /** @@ -419,6 +609,24 @@ class DomAccess extends Base { } } + /** + * Resets any DOM sizing configs to the last externally configured value. + * + * This is used during aligning to release any constraints applied by a previous alignment. + * @protected + */ + async resetDimensions(align) { + const { style } = this.getElement(align.id); + + style.flex = align.configuredFlex; + style.width = align.configuredWidth; + style.height = align.configuredHeight; + style.minWidth = align.configuredMinWidth; + style.minHeight = align.configuredMinHeight; + style.maxWidth = align.configuredMaxWidth; + style.maxHeight = align.configuredMaxHeight; + } + /** * @param {Object} data * @param {String} data.direction left, top @@ -555,6 +763,42 @@ class DomAccess extends Base { return {id: data.id}; } + /** + * + */ + syncAligns() { + const + me = this, + { _aligns } = me; + + // Keep all registered aligns aligned on any detected change + _aligns?.forEach(align => { + // Align subject and target still in the DOM - correct its alignment + if (document.contains(align.subject) && document.contains(align.targetElement)) { + me.align(align); + } + // Align subject or target no longer in the DOM - remove it. + else { + const + { _alignResizeObserver } = me, + { constrainToElement } = align; + + // Stop observing the align elements + _alignResizeObserver.unobserve(align.subject); + _alignResizeObserver.unobserve(align.offsetParent); + _alignResizeObserver.unobserve(align.targetElement); + if (constrainToElement) { + _alignResizeObserver.unobserve(constrainToElement); + } + + // Clear the last aligned class. + align.subject.classList.remove(`neo-aligned-${align.result?.position}`); + + _aligns.delete(align.id); + } + }) + } + /** * @param {Object} data * @param {String} [data.behavior='smooth'] // auto or smooth diff --git a/src/menu/List.mjs b/src/menu/List.mjs index d66f9e40b..b192a7b3e 100644 --- a/src/menu/List.mjs +++ b/src/menu/List.mjs @@ -1,6 +1,5 @@ import BaseList from '../list/Base.mjs'; import ListModel from '../selection/menu/ListModel.mjs'; -import NeoArray from '../util/Array.mjs'; import Store from './Store.mjs'; /** @@ -28,11 +27,6 @@ class List extends BaseList { * @member {String[]} baseCls=['neo-menu-list','neo-list'] */ baseCls: ['neo-menu-list', 'neo-list'], - /** - * True will add 'neo-floating' to the instance cls list. - * @member {Boolean} floating_=false - */ - floating_: false, /** * setTimeout() id after a focus-leave event. * @member {Number|null} focusTimeoutId=null @@ -115,19 +109,6 @@ class List extends BaseList { */ parentComponent = null - /** - * Triggered after the floating config got changed - * @param {Object[]} value - * @param {Object[]} oldValue - * @protected - */ - afterSetFloating(value, oldValue) { - let cls = this.cls; - - NeoArray[value ? 'add' : 'remove'](cls, 'neo-floating'); - this.cls = cls - } - /** * Triggered after the items config got changed * @param {Object[]} value @@ -167,43 +148,6 @@ class List extends BaseList { } } - /** - * Triggered after the mounted config got changed - * @param {Boolean} value - * @param {Boolean} oldValue - * @protected - */ - afterSetMounted(value, oldValue) { - super.afterSetMounted(value, oldValue); - - let me = this, - id = me.id, - parentId = me.parentComponent?.id; - - if (parentId) { - if (value) { - Neo.main.addon.ScrollSync.register({ - sourceId: parentId, - targetId: id - }); - - !me.parentMenu && me.getDomRect([id, parentId]).then(rects => { - let style = me.style || {}; - - style.left = `${rects[1].right - rects[0].width}px`; - style.top = `${rects[1].bottom + 1}px`; - - me.style = style - }) - } else if (oldValue !== undefined) { - Neo.main.addon.ScrollSync.unregister({ - sourceId: parentId, - targetId: id - }) - } - } - } - /** * Triggered after the zIndex config got changed * @param {Number} value @@ -401,48 +345,34 @@ class List extends BaseList { * @param {Object} record */ showSubMenu(nodeId, record) { - let me = this, + const + me = this, store = me.store, recordId = record[store.keyProperty], - subMenuMap = me.subMenuMap || {}, + subMenuMap = me.subMenuMap || (me.subMenuMap = {}), subMenuMapId = me.getMenuMapId(recordId), - subMenu = subMenuMap[subMenuMapId], - menuStyle, style; - - me.getDomRect(nodeId).then(rect => { - style = { - left: `${rect.right + me.subMenuGap}px`, - top : `${rect.top - 1}px` // minus the border - }; - - if (subMenu) { - menuStyle = subMenu.style; - - Object.assign(menuStyle, style); - - subMenu.setSilent({style: menuStyle}) - } else { - subMenuMap[subMenuMapId] = subMenu = Neo.create({ - module : List, - appName : me.appName, - displayField : me.displayField, - floating : true, - items : record.items, - isRoot : false, - parentComponent: me.parentComponent, - parentId : Neo.apps[me.appName].mainView.id, - parentIndex : store.indexOf(record), - parentMenu : me, - style, - zIndex : me.zIndex + 1 - }) - } - - me.activeSubMenu = subMenu; - me.subMenuMap = subMenuMap; - - subMenu.render(true) - }); + subMenu = subMenuMap[subMenuMapId] || (subMenuMap[subMenuMapId] = Neo.create({ + align : { + target : nodeId, + edgeAlign : 'l0-r0', + axisLock : true, + targetMargin : me.subMenuGap + }, + module : List, + appName : me.appName, + displayField : me.displayField, + floating : true, + items : record.items, + isRoot : false, + parentComponent: me.parentComponent, + parentId : Neo.apps[me.appName].mainView.id, + parentIndex : store.indexOf(record), + parentMenu : me, + zIndex : me.zIndex + 1 + })); + + me.activeSubMenu = subMenu; + subMenu.render(true) } /** diff --git a/src/table/header/Button.mjs b/src/table/header/Button.mjs index fbf5d22fb..535d5aceb 100644 --- a/src/table/header/Button.mjs +++ b/src/table/header/Button.mjs @@ -9,11 +9,11 @@ import TextField from '../../form/field/Text.mjs'; class Button extends BaseButton { /** * Valid values for align - * @member {String[]} alignValues: ['left','center','right'] + * @member {String[]} cellAlignValues: ['left','center','right'] * @protected * @static */ - static alignValues = ['left', 'center', 'right'] + static cellAlignValues = ['left', 'center', 'right'] static config = { /** @@ -26,15 +26,15 @@ class Button extends BaseButton { * @protected */ ntype: 'table-header-button', - /** - * Alignment of the matching table cells. Valid values are left, center, right - * @member {String} align_='left' - */ - align_: 'left', /** * @member {String[]} baseCls=['neo-table-header-button'] */ baseCls: ['neo-table-header-button'], + /** + * Alignment of the matching table cells. Valid values are left, center, right + * @member {String} cellAlign_='left' + */ + cellAlign_: 'left', /** * @member {String|null} dataField=null */ @@ -111,19 +111,17 @@ class Button extends BaseButton { construct(config) { super.construct(config); - let me = this; - - if (me.draggable) { - me.addDomListeners({ - dragend : me.onDragEnd, - dragenter: me.onDragEnter, - dragleave: me.onDragLeave, - dragover : me.onDragOver, - dragstart: me.onDragStart, - drop : me.onDrop, - scope : me - }); - } + let me = this; + + me.draggable && me.addDomListeners({ + dragend : me.onDragEnd, + dragenter: me.onDragEnter, + dragleave: me.onDragLeave, + dragover : me.onDragOver, + dragstart: me.onDragStart, + drop : me.onDrop, + scope : me + }) } /** @@ -255,13 +253,13 @@ class Button extends BaseButton { } /** - * Triggered before the align config gets changed + * Triggered before the cellAlign config gets changed * @param {String} value * @param {String} oldValue * @protected */ - beforeSetAlign(value, oldValue) { - return this.beforeSetEnumValue(value, oldValue, 'align', 'alignValues'); + beforeSetCellAlign(value, oldValue) { + return this.beforeSetEnumValue(value, oldValue, 'cellAlign', 'cellAlignValues'); } /** diff --git a/src/util/Array.mjs b/src/util/Array.mjs index ea941f8b0..1c571507f 100644 --- a/src/util/Array.mjs +++ b/src/util/Array.mjs @@ -132,29 +132,15 @@ class NeoArray extends Base { } /** - * Returns an array of items which are present in array1 and array2 + * Returns an array of items which are present in the passed arrays. + * Multiple arrays may be passed. * Only supports primitive items * @param {Array} array1 * @param {Array} array2 * @returns {Array} */ - static union(array1, array2) { - let result = [], - merge = array1.concat(array2), - len = merge.length, - assoc = {}, - item; - - while (len--) { - item = merge[len]; - - if (!assoc[item]) { - result.unshift(item); - assoc[item] = true; - } - } - - return result; + static union() { + return [...new Set(Array.prototype.concat(...arguments))]; } /** diff --git a/src/util/Rectangle.mjs b/src/util/Rectangle.mjs index efb8928be..9d41d921c 100644 --- a/src/util/Rectangle.mjs +++ b/src/util/Rectangle.mjs @@ -1,11 +1,84 @@ -import Base from '../core/Base.mjs'; - /** * The class contains utility methods for working with DOMRect Objects * @class Neo.util.Rectangle - * @extends Neo.core.Base + * @extends DOMRect */ -class Rectangle extends Base { + +const + emptyArray = Object.freeze([]), + // Convert edge array values into the [T,R,B,L] form. + parseEdgeValue = (e = 0) => { + if (!Array.isArray(e)) { + e = [e]; + } + switch (e.length) { + case 1: + e.length = 4; + return e.fill(e[0], 1, 4); + case 2:// top&bottom, left&right + return [e[0], e[1], e[0], e[1]]; + case 3:// top, left&right, bottom + return [e[0], e[1], e[2], e[1]]; + } + return e; + }, + parseEdgeAlign = edgeAlign => { + const + edgeParts = edgeAlignRE.exec(edgeAlign), + ourEdgeZone = edgeZone[edgeParts[1]], + theirEdgeZone = edgeZone[edgeParts[4]]; + + return { + ourEdge : edgeParts[1], + ourEdgeOffset : parseInt(edgeParts[2] || 50), + ourEdgeUnit : edgeParts[3] || '%', + ourEdgeZone, + theirEdge : edgeParts[4], + theirEdgeOffset : parseInt(edgeParts[5] || 50), + theirEdgeUnit : edgeParts[6] || '%', + theirEdgeZone, + + // Aligned to an edge, *outside* of the target. + // A normal align as a combo dropdown might request + edgeAligned : (ourEdgeZone & 1) === (theirEdgeZone & 1) && ourEdgeZone !== theirEdgeZone + } + }, + // The opposite of parseEdgeAlign, and it has to flip the edges + createReversedEdgeAlign = edges => { + const + ourEdge = oppositeEdge[edges.ourEdge], + theirEdge = oppositeEdge[edges.theirEdge]; + + // reconstitute a rule string with the edges flipped to the opposite sides + return `${ourEdge}${edges.ourEdgeOffset}${edges.ourEdgeUnit}-${theirEdge}${edges.theirEdgeOffset}${edges.theirEdgeUnit}` + + }, + getElRect = el => { + const r = el instanceof DOMRect ? el : (el?.nodeType === 1 ? el : typeof el === 'string' ? document.getElementById(el) : null)?.getBoundingClientRect(); + + // Convert DOMRect into Rectangle + return r && new Rectangle(r.x, r.y, r.width, r.height); + }, + oppositeEdge = { + t : 'b', + r : 'l', + b : 't', + l : 'r' + }, + edgeZone = { + t : 0, + r : 1, + b : 2, + l : 3 + }, + zoneNames = ['top', 'right', 'bottom', 'left'], + zoneEdges = ['t', 'r', 'b', 'l'], + zoneDimension = ['width', 'height'], + zoneCoord = [0, 1, 0, 1], + zeroMargins = [0, 0, 0, 0], + edgeAlignRE = /^([trblc])(\d*)(%|px)?-([trblc])(\d*)(%|px)?$/; + +export default class Rectangle extends DOMRect { static config = { /** * @member {String} className='Neo.util.Rectangle' @@ -147,8 +220,372 @@ class Rectangle extends Base { return movedRect; } -} -Neo.applyClassConfig(Rectangle); + set bottom(b) { + this.height += b - this.bottom; + } + get bottom() { + return super.bottom; + } + + set right(r) { + this.width += r - this.right; + } + get right() { + return super.right; + } + + // Change the x without moving the Rectangle. The left side moves and the right side doesn't + changeX(x) { + const widthDelta = this.x - x; + + this.x = x; + this.width += widthDelta; + } + + // Change the y without moving the Rectangle. The top side moves and the bottom side doesn't + changeY(y) { + const heightDelta = this.y - y; + + this.y = y; + this.height += heightDelta; + } + + clone() { + return Rectangle.clone(this); + } + + static clone(r) { + const result = new Rectangle(r.x, r.y, r.width, r.height); + + result.minWidth = r.minWidth; + result.minHeight = r.minHeight; + + return result; + } + + intersects(other) { + const me = this; + + if (other.height && other.width) { + const + left = Math.max(me.x, other.x), + top = Math.max(me.y, other.y), + right = Math.min(me.x + me.width, other.x + other.width), + bottom = Math.min(me.y + me.height, other.y + other.height); + + if (left >= right || top >= bottom) { + return false; + } + + return new Rectangle(left, top, right - left, bottom - top); + } + // We're dealing with a point here - zero dimensions + else { + return (other.x >= me.x && other.y >= me.y && other.right <= me.right && other.bottom <= me.bottom); + } + } + + /** + * Checks if the other Rectangle is fully contained inside this Rectangle + * @param {Object} other + * @returns {Boolean} + */ + contains(other) { + return this.bottom >= other.bottom + && this.left <= other.left + && this.right >= other.right + && this.top <= other.top; + } + + /** + * Returns a clone of this Rectangle expanded according to the edges array. + * @param {Number}Number[]} edges + * @returns + */ + expand(edges) { + edges = parseEdgeValue(edges); + + return new this.constructor(this.x - edges[3], this.y - edges[0], this.width + edges[1] + edges[3], this.height + edges[0] + edges[2]); + } -export default Rectangle; + moveBy(x = 0, y = 0) { + const result = this.clone(); + + if (Array.isArray(x)) { + y = x[1]; + x = x[0]; + } + result.x += x; + result.y += y; + return result; + } + + /** + * Returns `true` if this Rectangle completely contains the other Rectangle + * @param {Rectangle} other + */ + contains(other) { + return this.constructor.includes(this, other); + } + + /** + * Returns a copy of this Rectangle constrained to fit within the passed Rectangle + * @param {Rectangle} constrainTo + * @returns {Rectangle|Boolean} A new Rectangle constrained to te passed Rectangle, or false if it could not be constrained. + */ + constrainTo(constrainTo) { + const + me = this, + minWidth = me.minWidth || me.width, + minHeight = me.minHeight || me.height; + + // Not possible, even when shrunk to minima + if (minHeight > constrainTo.height || minWidth > constrainTo.width) { + return false; + } + + // We do not mutate this Rectangle, but return a constrained version + const result = me.clone(); + + // Translate result so that the top and left are visible + result.x = Math.max(me.x + Math.min(constrainTo.right - result.right, 0), constrainTo.x); + result.y = Math.max(me.y + Math.min(constrainTo.bottom - result.bottom, 0), constrainTo.y); + + // Pull in any resulting overflow + result.bottom = Math.min(result.bottom, constrainTo.bottom); + result.right = Math.min(result.right, constrainTo.right); + + return result; + } + + alignTo(align) { + const + me = this, + { + minWidth, + minHeight + } = me, + { + constrainTo, // Element or Rectangle result must fit into + target, // Element or Rectangle to align to + edgeAlign, // t50-b50 type string + axisLock, // true for flip, 'flexible' for flip, then try the other edges + offset, // Final [x, y] vector to move the result by. + matchSize + } = align, + targetMargin = align.targetMargin ? parseEdgeValue(align.targetMargin) : zeroMargins, + targetRect = getElRect(target), + constrainRect = getElRect(constrainTo), + edges = parseEdgeAlign(edgeAlign), + matchDimension = zoneDimension[edges.theirEdgeZone & 1]; + + let result = me.clone(); + + if (matchSize) { + result[matchDimension] = targetRect[matchDimension]; + } + + // Must do the calculations after the aligned side has been matched in size if requested. + const + myPoint = result.getAnchorPoint(edges.ourEdgeZone, edges.ourEdgeOffset, edges.ourEdgeUnit), + targetPoint = targetRect.getAnchorPoint(edges.theirEdgeZone, edges.theirEdgeOffset, edges.theirEdgeUnit, targetMargin), + vector = [targetPoint[0] - myPoint[0], targetPoint[1] - myPoint[1]]; + + result = result.moveBy(vector); + + // A useful property in the resulting rectangle which specifies which zone of the target + // It is being places in, T,R,B or L - 0, 1, 2, 3 + // Some code may want to treat DOM elements differently depending on the zone + result.zone = edges.theirEdgeZone; + result.position = zoneNames[result.zone]; + + // Now we create the four Rectangles around the target, into which we may be constrained + // Zones T,R,B,L 0 9, 1, 2, 3: + // +-----------------------------------------------------------------------------------+ + // | +-------------------------+------------------------+----------------------------+ | + // | | ^ | | ^ | | + // | | | | | | | | + // | | <-------+--------------+---------Zone 0---------+-------------+----------> | | + // | | | | | | | | + // | | | | | | | | + // | +----------+--------------+------------------------+-------------+--------------+ | + // | | | | +--------------------+ | | | | + // | | | | | | | | | | + // | | | | | | | | | | + // | | Zone 3 | | | | Zone 1 | | + // | | | | | | | | | | + // | | | | | | | | | | + // | | | | +--------------------+ | | | | + // | ++---------+--------------+------------------------+-------------+--------------+ | + // | | | | | | | | + // | | | | | | | | + // | | | | | | | | + // | | <-------+--------------+--------Zone 2----------+-------------+------------> | | + // | | | | | | | | + // | | v | | v | | + // | ++------------------------+------------------------+----------------------------+ | + // +-----------------------------------------------------------------------------------+ + if (constrainRect && !constrainRect.contains(result)) { + // They asked to overlap the target, for example t0-t0 + // In these cases, we just return the result + if (targetRect.intersects(result)) { + return result; + } + + // This is the zone we try to fit into first, the one that was asked for + let zone = edges.theirEdgeZone; + + // We create an array of four rectangles into which we try to fit with appropriate align specs. + // We must start with the requested zone, whatever that is. + const zonesToTry = [{ + zone, + edgeAlign + }]; + + if (axisLock) { + // Flip to the opposite side for the second try. + // The alignment string has to be reversed + // so r20-l30 has to become l20-r30. + // The other two zones revert to centered so are easier + zonesToTry[1] = { + zone : zone = (zone + 2) % 4, + edgeAlign : createReversedEdgeAlign(edges) + } + + // Fall back to the other two zones if we are allowed to + if (axisLock === 'flexible') { + zonesToTry.push({ + zone : zone = (alignSpec.startZone + 1) % 4, + edgeAlign : `${oppositeEdge[zoneEdges[zone]]}-${zoneEdges[zone]}` + }); + zonesToTry.push({ + zone : zone = (zone + 2) % 4, + edgeAlign : `${oppositeEdge[zoneEdges[zone]]}-${zoneEdges[zone]}` + }); + } + } + else { + // go through the other zones in order + for (let i = 1; i < 4; i++) { + zonesToTry.push({ + zone : zone = (zone + 1) % 4, + edgeAlign : `${oppositeEdge[zoneEdges[zone]]}-${zoneEdges[zone]}` + }); + } + } + + // Calculate the constraint Rectangle for each zone + for (let i = 0; i < zonesToTry.length; i++) { + // We clone the outer constraining rectangle + // and move it into position + const c = constrainRect.clone(); + + switch (zonesToTry[i].zone) { + case 0: + // The zone i2 above the target - zone 0/T + c.bottom = targetRect.y - targetMargin[0]; + break; + case 1: + // The zone is to the right of the target - zone 1/R + c.changeX(targetRect.right + targetMargin[1]); + break; + case 2: + // The zone is below the target - zone 2/B + c.changeY(targetRect.bottom + targetMargin[2]); + break; + case 3: + // The zone is to the left of the target - zone 3/L + c.right = targetRect.x - targetMargin[3]; + break; + } + zonesToTry[i].constrainRect = c; + } + + // Now try to constrain our result into each zone's constraintZone + for (let i = 0; i < zonesToTry.length; i++) { + const + { + zone, + edgeAlign, + constrainRect + } = zonesToTry[i], + edge = zoneEdges[zone]; + + if (matchSize) { + // If we are aligning to the requested edge, or it's opposite edge then + // match that edge size, else revert it to our own size + result[matchDimension] = edge === edges.theirEdge || edge == oppositeEdge[edges.theirEdge] ? targetRect[matchDimension] : me[matchDimension]; + } + + // Do a simple align to the current edge + result = result.alignTo({ + target : targetRect, + edgeAlign, + targetMargin + }); + + let solution = result.constrainTo(constrainRect); + + // As soon as we find a zone into which the result is willing to be constrained. return it + if (solution) { + solution.zone = zone; + solution.position = zoneNames[zone]; + return solution; + } + } + } + + return result; + } + + getAnchorPoint(edgeZone, edgeOffset, edgeUnit, margin = emptyArray) { + const me = this; + + let result; + + // Edge zones go top, right, bottom, left + // Each one calculates the start point of that edge then moves along it by + // the edgeOffset, then moves *away* from it by the margin for that edge if there's a margin. + switch (edgeZone) { + case 0: + result = [me.x, me.y - (margin[0] || 0), me.width, 0]; + break; + case 1: + result = [me.x + me.width + (margin[1] || 0), me.y, me.height, 1]; + break; + case 2: + result = [me.x, me.y + me.height + (margin[2] || 0), me.width, 0]; + break; + case 3: + result = [me.x - (margin[3] || 0), me.y, me.height, 1]; + } + result[result[3]] += edgeUnit === '%' ? result[2] / 100 * edgeOffset : edgeOffset; + result.length = 2; + return result; + } + + equals(other) { + return other instanceof DOMRect && + other.x === this.x && + other.y === this.y && + other.height === this.height && + other.width === this.width; + } + + // For debugging purposes only + show(color = 'red') { + const div = document.createElement('div'); + + div.style = ` + position:absolute; + transform:translate3d(${this.x}px, ${this.y}px, 0); + height:${this.height}px; + width:${this.width}px; + background-color:${color} + `; + document.body.appendChild(div); + setTimeout(() => div.remove(), 30000); + return div; + } +} diff --git a/test/siesta/siesta-node.js b/test/siesta/siesta-node.js index 33adb396a..46871970b 100644 --- a/test/siesta/siesta-node.js +++ b/test/siesta/siesta-node.js @@ -31,7 +31,8 @@ project.configure({ project.plan( './tests/CollectionBase.mjs', - './tests/VdomHelper.mjs' + './tests/VdomHelper.mjs', + './tests/Rectangle.mjs' ); project.start(); \ No newline at end of file diff --git a/test/siesta/siesta.js b/test/siesta/siesta.js index 3621e617c..83583c09e 100644 --- a/test/siesta/siesta.js +++ b/test/siesta/siesta.js @@ -20,6 +20,7 @@ project.plan( 'tests/ClassConfigsAndFields.mjs', 'tests/ClassSystem.mjs', 'tests/CollectionBase.mjs', + 'tests/Rectangle.mjs', 'tests/VdomHelper.mjs', 'tests/VdomCalendar.mjs' ); diff --git a/test/siesta/tests/Rectangle.mjs b/test/siesta/tests/Rectangle.mjs new file mode 100644 index 000000000..360847224 --- /dev/null +++ b/test/siesta/tests/Rectangle.mjs @@ -0,0 +1,409 @@ +import Rectangle from '../../../src/util/Rectangle.mjs'; + +// Maintainer: +// A good technique for debugging Rectangle code is to use the show() method +// to visualize the Rectangles in the DOM. +// +// The following statements will reveal the state of the Rectangles used +// constrainingRect.show('#f1f1f1') +// target.show('red') +// subject.show('yellow') +// result.show('green') + +StartTest(t => { + let constrainTo, subject, target, result; + + t.it('contains', t => { + t.ok(new Rectangle(0, 0, 100, 100).contains(new Rectangle(10, 10, 80, 80))); + t.notOk(new Rectangle(0, 0, 100, 100).contains(new Rectangle(10, 10, 80, 110))); + t.notOk(new Rectangle(0, 0, 100, 100).contains(new Rectangle(10, 10, 110, 80))); + t.notOk(new Rectangle(0, 0, 100, 100).contains(new Rectangle(10, 10, 110, 110))); + t.notOk(new Rectangle(0, 0, 100, 100).contains(new Rectangle(-10, 10, 80, 80))); + t.notOk(new Rectangle(0, 0, 100, 100).contains(new Rectangle(10, -10, 80, 80))); + }); + + t.describe('constrain', t => { + t.describe('Should constrain fitting subject rectangle', t => { + constrainTo = new Rectangle(0, 0, 200, 200); + + // Subject rect is below and to the right + result = new Rectangle(1000, 1000, 10, 10).constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(190, 190, 10, 10))); + + // Subject rect is to the right + result = new Rectangle(1000, 0, 10, 10).constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(190, 0, 10, 10))); + + // Subject rect is above and right + result = new Rectangle(1000, -1000, 10, 10).constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(190, 0, 10, 10))); + + // Subject rect is above + result = new Rectangle(0, -1000, 10, 10).constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 0, 10, 10))); + + // Subject rect is above and to the left + result = new Rectangle(-1000, -1000, 10, 10).constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 0, 10, 10))); + + // Subject rect is to the left + result = new Rectangle(-1000, 0, 10, 10).constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 0, 10, 10))); + + // Subject rect is below and to the left + result = new Rectangle(-1000, 1000, 10, 10).constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 190, 10, 10))); + + // Subject rect is below + result = new Rectangle(0, 1000, 10, 10).constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 190, 10, 10))); + }); + + t.describe('Should constrain non-fitting subject rectangle when minima allow', t => { + constrainTo = new Rectangle(0, 0, 200, 200); + subject = new Rectangle(1000, 1000, 210, 210); + + // Subject Rectangle is willing to shrink to 200x200 + subject.minWidth = subject.minHeight = 200; + + // Subject rect is below and to the right + result = subject.constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 0, 200, 200))); + + // Subject rect is to the right + subject.x = 1000; subject.y = 0; + result = subject.constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 0, 200, 200))); + + // Subject rect is above and right + subject.x = 1000; subject.y = -1000; + result = subject.constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 0, 200, 200))); + + // Subject rect is above + subject.x = 0; subject.y = -1000; + result = subject.constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 0, 200, 200))); + + // Subject rect is above and to the left + subject.x = 1000; subject.y = -1000; + result = subject.constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 0, 200, 200))); + + // Subject rect is to the left + subject.x = -1000; subject.y = 0; + result = subject.constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 0, 200, 200))); + + // Subject rect is below and to the left + subject.x = -1000; subject.y = 1000; + result = subject.constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 0, 200, 200))); + + // Subject rect is below + subject.x = 0; subject.y = 1000; + result = subject.constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 0, 200, 200))); + }); + + t.describe('Should constrain non-fitting subject rectangle when minima allow', t => { + constrainTo = new Rectangle(0, 0, 200, 200); + subject = new Rectangle(1000, 1000, 210, 210); + + // Subject Rectangle is willing to shrink to 200x200 + subject.minWidth = subject.minHeight = 200; + + // Subject rect is below and to the right + result = subject.constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 0, 200, 200))); + + // Subject rect is to the right + subject.x = 1000; subject.y = 0; + result = subject.constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 0, 200, 200))); + + // Subject rect is above and right + subject.x = 1000; subject.y = -1000; + result = subject.constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 0, 200, 200))); + + // Subject rect is above + subject.x = 0; subject.y = -1000; + result = subject.constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 0, 200, 200))); + + // Subject rect is above and to the left + subject.x = 1000; subject.y = -1000; + result = subject.constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 0, 200, 200))); + + // Subject rect is to the left + subject.x = -1000; subject.y = 0; + result = subject.constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 0, 200, 200))); + + // Subject rect is below and to the left + subject.x = -1000; subject.y = 1000; + result = subject.constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 0, 200, 200))); + + // Subject rect is below + subject.x = 0; subject.y = 1000; + result = subject.constrainTo(constrainTo); + t.ok(result.equals(new Rectangle(0, 0, 200, 200))); + }); + }); + + t.it('expand should work', t => { + t.ok(new Rectangle(10, 10, 10, 10).expand(5).equals(new Rectangle(5, 5, 20, 20))); + t.ok(new Rectangle(10, 10, 10, 10).expand([5, 6]).equals(new Rectangle(4, 5, 22, 20))); + t.ok(new Rectangle(10, 10, 10, 10).expand([5, 6, 7]).equals(new Rectangle(4, 5, 22, 22))); + t.ok(new Rectangle(10, 10, 10, 10).expand([5, 6, 7, 8]).equals(new Rectangle(2, 5, 24, 22))); + }); + + t.describe('alignTo', t => { + t.describe('unconstrained', t => { + target = new Rectangle(100, 100, 100, 100); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + edgeAlign : 't-b' + }); + t.ok(result.equals(new Rectangle(125, 200, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + edgeAlign : 't0-b0' + }); + t.ok(result.equals(new Rectangle(100, 200, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + edgeAlign : 't100-b100' + }); + t.ok(result.equals(new Rectangle(150, 200, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + edgeAlign : 'b-t' + }); + t.ok(result.equals(new Rectangle(125, 50, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + edgeAlign : 'b0-t0' + }); + t.ok(result.equals(new Rectangle(100, 50, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + edgeAlign : 'b100-t100' + }); + t.ok(result.equals(new Rectangle(150, 50, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + edgeAlign : 'l-r' + }); + t.ok(result.equals(new Rectangle(200, 125, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + edgeAlign : 'l0-r0' + }); + t.ok(result.equals(new Rectangle(200, 100, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + edgeAlign : 'l100-r100' + }); + t.ok(result.equals(new Rectangle(200, 150, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + edgeAlign : 'r-l' + }); + t.ok(result.equals(new Rectangle(50, 125, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + edgeAlign : 'r0-l0' + }); + t.ok(result.equals(new Rectangle(50, 100, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + edgeAlign : 'r100-l100' + }); + t.ok(result.equals(new Rectangle(50, 150, 50, 50))); + }); + + // The aligned edge of the subject must match the size of the edge of the target that it aligns to + t.describe('unconstrained width matchSize', t => { + target = new Rectangle(100, 100, 100, 100); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + edgeAlign : 't-b', + matchSize : true + }); + t.ok(result.equals(new Rectangle(100, 200, 100, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + edgeAlign : 'b-t', + matchSize : true + }); + t.ok(result.equals(new Rectangle(100, 50, 100, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + edgeAlign : 'l-r', + matchSize : true + }); + t.ok(result.equals(new Rectangle(200, 100, 50, 100))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + edgeAlign : 'r-l', + matchSize : true + }); + t.ok(result.equals(new Rectangle(50, 100, 50, 100))); + }); + + // The subject must be pushed away from the target by the correct targetMargin + t.describe('unconstrained with targetMargin', async t => { + target = new Rectangle(110, 110, 80, 80); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + targetMargin : 10, + edgeAlign : 't-b' + }); + t.ok(result.equals(new Rectangle(125, 200, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + targetMargin : 10, + edgeAlign : 't0-b0' + }); + t.ok(result.equals(new Rectangle(110, 200, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + targetMargin : 10, + edgeAlign : 't100-b100' + }); + t.ok(result.equals(new Rectangle(140, 200, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + targetMargin : 10, + edgeAlign : 'b-t' + }); + t.ok(result.equals(new Rectangle(125, 50, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + targetMargin : 10, + edgeAlign : 'b0-t0' + }); + t.ok(result.equals(new Rectangle(110, 50, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + targetMargin : 10, + edgeAlign : 'b100-t100' + }); + t.ok(result.equals(new Rectangle(140, 50, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + targetMargin : 10, + edgeAlign : 'l-r' + }); + t.ok(result.equals(new Rectangle(200, 125, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + targetMargin : 10, + edgeAlign : 'l0-r0' + }); + t.ok(result.equals(new Rectangle(200, 110, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + targetMargin : 10, + edgeAlign : 'l100-r100' + }); + t.ok(result.equals(new Rectangle(200, 140, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + targetMargin : 10, + edgeAlign : 'r-l' + }); + t.ok(result.equals(new Rectangle(50, 125, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + targetMargin : 10, + edgeAlign : 'r0-l0' + }); + t.ok(result.equals(new Rectangle(50, 110, 50, 50))); + + result = new Rectangle(0, 0, 50, 50).alignTo({ + target, + targetMargin : 10, + edgeAlign : 'r100-l100' + }); + t.ok(result.equals(new Rectangle(50, 140, 50, 50))); + }); + + // Test the adaptation scenarios for the requested position being below the target. + // TODO: test constrain adaptation for all four edge alignment zones + t.describe('constrained, edgeAlign : "t-b"', t => { + constrainTo = new Rectangle(0, 0, 500, 500); + target = new Rectangle(200, 200, 100, 100); + subject = new Rectangle(0, 0, 10, 1000); + + t.it('Subject shrinks to fit', t => { + subject.minHeight = 100; + result = subject.alignTo({ + target, + constrainTo, + edgeAlign : 't-b' + }); + t.ok(result.equals(new Rectangle(245, 300, 10, 200))); + }); + + subject = new Rectangle(0, 0, 10, 1000); + subject.minHeight = 100; + + t.it('Subject shrinks to fit, cannot fit in first choice zone, flips edge and matches aligned edge size', t => { + result = subject.alignTo({ + target, + constrainTo, + matchSize : true, + edgeAlign : 't-b' + }); + t.ok(result.equals(new Rectangle(200, 300, 100, 200))); + }); + + t.it('Subject cannot shrink enough to fit at first choice moves to closest fitting zone', t => { + // Only 50px below. Should flip to top + target = new Rectangle(200, 350, 100, 100), + subject = new Rectangle(0, 0, 50, 200); + subject.minHeight = 100; + + result = subject.alignTo({ + target, + constrainTo, + matchSize : true, + edgeAlign : 't-b' + }); + t.ok(result.equals(new Rectangle(150, 300, 50, 200))); + }); + }); + }); +}); From 57044d6c0f1ff8ecbd96c30706bbbcd96590734b Mon Sep 17 00:00:00 2001 From: tobiu Date: Sun, 10 Sep 2023 21:10:54 +0200 Subject: [PATCH 003/162] examples.ConfigurationViewport: hotfix --- examples/ConfigurationViewport.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ConfigurationViewport.mjs b/examples/ConfigurationViewport.mjs index f36771ca4..9bf21d2b7 100644 --- a/examples/ConfigurationViewport.mjs +++ b/examples/ConfigurationViewport.mjs @@ -64,8 +64,8 @@ class ConfigurationViewport extends Viewport { delete me.exampleContainerConfig.style; } - me.configurationComponents = me.createConfigurationComponents() || []; me.exampleComponent = me.createExampleComponent(); + me.configurationComponents = me.createConfigurationComponents() || []; theme = me.exampleComponent.getTheme(); From 4ef47635c57b16f9c7c9fd003f10cff413541098 Mon Sep 17 00:00:00 2001 From: tobiu Date: Sun, 10 Sep 2023 21:13:35 +0200 Subject: [PATCH 004/162] examples.form.field.select.MainContainer: removed the update() call after changing the style --- examples/form/field/select/MainContainer.mjs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/form/field/select/MainContainer.mjs b/examples/form/field/select/MainContainer.mjs index 3d7f0bc03..b80477b19 100644 --- a/examples/form/field/select/MainContainer.mjs +++ b/examples/form/field/select/MainContainer.mjs @@ -134,6 +134,8 @@ class MainContainer extends ConfigurationViewport { module : CheckBox, checked : false, labelText: 'At end', + style : {marginTop: '10px'}, + listeners: {change: ({ value }) => { const { exampleComponent } = this, @@ -143,10 +145,9 @@ class MainContainer extends ConfigurationViewport { bottom : value ? '1em' : '', position : value ? 'absolute' : '' }); + exampleComponent.style = style; - exampleComponent.update(); - }}, - style : {marginTop: '10px'} + }} }]; } From 2f6b80977ae4b676a37f43ad56f2183b02f08dd3 Mon Sep 17 00:00:00 2001 From: tobiu Date: Sun, 10 Sep 2023 21:32:51 +0200 Subject: [PATCH 005/162] button.Base: afterSetMenu() => cleanup --- src/button/Base.mjs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/button/Base.mjs b/src/button/Base.mjs index 0f14e048a..49af9ba03 100644 --- a/src/button/Base.mjs +++ b/src/button/Base.mjs @@ -80,8 +80,8 @@ class Base extends Component { iconPosition_: 'left', /** * An array representing the configuration of the menu items. - * - * Or a configuration object which adds custom configuration to the menu to be + * + * Or a configuration object which adds custom configuration to the menu to be * created and includes an `items` property to define the menu items. * @member {Object|Object[]|null} menu_=null */ @@ -256,7 +256,7 @@ class Base extends Component { afterSetMenu(value, oldValue) { if (value) { import('../menu/List.mjs').then(module => { - let me = this, + let me = this, isArray = Array.isArray(value), items = isArray ? value : value.items, menuConfig = isArray ? {} : value; @@ -264,7 +264,7 @@ class Base extends Component { me.menuList = Neo.create({ align : { edgeAlign : 't0-b0', - target : this.id + target : me.id }, ...menuConfig, module : module.default, @@ -275,8 +275,8 @@ class Base extends Component { items, parentComponent: me, ...me.menuListConfig - }); - }); + }) + }) } } From ba978c41024bb9a685b133adeac8b6b5980906a5 Mon Sep 17 00:00:00 2001 From: tobiu Date: Sun, 10 Sep 2023 21:40:06 +0200 Subject: [PATCH 006/162] menu.List: removed onItemClick() => a list select will now show subMenus, otherwise this happens twice. --- src/menu/List.mjs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/menu/List.mjs b/src/menu/List.mjs index b192a7b3e..7be998b75 100644 --- a/src/menu/List.mjs +++ b/src/menu/List.mjs @@ -279,16 +279,6 @@ class List extends BaseList { } } - /** - * @param {Object} node - * @param {Object} data - */ - onItemClick(node, data) { - super.onItemClick(node, data); - - this.onKeyDownEnter(node.id) - } - /** * @param {String} nodeId */ @@ -344,7 +334,7 @@ class List extends BaseList { * @param {String} nodeId * @param {Object} record */ - showSubMenu(nodeId, record) { + showSubMenu(nodeId, record) {console.log('showSubMenu', nodeId) const me = this, store = me.store, From e5bd4914769572571538212ff087aadfdd18eb24 Mon Sep 17 00:00:00 2001 From: tobiu Date: Sun, 10 Sep 2023 21:41:21 +0200 Subject: [PATCH 007/162] menu.List: - testing log --- src/menu/List.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menu/List.mjs b/src/menu/List.mjs index 7be998b75..f8014f4ac 100644 --- a/src/menu/List.mjs +++ b/src/menu/List.mjs @@ -334,7 +334,7 @@ class List extends BaseList { * @param {String} nodeId * @param {Object} record */ - showSubMenu(nodeId, record) {console.log('showSubMenu', nodeId) + showSubMenu(nodeId, record) { const me = this, store = me.store, From 4e5f2cfcc51f24ee87aa45de20bc77890a31d743 Mon Sep 17 00:00:00 2001 From: tobiu Date: Sun, 10 Sep 2023 21:51:27 +0200 Subject: [PATCH 008/162] menu.List: showSubMenu() => added a negative top&left style to prevent menus showing up inside the top left corner for one frame. --- src/menu/List.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/menu/List.mjs b/src/menu/List.mjs index f8014f4ac..778b1c3b3 100644 --- a/src/menu/List.mjs +++ b/src/menu/List.mjs @@ -358,6 +358,7 @@ class List extends BaseList { parentId : Neo.apps[me.appName].mainView.id, parentIndex : store.indexOf(record), parentMenu : me, + style : {left: '-10000px', top: '-10000px'}, zIndex : me.zIndex + 1 })); From 4e0f029446a7ebff6f45778337fda566cc3a54a1 Mon Sep 17 00:00:00 2001 From: tobiu Date: Sun, 10 Sep 2023 21:53:47 +0200 Subject: [PATCH 009/162] button.Base: afterSetMenu() => docs comment => added objects --- src/button/Base.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/button/Base.mjs b/src/button/Base.mjs index 49af9ba03..e29c64dc0 100644 --- a/src/button/Base.mjs +++ b/src/button/Base.mjs @@ -249,8 +249,8 @@ class Base extends Component { /** * Triggered after the menu config got changed - * @param {Object[]|null} value - * @param {Object[]|null} oldValue + * @param {Object|Object[]|null} value + * @param {Object|Object[]|null} oldValue * @protected */ afterSetMenu(value, oldValue) { From cd4390c8204a3180f9d61d55b765d009d153477a Mon Sep 17 00:00:00 2001 From: tobiu Date: Sun, 10 Sep 2023 22:31:46 +0200 Subject: [PATCH 010/162] button.Base: destroy() #4867 --- src/button/Base.mjs | 9 +++++++++ src/collection/Base.mjs | 7 ++++--- src/list/Base.mjs | 6 +++--- src/menu/List.mjs | 4 +--- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/button/Base.mjs b/src/button/Base.mjs index e29c64dc0..286d2b071 100644 --- a/src/button/Base.mjs +++ b/src/button/Base.mjs @@ -431,6 +431,15 @@ class Base extends Component { } } + /** + * @param {Boolean} updateParentVdom + * @param {Boolean} silent + */ + destroy(updateParentVdom=false, silent=false) { + this.menuList && this.menuList.destroy(true, false); + super.destroy(updateParentVdom, silent); + } + /** * Convenience shortcut * @returns {Object} diff --git a/src/collection/Base.mjs b/src/collection/Base.mjs index 0d03f6d20..07fa259fe 100644 --- a/src/collection/Base.mjs +++ b/src/collection/Base.mjs @@ -463,10 +463,11 @@ class Base extends CoreBase { * Clears the map & items array before the super call */ destroy() { - let me = this; + let me = this, + items = me._items; - me._items.splice(0, me._items.length); - me.map.clear(); + items && items.splice(0, items.length); + me.map && me.map.clear(); super.destroy(); } diff --git a/src/list/Base.mjs b/src/list/Base.mjs index c03c19cd6..dc9769f32 100644 --- a/src/list/Base.mjs +++ b/src/list/Base.mjs @@ -492,16 +492,16 @@ class Base extends Component { } /** - * + * @param args */ - destroy() { + destroy(...args) { let me = this; me.selectionModel?.destroy(); me.autoDestroyStore && me.store?.destroy(); - super.destroy(); + super.destroy(...args); } /** diff --git a/src/menu/List.mjs b/src/menu/List.mjs index 778b1c3b3..31af477a2 100644 --- a/src/menu/List.mjs +++ b/src/menu/List.mjs @@ -187,9 +187,7 @@ class List extends BaseList { destroy(...args) { let me = this, activeSubMenu = me.activeSubMenu, - subMenuMap = me.subMenuMap; - - me.store.destroy(); + subMenuMap = me.subMenuMap || {}; activeSubMenu?.unmount(); From 40df7d4b39dfb6a8ac46a2f3d483aff8ada7724f Mon Sep 17 00:00:00 2001 From: tobiu Date: Sun, 10 Sep 2023 22:33:41 +0200 Subject: [PATCH 011/162] button.Base: destroy() #4867 --- src/collection/Base.mjs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/collection/Base.mjs b/src/collection/Base.mjs index 07fa259fe..0d03f6d20 100644 --- a/src/collection/Base.mjs +++ b/src/collection/Base.mjs @@ -463,11 +463,10 @@ class Base extends CoreBase { * Clears the map & items array before the super call */ destroy() { - let me = this, - items = me._items; + let me = this; - items && items.splice(0, items.length); - me.map && me.map.clear(); + me._items.splice(0, me._items.length); + me.map.clear(); super.destroy(); } From 0e4ca8ec7295e52867a8265ef020640e36471f3c Mon Sep 17 00:00:00 2001 From: tobiu Date: Sun, 10 Sep 2023 22:41:49 +0200 Subject: [PATCH 012/162] button.Base: toggleMenu() => focus() is no longer working for the 2+ show call #4868 --- src/button/Base.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/button/Base.mjs b/src/button/Base.mjs index 286d2b071..1daac2ad0 100644 --- a/src/button/Base.mjs +++ b/src/button/Base.mjs @@ -524,12 +524,12 @@ class Base extends Component { let menuList = this.menuList, hidden = !menuList.hidden; + menuList.hidden = hidden; + if (!hidden) { - !menuList.rendered && menuList.render(true); await this.timeout(50); menuList.focus() } - menuList.hidden = hidden; } } From 08aff3c273f16b6fa34f6f62ef0bb3a51516668a Mon Sep 17 00:00:00 2001 From: tobiu Date: Sun, 10 Sep 2023 22:52:39 +0200 Subject: [PATCH 013/162] examples.form.field.select.MainContainer: width control #4869 --- examples/form/field/select/MainContainer.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/form/field/select/MainContainer.mjs b/examples/form/field/select/MainContainer.mjs index b80477b19..bafae9d89 100644 --- a/examples/form/field/select/MainContainer.mjs +++ b/examples/form/field/select/MainContainer.mjs @@ -122,7 +122,7 @@ class MainContainer extends ConfigurationViewport { listeners: {change: me.onConfigChange.bind(me, 'typeAhead')}, style : {marginTop: '10px'} }, { - module : NumberField, + module : TextField, labelText: 'width', listeners: {change: me.onConfigChange.bind(me, 'width')}, maxValue : 300, From f50d3670d82bff0945b1e403d07519462b15acb5 Mon Sep 17 00:00:00 2001 From: tobiu Date: Sun, 10 Sep 2023 22:54:10 +0200 Subject: [PATCH 014/162] examples.form.field.select.MainContainer: cleanup --- examples/form/field/select/MainContainer.mjs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/examples/form/field/select/MainContainer.mjs b/examples/form/field/select/MainContainer.mjs index bafae9d89..6c8cbef46 100644 --- a/examples/form/field/select/MainContainer.mjs +++ b/examples/form/field/select/MainContainer.mjs @@ -12,15 +12,11 @@ import TextField from '../../../../src/form/field/Text.mjs'; */ class MainContainer extends ConfigurationViewport { static config = { - className : 'Neo.examples.form.field.select.MainContainer', - autoMount : true, - configItemLabelWidth: 160, - layout : {ntype: 'hbox', align: 'stretch'}, - exampleContainerConfig : { - style : { - position : 'relative' - } - } + className : 'Neo.examples.form.field.select.MainContainer', + autoMount : true, + configItemLabelWidth : 160, + exampleContainerConfig: {style: {position: 'relative'}}, + layout : {ntype: 'hbox', align: 'stretch'} } createConfigurationComponents() { From d8059876304baa59698e1a500391289e848f9dcd Mon Sep 17 00:00:00 2001 From: Nige White Date: Mon, 11 Sep 2023 07:10:48 +0200 Subject: [PATCH 015/162] Fix https://github.com/neomjs/neo/issues/4870 --- src/component/Base.mjs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/component/Base.mjs b/src/component/Base.mjs index cf968c2f5..4645a2ed4 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -5,7 +5,6 @@ import DomEventManager from '../manager/DomEvent.mjs'; import KeyNavigation from '../util/KeyNavigation.mjs'; import Logger from '../util/Logger.mjs'; import NeoArray from '../util/Array.mjs'; -import Observable from '../core/Observable.mjs'; import Rectangle from '../util/Rectangle.mjs'; import Style from '../util/Style.mjs'; import Util from '../core/Util.mjs'; @@ -1217,8 +1216,7 @@ class Base extends CoreBase { // checking if the component got destroyed before the update cycle is done if (me.id) { // console.log('Component vnode updated', data); - me.vnode = data.vnode; - me.isVdomUpdating = false; + me.vnode = data.vnode; deltas = data.deltas; @@ -1584,7 +1582,16 @@ class Base extends CoreBase { */ get isVdomUpdating() { // The VDOM is being updated if we have the promise that executeVdomUpdate uses - return Boolean(this.vdomUpdate) + return Boolean(this.vdomUpdate); + } + + // Allow the Component to be set to the isVdomUpdating state + set isVdomUpdating(isVdomUpdating) { + isVdomUpdating = Boolean(isVdomUpdating); + + if (isVdomUpdating !== this.isVdomUpdating) { + this.vdomUpdate = isVdomUpdating; + } } /** From d9f00b4b6757fd43f6d1057e8398e7893937d766 Mon Sep 17 00:00:00 2001 From: Nige White Date: Mon, 11 Sep 2023 08:45:09 +0200 Subject: [PATCH 016/162] Revert removal of Observable --- src/component/Base.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/component/Base.mjs b/src/component/Base.mjs index 4645a2ed4..47c3a2ebd 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -5,6 +5,7 @@ import DomEventManager from '../manager/DomEvent.mjs'; import KeyNavigation from '../util/KeyNavigation.mjs'; import Logger from '../util/Logger.mjs'; import NeoArray from '../util/Array.mjs'; +import Observable from '../core/Observable.mjs'; import Rectangle from '../util/Rectangle.mjs'; import Style from '../util/Style.mjs'; import Util from '../core/Util.mjs'; From 77a4f62c7b57a31c4c40e6fd362fa6481d5f0d09 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 09:36:25 +0200 Subject: [PATCH 017/162] component.Base: cleanup --- src/component/Base.mjs | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/component/Base.mjs b/src/component/Base.mjs index 47c3a2ebd..97ab15e38 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -336,6 +336,23 @@ class Base extends CoreBase { */ resolveUpdateCache = [] + /** + * Convenience method + * @returns {Boolean} + */ + get isVdomUpdating() { + // The VDOM is being updated if we have the promise that executeVdomUpdate uses + return Boolean(this.vdomUpdate); + } + // Allow the Component to be set to the isVdomUpdating state + set isVdomUpdating(isVdomUpdating) { + isVdomUpdating = Boolean(isVdomUpdating); + + if (isVdomUpdating !== this.isVdomUpdating) { + this.vdomUpdate = isVdomUpdating; + } + } + /** * Apply component based listeners * @member {Object} listeners={} @@ -1197,7 +1214,7 @@ class Base extends CoreBase { /** * If a VDOM update is in flight, this is the Promise that will resolve when * the update is completed. - * @member {Promise} vdomUpdate + * @member {Promise|null} vdomUpdate * @protected */ me.vdomUpdate = Neo.vdom.Helper.update(opts); @@ -1326,9 +1343,11 @@ class Base extends CoreBase { if (minWidth) { result.minWidth = minWidth; } + if (minHeight) { result.minHeight = minHeight; } + return result; } @@ -1577,24 +1596,6 @@ class Base extends CoreBase { return false } - /** - * Convenience method - * @returns {Boolean} - */ - get isVdomUpdating() { - // The VDOM is being updated if we have the promise that executeVdomUpdate uses - return Boolean(this.vdomUpdate); - } - - // Allow the Component to be set to the isVdomUpdating state - set isVdomUpdating(isVdomUpdating) { - isVdomUpdating = Boolean(isVdomUpdating); - - if (isVdomUpdating !== this.isVdomUpdating) { - this.vdomUpdate = isVdomUpdating; - } - } - /** * @param {Number|String} value * @returns {Promise} @@ -1911,6 +1912,7 @@ class Base extends CoreBase { parentIndex: autoMount ? me.getMountedParentIndex() : undefined, ...me.vdom }); + me.onRender(data, useVdomWorker ? autoMount : false); me.isVdomUpdating = false; From 3a0e787cf42abd8e7f37cedc62d7dacf5f184756 Mon Sep 17 00:00:00 2001 From: Torsten Date: Mon, 11 Sep 2023 09:38:53 +0200 Subject: [PATCH 018/162] Updated iconCls to remove serveral items which contain several cls in a single string. --- src/tree/Accordion.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tree/Accordion.mjs b/src/tree/Accordion.mjs index 4ec8aa1f0..260a5b2a9 100644 --- a/src/tree/Accordion.mjs +++ b/src/tree/Accordion.mjs @@ -266,7 +266,7 @@ class AccordionTree extends TreeList { cn : [{ flag : 'iconCls', tag : 'span', - cls : ['neo-accordion-item-icon', item[me.fields.icon]], + cls : itemIconCls, id : id + '__icon', removeDom: (!item.isLeaf || !me.showIcon) }, { From 614bfc9a8cd8a0e871d7d3b5c34255794f82071f Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 09:52:27 +0200 Subject: [PATCH 019/162] button.Base: adjust the initial main menu rendering position to a negative offset #4871 --- src/button/Base.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/button/Base.mjs b/src/button/Base.mjs index 1daac2ad0..f7f8de7af 100644 --- a/src/button/Base.mjs +++ b/src/button/Base.mjs @@ -273,6 +273,7 @@ class Base extends Component { floating : true, hidden : true, items, + style : {left: '-10000px', top: '-10000px'}, parentComponent: me, ...me.menuListConfig }) From 1ef84c9a83423dd8d2366dcf1cebf3ac1b0a99b9 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 11:06:48 +0200 Subject: [PATCH 020/162] menu.List: use a top-level background color #4872 --- resources/scss/src/menu/List.scss | 7 ++++--- resources/scss/theme-dark/menu/List.scss | 3 ++- resources/scss/theme-light/menu/List.scss | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/resources/scss/src/menu/List.scss b/resources/scss/src/menu/List.scss index b644620fd..e868ec668 100644 --- a/resources/scss/src/menu/List.scss +++ b/resources/scss/src/menu/List.scss @@ -1,7 +1,8 @@ .neo-menu-list { - border : 1px solid var(--menu-list-border-color); - overflow-y : auto; - width : fit-content; + background-color: var(--menu-list-background-color); + border : 1px solid var(--menu-list-border-color); + overflow-y : auto; + width : fit-content; .neo-list-item { align-items : center; diff --git a/resources/scss/theme-dark/menu/List.scss b/resources/scss/theme-dark/menu/List.scss index f5ce5cac5..cc4a910cb 100644 --- a/resources/scss/theme-dark/menu/List.scss +++ b/resources/scss/theme-dark/menu/List.scss @@ -1,5 +1,6 @@ :root .neo-theme-dark { // .neo-menu-list - --menu-list-border-color : #3c3f41; + --menu-list-background-color : #3c3f41; + --menu-list-border-color : #2b2b2b; --menu-list-box-shadow : 0 5px 10px rgba(0,0,0,.4); --menu-list-item-background-color : inherit; --menu-list-item-background-color-disabled: inherit; diff --git a/resources/scss/theme-light/menu/List.scss b/resources/scss/theme-light/menu/List.scss index 4c1a137fb..dbbfeb9af 100644 --- a/resources/scss/theme-light/menu/List.scss +++ b/resources/scss/theme-light/menu/List.scss @@ -1,4 +1,5 @@ :root .neo-theme-light { // .neo-menu-list + --menu-list-background-color : #fff; --menu-list-border-color : #1c60a0; --menu-list-box-shadow : 0 5px 10px rgba(0,0,0,.4); --menu-list-item-background-color : inherit; From 39da0159def33723bdf3e5370e046105c67f7de2 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 11:49:02 +0200 Subject: [PATCH 021/162] component.Base: getApp() => get app() refactoring #4874 --- src/component/Base.mjs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/component/Base.mjs b/src/component/Base.mjs index 97ab15e38..d60598e92 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -336,6 +336,14 @@ class Base extends CoreBase { */ resolveUpdateCache = [] + /** + * Convenience method to access the App this component belongs to + * @returns {Neo.controller.Application|null} + */ + get app() { + return Neo.apps[this.appName] || null + } + /** * Convenience method * @returns {Boolean} @@ -1263,14 +1271,6 @@ class Base extends CoreBase { }) } - /** - * Convenience method to access the App this component belongs to - * @returns {Neo.controller.Application} - */ - getApp() { - return Neo.apps[this.appName] - } - /** * Override this method to add dynamic values into this.cls * @returns {String[]} From c67c52367096a43d8e2368e47d5ac51c1d1938ff Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 11:52:56 +0200 Subject: [PATCH 022/162] #4874 using the new app shortcut --- src/component/Base.mjs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/component/Base.mjs b/src/component/Base.mjs index d60598e92..0b60e95e7 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -1461,8 +1461,7 @@ class Base extends CoreBase { } } - app = Neo.apps[me.appName]; - mainView = app?.mainView; + mainView = me.app?.mainView; if (mainView) { parentNodes = VDomUtil.getParentNodes(mainView.vdom, me.id); @@ -1759,7 +1758,7 @@ class Base extends CoreBase { */ onRender(data, autoMount) { let me = this, - app = Neo.apps[me.appName]; + app = me.app; me.rendering = false; @@ -1888,7 +1887,7 @@ class Base extends CoreBase { async render(mount) { let me = this, autoMount = mount || me.autoMount, - app = Neo.apps[me.appName], + app = me.app, useVdomWorker = Neo.config.useVdomWorker; me.rendering = true; @@ -2223,7 +2222,7 @@ class Base extends CoreBase { */ updateVdom(vdom=this.vdom, vnode=this.vnode, resolve, reject) { let me = this, - app = Neo.apps[me.appName], + app = me.app, mounted = me.mounted, listenerId; From 6e68118645f8c739635b38e9b18bcb9b27496c10 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 12:03:15 +0200 Subject: [PATCH 023/162] button.Base, menu.List => remove the negative offsets for showing popovers #4875 --- src/button/Base.mjs | 1 - src/menu/List.mjs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/button/Base.mjs b/src/button/Base.mjs index f7f8de7af..1daac2ad0 100644 --- a/src/button/Base.mjs +++ b/src/button/Base.mjs @@ -273,7 +273,6 @@ class Base extends Component { floating : true, hidden : true, items, - style : {left: '-10000px', top: '-10000px'}, parentComponent: me, ...me.menuListConfig }) diff --git a/src/menu/List.mjs b/src/menu/List.mjs index 31af477a2..13f5ec5cb 100644 --- a/src/menu/List.mjs +++ b/src/menu/List.mjs @@ -356,7 +356,6 @@ class List extends BaseList { parentId : Neo.apps[me.appName].mainView.id, parentIndex : store.indexOf(record), parentMenu : me, - style : {left: '-10000px', top: '-10000px'}, zIndex : me.zIndex + 1 })); From 1bc3ce2e49453818559f13c40de02c1fd666cdf1 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 12:15:18 +0200 Subject: [PATCH 024/162] #4873 examples.ConfigurationViewport: onSwitchTheme() => applying changes to the document.body --- examples/ConfigurationViewport.mjs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/examples/ConfigurationViewport.mjs b/examples/ConfigurationViewport.mjs index 9bf21d2b7..80f25447d 100644 --- a/examples/ConfigurationViewport.mjs +++ b/examples/ConfigurationViewport.mjs @@ -177,22 +177,34 @@ class ConfigurationViewport extends Viewport { onSwitchTheme(target) { let me = this, button = Neo.getComponent(me.id + (target !== 'cmp' ? '__' : '_cmp_') + 'switchThemeButton'), - cls = target === 'cmp' ? me.exampleComponent.cls : me.cls; + cls, newTheme, oldTheme; if (button.text === 'Theme Light') { + newTheme = 'neo-theme-light'; + oldTheme = 'neo-theme-dark'; + button.text = 'Theme Dark'; - NeoArray.remove(cls, 'neo-theme-dark'); - NeoArray.add(cls, 'neo-theme-light'); } else { + newTheme = 'neo-theme-dark'; + oldTheme = 'neo-theme-light'; + button.text = 'Theme Light'; - NeoArray.remove(cls, 'neo-theme-light'); - NeoArray.add(cls, 'neo-theme-dark'); } if (target === 'cmp') { + cls = me.exampleComponent.cls; + + NeoArray.remove(cls, oldTheme); + NeoArray.add(cls, newTheme); + me.exampleComponent.cls = cls; } else { - me.cls = cls; + Neo.applyDeltas(me.appName, { + cls: { + add : [newTheme], + remove: [oldTheme] + } + }) } } } From 8c49391b806068a23eabf965672e58f9114b2eae Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 12:28:57 +0200 Subject: [PATCH 025/162] component.Base: theme_ config #4876 --- examples/ConfigurationViewport.mjs | 9 ++------- src/component/Base.mjs | 28 +++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/examples/ConfigurationViewport.mjs b/examples/ConfigurationViewport.mjs index 80f25447d..1a3391a2f 100644 --- a/examples/ConfigurationViewport.mjs +++ b/examples/ConfigurationViewport.mjs @@ -177,7 +177,7 @@ class ConfigurationViewport extends Viewport { onSwitchTheme(target) { let me = this, button = Neo.getComponent(me.id + (target !== 'cmp' ? '__' : '_cmp_') + 'switchThemeButton'), - cls, newTheme, oldTheme; + newTheme, oldTheme; if (button.text === 'Theme Light') { newTheme = 'neo-theme-light'; @@ -192,12 +192,7 @@ class ConfigurationViewport extends Viewport { } if (target === 'cmp') { - cls = me.exampleComponent.cls; - - NeoArray.remove(cls, oldTheme); - NeoArray.add(cls, newTheme); - - me.exampleComponent.cls = cls; + me.exampleComponent.theme = newTheme; } else { Neo.applyDeltas(me.appName, { cls: { diff --git a/src/component/Base.mjs b/src/component/Base.mjs index 0b60e95e7..45a2a8100 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -287,6 +287,12 @@ class Base extends CoreBase { * @member {Object} style_=null */ style_: null, + /** + * You can pass an used theme directly to any component, + * to style specific component trees differently from your main view. + * @member {String|null} theme_=null + */ + theme_: null, /** * Add tooltip config objects * See tooltip/Base.mjs @@ -294,8 +300,7 @@ class Base extends CoreBase { */ tooltips_: null, /** - * Add 'primary' and other attributes to make it - * an outstanding design + * Add 'primary' and other attributes to make it an outstanding design * @member {String|null} ui_=null */ ui_: null, @@ -736,6 +741,23 @@ class Base extends CoreBase { } } + /** + * Triggered after the html config got changed + * @param {String|null} value + * @param {String|null} oldValue + * @protected + */ + afterSetTheme(value, oldValue) { + if (value || oldValue !== undefined) { + let cls = this.cls; + + NeoArray.remove(cls, oldValue); + value && NeoArray.add(cls, value); + + this.cls = cls + } + } + /** * Triggered after the tooltips config got changed * @param {Boolean} value @@ -1453,7 +1475,7 @@ class Base extends CoreBase { getTheme() { let me = this, themeMatch = 'neo-theme-', - app, mainView, parentNodes; + mainView, parentNodes; for (const item of me.cls || []) { if (item.startsWith(themeMatch)) { From 007736f1ea236b1778ceb6b19f075ba158c04d0c Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 12:34:59 +0200 Subject: [PATCH 026/162] button.Base: afterSetTheme() => update the related list theme #4877 --- src/button/Base.mjs | 15 +++++++++++++++ src/component/Base.mjs | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/button/Base.mjs b/src/button/Base.mjs index 1daac2ad0..660164dd7 100644 --- a/src/button/Base.mjs +++ b/src/button/Base.mjs @@ -274,6 +274,7 @@ class Base extends Component { hidden : true, items, parentComponent: me, + theme : me.theme, ...me.menuListConfig }) }) @@ -293,6 +294,20 @@ class Base extends Component { this.cls = cls; } + /** + * Triggered after the theme config got changed + * @param {String|null} value + * @param {String|null} oldValue + * @protected + */ + afterSetTheme(value, oldValue) { + super.afterSetTheme(value, oldValue); + + if (this.menuList) { + this.menuList.theme = value + } + } + /** * Triggered after the text config got changed * @param {String|null} value diff --git a/src/component/Base.mjs b/src/component/Base.mjs index 45a2a8100..7847e769e 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -742,7 +742,7 @@ class Base extends CoreBase { } /** - * Triggered after the html config got changed + * Triggered after the theme config got changed * @param {String|null} value * @param {String|null} oldValue * @protected From ab6e0e4f4570b7a97d682f9e2e30642b9db3fb84 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 12:53:35 +0200 Subject: [PATCH 027/162] menu.List: afterSetTheme() => pass the theme to child menus #4878 --- src/component/Base.mjs | 2 +- src/menu/List.mjs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/component/Base.mjs b/src/component/Base.mjs index 7847e769e..df05e4158 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -1924,7 +1924,7 @@ class Base extends CoreBase { delete me.vdom.removeDom; me._needsVdomUpdate = false; - me.afterSetNeedsVdomUpdate?.(false, true) + me.afterSetNeedsVdomUpdate?.(false, true); const data = await Neo.vdom.Helper.create({ appName : me.appName, diff --git a/src/menu/List.mjs b/src/menu/List.mjs index 13f5ec5cb..aa2acf856 100644 --- a/src/menu/List.mjs +++ b/src/menu/List.mjs @@ -148,6 +148,20 @@ class List extends BaseList { } } + /** + * Triggered after the theme config got changed + * @param {String|null} value + * @param {String|null} oldValue + * @protected + */ + afterSetTheme(value, oldValue) { + super.afterSetTheme(value, oldValue); + + Object.values(this.subMenuMap || {}).forEach(menu => { + menu.theme = value + }) + } + /** * Triggered after the zIndex config got changed * @param {Number} value @@ -356,6 +370,7 @@ class List extends BaseList { parentId : Neo.apps[me.appName].mainView.id, parentIndex : store.indexOf(record), parentMenu : me, + theme : me.theme, zIndex : me.zIndex + 1 })); From df08dc1dc4172de583c31b676cd933bbdaffaa2d Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 12:59:07 +0200 Subject: [PATCH 028/162] form.field.Picker: afterSetTheme() => adjust the picker #4879 --- src/form/field/Picker.mjs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/form/field/Picker.mjs b/src/form/field/Picker.mjs index 16e3a8f51..1c2adacef 100644 --- a/src/form/field/Picker.mjs +++ b/src/form/field/Picker.mjs @@ -137,6 +137,20 @@ class Picker extends Text { super.afterSetMounted(value, oldValue); } + /** + * Triggered after the theme config got changed + * @param {String|null} value + * @param {String|null} oldValue + * @protected + */ + afterSetTheme(value, oldValue) { + super.afterSetTheme(value, oldValue); + + if (this.picker) { + this.picker.theme = value + } + } + /** * @returns {Neo.container.Base} */ @@ -161,6 +175,7 @@ class Picker extends Text { id : me.getPickerId(), items : pickerComponent ? [pickerComponent] : [], maxHeight: me.pickerMaxHeight, + theme : me.theme, vdom : {cn: [], 'aria-activedescendant': me.id, tabIndex: -1}, width : pickerWidth, ...me.pickerConfig, From fc1c026b191528a44b461b14fe08ff26b346fce6 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 13:19:08 +0200 Subject: [PATCH 029/162] remote method calls: ensure the appName (window identifier) is present #4880 --- src/container/Dialog.mjs | 6 +++--- src/form/field/Picker.mjs | 2 ++ src/form/field/Time.mjs | 16 ++++++++-------- src/form/field/trigger/CopyToClipboard.mjs | 6 +++++- src/grid/View.mjs | 7 +++++-- src/list/Base.mjs | 9 ++++++--- src/list/plugin/Animate.mjs | 6 +++--- src/table/Container.mjs | 4 ++-- src/util/Css.mjs | 14 ++++++-------- 9 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/container/Dialog.mjs b/src/container/Dialog.mjs index 857da2e68..5b2a99e9c 100644 --- a/src/container/Dialog.mjs +++ b/src/container/Dialog.mjs @@ -114,9 +114,9 @@ class Dialog extends Base { let me = this; Neo.main.addon.Dialog.close({ - id: me.id, - appName: me.appName - }); + appName: me.appName, + id : me.id + }) } /** diff --git a/src/form/field/Picker.mjs b/src/form/field/Picker.mjs index 1c2adacef..8ad2bf039 100644 --- a/src/form/field/Picker.mjs +++ b/src/form/field/Picker.mjs @@ -259,6 +259,7 @@ class Picker extends Text { me.pickerIsMounted = false; Neo.main.addon.ScrollSync.unregister({ + appName : me.appName, sourceId: me.id, targetId: picker.id }) @@ -355,6 +356,7 @@ class Picker extends Text { picker.render(true); Neo.main.addon.ScrollSync.register({ + appName : me.appName, sourceId: me.id, targetId: picker.id }) diff --git a/src/form/field/Time.mjs b/src/form/field/Time.mjs index 600e3dd91..1090f219d 100644 --- a/src/form/field/Time.mjs +++ b/src/form/field/Time.mjs @@ -296,12 +296,11 @@ class Time extends Picker { let me = this; Neo.main.DomAccess.focus({ - id: me.getInputElId() + appName: me.appName, + id : me.getInputElId() }).then(() => { - if (callback) { - callback.apply(me); - } - }); + callback?.apply(me) + }) } /** @@ -410,11 +409,12 @@ class Time extends Picker { list.selectionModel.select(id); if (!preventFocus) { - list.focus(id); + list.focus(id) } else { Neo.main.DomAccess.scrollIntoView({ - id: id - }); + appName: me.appName, + id : id + }) } } } diff --git a/src/form/field/trigger/CopyToClipboard.mjs b/src/form/field/trigger/CopyToClipboard.mjs index 23dd87197..96d31e421 100644 --- a/src/form/field/trigger/CopyToClipboard.mjs +++ b/src/form/field/trigger/CopyToClipboard.mjs @@ -89,10 +89,14 @@ class CopyToClipboard extends Base { * @param {Object} data */ onTriggerClick(data) { + let me = this; + Neo.main.DomAccess.selectNode({ - id: this.field.getInputEl().id + appName: me.appName, + id : me.field.getInputEl().id }).then(data => { Neo.main.DomAccess.execCommand({ + appName: me.appName, command: 'copy' }); }); diff --git a/src/grid/View.mjs b/src/grid/View.mjs index 60c7e981b..e041c652c 100644 --- a/src/grid/View.mjs +++ b/src/grid/View.mjs @@ -153,9 +153,12 @@ class View extends Component { me.promiseUpdate().then(() => { if (selectedRows?.length > 0) { // this logic only works for selection.table.RowModel - Neo.main.DomAccess.scrollToTableRow({id: selectedRows[0]}); + Neo.main.DomAccess.scrollToTableRow({ + appName: me.appName, + id : selectedRows[0] + }) } - }); + }) } /** diff --git a/src/list/Base.mjs b/src/list/Base.mjs index dc9769f32..5f47f7d42 100644 --- a/src/list/Base.mjs +++ b/src/list/Base.mjs @@ -511,10 +511,13 @@ class Base extends Component { focus(id) { super.focus(id); - id && this.scrollIntoViewOnFocus && Neo.main.DomAccess.scrollIntoView({ + let me = this; + + id && me.scrollIntoViewOnFocus && Neo.main.DomAccess.scrollIntoView({ + appName : me.appName, behavior: 'auto', - id : id || this.id - }); + id : id || me.id + }) } /** diff --git a/src/list/plugin/Animate.mjs b/src/list/plugin/Animate.mjs index 1414c307b..e725604e2 100644 --- a/src/list/plugin/Animate.mjs +++ b/src/list/plugin/Animate.mjs @@ -418,13 +418,13 @@ class Animate extends Base { easing = me.transitionEasing, id = me.owner.id; - deleteRule && CssUtil.deleteRules(`#${id} .neo-list-item`); + deleteRule && CssUtil.deleteRules(me.appName, `#${id} .neo-list-item`); - CssUtil.insertRules([ + CssUtil.insertRules(me.appName, [ `#${id} .neo-list-item {`, `transition: opacity ${duration}ms ${easing}, transform ${duration}ms ${easing}`, '}' - ].join('')); + ].join('')) } } diff --git a/src/table/Container.mjs b/src/table/Container.mjs index c1b077194..956d1ee91 100644 --- a/src/table/Container.mjs +++ b/src/table/Container.mjs @@ -1,6 +1,6 @@ import BaseContainer from '../container/Base.mjs'; import ClassSystemUtil from '../util/ClassSystem.mjs'; -import Css from '../util/Css.mjs'; +import CssUtil from '../util/Css.mjs'; import NeoArray from '../util/Array.mjs'; import RowModel from '../selection/table/RowModel.mjs'; import Store from '../data/Store.mjs'; @@ -207,7 +207,7 @@ class Container extends BaseContainer { cssRules.push('#' + id + '::-webkit-scrollbar-track:horizontal {margin-right: ' + me.dockRightMargin + 'px;}'); } if (cssRules.length > 0) { - Css.insertRules(cssRules); + CssUtil.insertRules(me.appName, cssRules); } me.scrollbarsCssApplied = true; diff --git a/src/util/Css.mjs b/src/util/Css.mjs index 83b3a8f9f..a0ce2ced0 100644 --- a/src/util/Css.mjs +++ b/src/util/Css.mjs @@ -15,29 +15,27 @@ class Css extends Base { /** * Pass the selectorText of the rules which you want to remove + * @param {String} appName * @param {String[]|String} rules */ - static deleteRules(rules) { + static deleteRules(appName, rules) { if (!Array.isArray(rules)) { rules = [rules]; } - Neo.main.addon.Stylesheet.deleteCssRules({ - rules: rules - }); + Neo.main.addon.Stylesheet.deleteCssRules({appName, rules}) } /** + * @param {String} appName * @param {String[]|String} rules */ - static insertRules(rules) { + static insertRules(appName, rules) { if (!Array.isArray(rules)) { rules = [rules]; } - Neo.main.addon.Stylesheet.insertCssRules({ - rules: rules - }); + Neo.main.addon.Stylesheet.insertCssRules({appName, rules}) } } From 069bbdbe4fd5e8273b12e5edf2bf2ba5bd678368 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 15:28:49 +0200 Subject: [PATCH 030/162] list.Base: method order --- src/list/Base.mjs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/list/Base.mjs b/src/list/Base.mjs index 5f47f7d42..8a7cc37a0 100644 --- a/src/list/Base.mjs +++ b/src/list/Base.mjs @@ -545,14 +545,6 @@ class Base extends Component { return headerlessIndex + delta; } - /** - * @param {Number|String} recordId - * @returns {String} - */ - getItemId(recordId) { - return `${this.id}__${recordId}`; - } - /** * Returns the index of a list item excluding item headers * @param {Number} index @@ -572,6 +564,14 @@ class Base extends Component { return headerlessIndex; } + /** + * @param {Number|String} recordId + * @returns {String} + */ + getItemId(recordId) { + return `${this.id}__${recordId}`; + } + /** * @param {String} vnodeId * @returns {String|Number} itemId From 0ca251dcd0655ad6600a4609c4eca29d7d0a0e28 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 15:58:55 +0200 Subject: [PATCH 031/162] form.Container: adjustTreeLeaves() => do not use assign: true for arrays #4881 --- src/form/Container.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/form/Container.mjs b/src/form/Container.mjs index 035ad10d5..de5e3b2f8 100644 --- a/src/form/Container.mjs +++ b/src/form/Container.mjs @@ -44,6 +44,8 @@ class Container extends BaseContainer { type = Neo.typeOf(value); if (type === 'Array') { + assign = false; + value.forEach(item => { if (Neo.typeOf(item) === 'Object') { this.adjustTreeLeaves(item, configName) From bca7a067342c350c1d092a820f30fa11e1941506 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 15:59:32 +0200 Subject: [PATCH 032/162] v6.4.0 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index f86cbb5bd..915c76c40 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.3.11' + * @member {String} version='6.4.0' */ - version: '6.3.11' + version: '6.4.0' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index f86cbb5bd..915c76c40 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.3.11' + * @member {String} version='6.4.0' */ - version: '6.3.11' + version: '6.4.0' } /** diff --git a/package.json b/package.json index 9b7e825d0..90f449d29 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.3.11", + "version": "6.4.0", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index 412a0418f..24c1c57f3 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.3.11' + * @default '6.4.0' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.3.11' + version: '6.4.0' }; Object.assign(DefaultConfig, { From 7b919288517b7cecc0c2d937b8f7b4f397810751 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 16:45:01 +0200 Subject: [PATCH 033/162] menu.List: re-add onItemClick() #4883 --- src/menu/List.mjs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/menu/List.mjs b/src/menu/List.mjs index aa2acf856..8e5e9586b 100644 --- a/src/menu/List.mjs +++ b/src/menu/List.mjs @@ -291,6 +291,16 @@ class List extends BaseList { } } + /** + * @param {Object} node + * @param {Object} data + */ + onItemClick(node, data) { + super.onItemClick(node, data); + + this.onKeyDownEnter(node.id) + } + /** * @param {String} nodeId */ @@ -374,8 +384,10 @@ class List extends BaseList { zIndex : me.zIndex + 1 })); - me.activeSubMenu = subMenu; - subMenu.render(true) + if (me.activeSubMenu !== subMenu) { + me.activeSubMenu = subMenu; + subMenu.render(true) + } } /** From 07e07b2496366f8851d1379e7b087c1dedbb293e Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 16:45:38 +0200 Subject: [PATCH 034/162] v6.4.1 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index 915c76c40..009b4874f 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.4.0' + * @member {String} version='6.4.1' */ - version: '6.4.0' + version: '6.4.1' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index 915c76c40..009b4874f 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.4.0' + * @member {String} version='6.4.1' */ - version: '6.4.0' + version: '6.4.1' } /** diff --git a/package.json b/package.json index 90f449d29..60f1f7e44 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.4.0", + "version": "6.4.1", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index 24c1c57f3..dfe29be18 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.4.0' + * @default '6.4.1' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.4.0' + version: '6.4.1' }; Object.assign(DefaultConfig, { From 8d2285a92858fe02fbf1efff3852dd959bba5926 Mon Sep 17 00:00:00 2001 From: Tobias Uhlig Date: Mon, 11 Sep 2023 16:57:14 +0200 Subject: [PATCH 035/162] v6.4.1 (#4884) (#4885) * menu.List: re-add onItemClick() #4883 * v6.4.1 From 80d82d49c71d68c6ab688d3368dd9670ac5077ee Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 18:02:20 +0200 Subject: [PATCH 036/162] form.field.Checkbox: update the styling for firefox (hiding the default icon) #4886 --- resources/scss/src/form/field/CheckBox.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/scss/src/form/field/CheckBox.scss b/resources/scss/src/form/field/CheckBox.scss index 393911eee..83bb1c00b 100644 --- a/resources/scss/src/form/field/CheckBox.scss +++ b/resources/scss/src/form/field/CheckBox.scss @@ -10,8 +10,9 @@ } .neo-checkbox-input { - margin: 0; - width: 0; // using display: none would break the keynav + appearance: none; + margin : 0; + width : 0; // using display: none would break the keynav &:checked { +.neo-checkbox-icon { From c9f44126d8cc11236ec94af8639b50205958da26 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 18:28:18 +0200 Subject: [PATCH 037/162] form.field.Date: hide the default trigger in Firefox > v109 #4887 --- resources/scss/src/form/field/Date.scss | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/resources/scss/src/form/field/Date.scss b/resources/scss/src/form/field/Date.scss index d3fef1daf..d2e9510ce 100644 --- a/resources/scss/src/form/field/Date.scss +++ b/resources/scss/src/form/field/Date.scss @@ -6,16 +6,20 @@ input::-webkit-inner-spin-button, input::-webkit-calendar-picker-indicator, input::-webkit-outer-spin-button { - display : none; - margin : 0; - -webkit-appearance: none; + appearance: none; + display : none; + margin : 0; } - // Firefox => no luck => https://gitlab.com/tobiu/neoteric/issues/198 - ::-moz-datetime-reset-button { - display: none; - } .datetime-reset-button { display: none; } -} \ No newline at end of file +} + +@-moz-document url-prefix() { + .neo-datefield { + .neo-textfield-input { + clip-path: inset(0 2em 0 0); + } + } +} From 993eb0360ae080bd6ddc1aa20bd84169f74b4965 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 18:31:06 +0200 Subject: [PATCH 038/162] v6.4.2 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index 009b4874f..4d520cfdd 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.4.1' + * @member {String} version='6.4.2' */ - version: '6.4.1' + version: '6.4.2' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index 009b4874f..4d520cfdd 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.4.1' + * @member {String} version='6.4.2' */ - version: '6.4.1' + version: '6.4.2' } /** diff --git a/package.json b/package.json index 60f1f7e44..8a7ac4c8e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.4.1", + "version": "6.4.2", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index dfe29be18..0548a9f3a 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.4.1' + * @default '6.4.2' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.4.1' + version: '6.4.2' }; Object.assign(DefaultConfig, { From 47f04ca2a758251b68e269c1ead027383c2aaa41 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 20:53:29 +0200 Subject: [PATCH 039/162] Neo.worker.App: setConfigs() remote method #4889 --- src/worker/App.mjs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/worker/App.mjs b/src/worker/App.mjs index ef9bb7d72..35c5f502b 100644 --- a/src/worker/App.mjs +++ b/src/worker/App.mjs @@ -28,7 +28,8 @@ class App extends Base { remote: { main: [ 'createNeoInstance', - 'destroyNeoInstance' + 'destroyNeoInstance', + 'setConfigs' ] }, /** @@ -386,6 +387,24 @@ class App extends Base { me.themeFilesCache = [] } + /** + * Set configs of any app realm based Neo instance from main + * @param {Object} data + * @param {String} data.id + */ + setConfigs(data) { + let instance = Neo.get(data.id); + + if (instance) { + delete data.id; + instance.set(data); + + return true + } + + return false + } + /** * @param {Object} data * @param {String} data.key From 97662e95adfb2400079856f5cb2070fb948c5087 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 20:54:55 +0200 Subject: [PATCH 040/162] v6.4.3 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index 4d520cfdd..29b9849d9 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.4.2' + * @member {String} version='6.4.3' */ - version: '6.4.2' + version: '6.4.3' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index 4d520cfdd..29b9849d9 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.4.2' + * @member {String} version='6.4.3' */ - version: '6.4.2' + version: '6.4.3' } /** diff --git a/package.json b/package.json index 8a7ac4c8e..de4f46eac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.4.2", + "version": "6.4.3", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index 0548a9f3a..8b5df76a7 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.4.2' + * @default '6.4.3' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.4.2' + version: '6.4.3' }; Object.assign(DefaultConfig, { From 19d40abf60b47e040979263bca87477f3f54d508 Mon Sep 17 00:00:00 2001 From: Tobias Uhlig Date: Mon, 11 Sep 2023 20:58:10 +0200 Subject: [PATCH 041/162] v6.4.3 (#4890) (#4891) * Neo.worker.App: setConfigs() remote method #4889 * v6.4.3 From 0cac92f43cfe339012da13b226a9a33787e7ffc9 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 11 Sep 2023 22:40:25 +0200 Subject: [PATCH 042/162] main.DomAccess: minor cleanup --- src/main/DomAccess.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/DomAccess.mjs b/src/main/DomAccess.mjs index fd11d1ee8..b5d771f4d 100644 --- a/src/main/DomAccess.mjs +++ b/src/main/DomAccess.mjs @@ -352,7 +352,7 @@ class DomAccess extends Base { if (node) { rect = node.getBoundingClientRect(); style = node.ownerDocument.defaultView.getComputedStyle(node); - minWidth = style.getPropertyValue('min-width'), + minWidth = style.getPropertyValue('min-width'); minHeight = style.getPropertyValue('min-height'); // DomRect does not support spreading => {...DomRect} => {} From cc5f937f53980169934b982b95270d40f29d3555 Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 12 Sep 2023 07:46:34 +0200 Subject: [PATCH 043/162] calendar.view.EditEventContainer: regression bug => using form.Container.getField() #4895 --- src/calendar/view/EditEventContainer.mjs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/calendar/view/EditEventContainer.mjs b/src/calendar/view/EditEventContainer.mjs index 9de2bd33a..3f71f6759 100644 --- a/src/calendar/view/EditEventContainer.mjs +++ b/src/calendar/view/EditEventContainer.mjs @@ -81,7 +81,7 @@ class EditEventContainer extends FormContainer { super.construct(config); // focus trap, see: https://github.com/neomjs/neo/issues/2306 - this.vdom.tabIndex = -1; + this.vdom.tabIndex = -1 } /** @@ -92,7 +92,7 @@ class EditEventContainer extends FormContainer { */ afterSetMounted(value, oldValue) { super.afterSetMounted(value, oldValue); - value && this.getField('title').focus(); + value && this.getField('title').then(field => field.focus()) } /** @@ -106,17 +106,17 @@ class EditEventContainer extends FormContainer { let me = this, timeFormat = me.intlFormat_time; - me.getField('endTime') .minValue = me.getEndTimeMinValue(value); - me.getField('startTime').maxValue = me.getStartTimeMaxValue(value); + me.getField('endTime') .then(field => field.minValue = me.getEndTimeMinValue(value)); + me.getField('startTime').then(field => field.maxValue = me.getStartTimeMaxValue(value)); me.reset({ calendarId: value.calendarId, endTime : timeFormat.format(value.endDate), startTime : timeFormat.format(value.startDate), title : value.title - }); + }) } else if (value) { - this.createItems(); + this.createItems() } } @@ -205,7 +205,7 @@ class EditEventContainer extends FormContainer { text : 'Delete' }]; - super.createItems(); + super.createItems() } } @@ -261,7 +261,7 @@ class EditEventContainer extends FormContainer { // we need a short delay, since a TimeField picker could be open setTimeout(() => { - me.mounted && me.unmount(); + me.mounted && me.unmount() }, 100); } @@ -282,9 +282,9 @@ class EditEventContainer extends FormContainer { record[field] = date; if (name === 'endTime') { - me.getField('startTime').maxValue = me.getStartTimeMaxValue(record); + me.getField('startTime').then(field => field.maxValue = me.getStartTimeMaxValue(record)) } else { - me.getField('endTime') .minValue = me.getEndTimeMinValue(record); + me.getField('endTime') .then(field => field.minValue = me.getEndTimeMinValue(record)) } } From 4c171cf18cda4b277d13529e2bafabf6decc54e7 Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 12 Sep 2023 07:50:51 +0200 Subject: [PATCH 044/162] calendar.view.EditEventContainer: onFocusLeave() cleanup --- src/calendar/view/EditEventContainer.mjs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/calendar/view/EditEventContainer.mjs b/src/calendar/view/EditEventContainer.mjs index 3f71f6759..851965f3c 100644 --- a/src/calendar/view/EditEventContainer.mjs +++ b/src/calendar/view/EditEventContainer.mjs @@ -256,13 +256,13 @@ class EditEventContainer extends FormContainer { /** * @param {Object} data */ - onFocusLeave(data) { + async onFocusLeave(data) { let me = this; // we need a short delay, since a TimeField picker could be open - setTimeout(() => { - me.mounted && me.unmount() - }, 100); + await me.timeout(100); + + me.mounted && me.unmount() } /** From 19bd2f89e2d049b7ace4d6f65e0de3465a6423c4 Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 12 Sep 2023 08:12:25 +0200 Subject: [PATCH 045/162] #4893 form.Container: adjustTreeLeaves() => passing all field paths to the method --- src/form/Container.mjs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/form/Container.mjs b/src/form/Container.mjs index de5e3b2f8..ccc219bb5 100644 --- a/src/form/Container.mjs +++ b/src/form/Container.mjs @@ -1,6 +1,7 @@ import BaseContainer from '../container/Base.mjs'; import BaseField from '../form/field/Base.mjs'; import ComponentManager from '../manager/Component.mjs'; +import NeoArray from '../util/Array.mjs'; /** * @class Neo.form.Container @@ -35,8 +36,9 @@ class Container extends BaseContainer { * The logic assumes that field config values must not be objects (separation between the key & value realm). * @param {Object} values * @param {String} configName + * @param {String[]} fieldPaths */ - static adjustTreeLeaves(values={}, configName) { + static adjustTreeLeaves(values={}, configName, fieldPaths) { let assign,type; Object.entries(values).forEach(([key, value]) => { @@ -48,12 +50,12 @@ class Container extends BaseContainer { value.forEach(item => { if (Neo.typeOf(item) === 'Object') { - this.adjustTreeLeaves(item, configName) + this.adjustTreeLeaves(item, configName, fieldPaths) } }) } else if (type === 'Object') { assign = false; - this.adjustTreeLeaves(value, configName) + this.adjustTreeLeaves(value, configName, fieldPaths) } if (assign) { @@ -307,7 +309,14 @@ class Container extends BaseContainer { * @param {Boolean} suspendEvents=false */ async setValues(values={}, suspendEvents=false) { - Container.adjustTreeLeaves(values, 'value'); + let fields = await this.getFields(), + fieldPaths = []; + + // Grouped CheckBoxes & Radios can have the same path + // => using NeoArray to ensure they only get added once + fields.map(field => NeoArray.add(fieldPaths, field.getPath())); + + Container.adjustTreeLeaves(values, 'value', fieldPaths); await this.setConfigs(values, suspendEvents) } From e46089432cb09595bcdf9ae568b1aed7772a0aad Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 12 Sep 2023 14:14:21 +0200 Subject: [PATCH 046/162] docs build throws a JS error #4896 --- src/util/Rectangle.mjs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/util/Rectangle.mjs b/src/util/Rectangle.mjs index 9d41d921c..06f5b00f4 100644 --- a/src/util/Rectangle.mjs +++ b/src/util/Rectangle.mjs @@ -273,11 +273,11 @@ export default class Rectangle extends DOMRect { top = Math.max(me.y, other.y), right = Math.min(me.x + me.width, other.x + other.width), bottom = Math.min(me.y + me.height, other.y + other.height); - + if (left >= right || top >= bottom) { return false; } - + return new Rectangle(left, top, right - left, bottom - top); } // We're dealing with a point here - zero dimensions @@ -300,8 +300,8 @@ export default class Rectangle extends DOMRect { /** * Returns a clone of this Rectangle expanded according to the edges array. - * @param {Number}Number[]} edges - * @returns + * @param {Number}Number[]} edges + * @returns {Rectangle} */ expand(edges) { edges = parseEdgeValue(edges); @@ -323,7 +323,7 @@ export default class Rectangle extends DOMRect { /** * Returns `true` if this Rectangle completely contains the other Rectangle - * @param {Rectangle} other + * @param {Rectangle} other */ contains(other) { return this.constructor.includes(this, other); @@ -391,7 +391,7 @@ export default class Rectangle extends DOMRect { myPoint = result.getAnchorPoint(edges.ourEdgeZone, edges.ourEdgeOffset, edges.ourEdgeUnit), targetPoint = targetRect.getAnchorPoint(edges.theirEdgeZone, edges.theirEdgeOffset, edges.theirEdgeUnit, targetMargin), vector = [targetPoint[0] - myPoint[0], targetPoint[1] - myPoint[1]]; - + result = result.moveBy(vector); // A useful property in the resulting rectangle which specifies which zone of the target @@ -566,7 +566,7 @@ export default class Rectangle extends DOMRect { } equals(other) { - return other instanceof DOMRect && + return other instanceof DOMRect && other.x === this.x && other.y === this.y && other.height === this.height && From 79199900367ce7641c941c9c9e7963f69f8739f3 Mon Sep 17 00:00:00 2001 From: Nige White Date: Tue, 12 Sep 2023 15:25:25 +0200 Subject: [PATCH 047/162] Fix fallback aligning, clipping and moving on resize-caused move (#4894) * Fix fallback aligning, clipping and moving on resize-caused move * Fix --- examples/table/container/MainContainer.mjs | 66 +++++++++++++++++++--- resources/scss/src/component/Base.scss | 9 +-- src/component/Base.mjs | 39 +++++++------ src/grid/View.mjs | 1 + src/main/DomAccess.mjs | 38 +++++++++---- src/table/View.mjs | 1 + src/util/Rectangle.mjs | 29 +++++----- 7 files changed, 126 insertions(+), 57 deletions(-) diff --git a/examples/table/container/MainContainer.mjs b/examples/table/container/MainContainer.mjs index 37665927c..684cad3d6 100644 --- a/examples/table/container/MainContainer.mjs +++ b/examples/table/container/MainContainer.mjs @@ -83,6 +83,27 @@ class MainContainer extends ConfigurationViewport { labelText: 'sortable', listeners: {change: me.onConfigChange.bind(me, 'sortable')}, style : {marginTop: '10px'} + }, { + module : Checkbox, + checked : false, + labelText: 'Fit width', + listeners: { + change({ value }) { + const { style } = me.exampleComponent; + + if (value) { + style.width = '100%'; + style.tableLayout = 'fixed'; + } else { + style.width = ''; + style.tableLayout = ''; + } + + me.exampleComponent.style = style; + me.exampleComponent.update(); + } + }, + style : {marginTop: '10px'} }]; } @@ -102,16 +123,43 @@ class MainContainer extends ConfigurationViewport { {dataField: 'country', text: 'Country'}, { text: 'Edit Action', - renderer: data => { - let button = Neo.create({ - module : Button, - appName : this.appName, - handler : this.editButtonHandler, - parentId: 'myTableStoreContainer', - text : 'Edit' - }); + renderer: ({ column, index }) => { + const + widgetId = `${column.id}-widget-${index}`, + button = (column.widgetMap || (column.widgetMap = {}))[widgetId] || (column.widgetMap[widgetId] = Neo.create({ + module : Button, + appName : this.appName, + handler : this.editButtonHandler, + parentId: 'myTableStoreContainer', + text : 'Edit' + })); + + return button.vdom; + } + }, + { + text : 'Menu', + renderer({ column, record, index }) { + const + widgetId = `${column.id}-widget-${index}`, + button = (column.widgetMap || (column.widgetMap = {}))[widgetId] || (column.widgetMap[widgetId] = Neo.create('Neo.button.Base', { + ntype : 'button', + appName : this.appName, + text : '\u22ee', + menu : { + items : [{ + text : 'Menu option 1' + }, { + text : 'Menu option 2' + }, { + text : 'Menu option 3' + }, { + text : 'Menu option 4' + }] + } + })); - return button.vdom + return button.vdom; } } ] diff --git a/resources/scss/src/component/Base.scss b/resources/scss/src/component/Base.scss index c80781965..8a9620ad2 100644 --- a/resources/scss/src/component/Base.scss +++ b/resources/scss/src/component/Base.scss @@ -1,8 +1,9 @@ .neo-floating { - top : -10000px; - left : -10000px; - position : fixed; - z-index : 1000; + top : -10000px; + left : -10000px; + position : fixed; + z-index : 1000; + background-color : var(--neo-background-color); } // Shadow at the top diff --git a/src/component/Base.mjs b/src/component/Base.mjs index df05e4158..0804ad2e2 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -1351,26 +1351,31 @@ class Base extends CoreBase { * @returns {Promise} */ async getDomRect(id=this.id, appName=this.appName) { - const - { - x, - y, - width, - height, - minWidth, - minHeight - } = await Neo.main.DomAccess.getBoundingClientRect({appName, id}), - result = new Rectangle(x, y, width, height); - - if (minWidth) { - result.minWidth = minWidth; + if (Array.isArray(id)) { + return await Neo.main.DomAccess.getBoundingClientRect({appName, id}); } + else { + const + { + x, + y, + width, + height, + minWidth, + minHeight + } = await Neo.main.DomAccess.getBoundingClientRect({appName, id}), + result = new Rectangle(x, y, width, height); + + if (minWidth) { + result.minWidth = minWidth; + } - if (minHeight) { - result.minHeight = minHeight; - } + if (minHeight) { + result.minHeight = minHeight; + } - return result; + return result; + } } /** diff --git a/src/grid/View.mjs b/src/grid/View.mjs index e041c652c..108a6a115 100644 --- a/src/grid/View.mjs +++ b/src/grid/View.mjs @@ -83,6 +83,7 @@ class View extends Component { } rendererOutput = column.renderer.call(column.rendererScope || container, { + column, field: column.field, index: i, record, diff --git a/src/main/DomAccess.mjs b/src/main/DomAccess.mjs index b5d771f4d..c60a4a538 100644 --- a/src/main/DomAccess.mjs +++ b/src/main/DomAccess.mjs @@ -102,9 +102,7 @@ class DomAccess extends Base { construct(config) { super.construct(config); - const - me = this, - syncAligns = me.syncAligns.bind(me); + const me = this; if (Neo.config.renderCountDeltas) { let node; @@ -122,7 +120,7 @@ class DomAccess extends Base { // Set up our aligning callback which is called when things change which may // mean that alignments need to be updated. - me.syncAligns = () => requestAnimationFrame(syncAligns); + me.syncAligns = me.syncAligns.bind(me); } /** @@ -211,8 +209,14 @@ class DomAccess extends Base { me.resetDimensions(align); // The Rectangle's align spec target and constrainTo must be Rectangles - align.target = me.getBoundingClientRect({ id : data.targetElement = me.getElementOrBody(data.target) }); - data.offsetParent = data.targetElement.offsetParent + align.target = me.getClippedRect({ id : data.targetElement = me.getElementOrBody(data.target) }); + + if (!align.target) { + // Set the Component with id data.id to hidden : true + return Neo.worker.App.setConfigs({ id : data.id, hidden : true }); + } + + data.offsetParent = data.targetElement.offsetParent; if (constrainTo) { align.constrainTo = me.getBoundingClientRect({ id : data.constrainToElement = me.getElementOrBody(constrainTo) }); } @@ -338,13 +342,9 @@ class DomAccess extends Base { let returnData; if (Array.isArray(data.id)) { - returnData = []; - - data.id.forEach(id => { - returnData.push(this.getBoundingClientRect({id: id})); - }); + return data.id.map(id => this.getBoundingClientRect({ id })); } else { - let node = this.getElementOrBody(data.id), + let node = this.getElementOrBody(data.nodeType ? data : data.id), rect = {}, style, minWidth, minHeight; returnData = {}; @@ -373,6 +373,20 @@ class DomAccess extends Base { return returnData; } + getClippedRect(data) { + let node = this.getElement(typeof data === 'object' ? data.id : data), + { defaultView } = node.ownerDocument, + rect = this.getBoundingClientRect(node); + + for (let parentElement = node.offsetParent; rect && parentElement !== document.documentElement; parentElement = parentElement.parentElement) { + if (defaultView.getComputedStyle(parentElement).getPropertyValue('overflow') !== 'visible') { + rect = rect.intersects(this.getBoundingClientRect(parentElement)); + } + } + + return rect; + } + onDocumentMutation(mutations) { const me = this; diff --git a/src/table/View.mjs b/src/table/View.mjs index 51adb0397..09ec5acd9 100644 --- a/src/table/View.mjs +++ b/src/table/View.mjs @@ -67,6 +67,7 @@ class View extends Component { } rendererOutput = column.renderer.call(column.rendererScope || tableContainer, { + column, dataField, index, record, diff --git a/src/util/Rectangle.mjs b/src/util/Rectangle.mjs index 06f5b00f4..77d3e9f5d 100644 --- a/src/util/Rectangle.mjs +++ b/src/util/Rectangle.mjs @@ -362,10 +362,6 @@ export default class Rectangle extends DOMRect { alignTo(align) { const me = this, - { - minWidth, - minHeight - } = me, { constrainTo, // Element or Rectangle result must fit into target, // Element or Rectangle to align to @@ -453,17 +449,15 @@ export default class Rectangle extends DOMRect { edgeAlign : createReversedEdgeAlign(edges) } - // Fall back to the other two zones if we are allowed to - if (axisLock === 'flexible') { - zonesToTry.push({ - zone : zone = (alignSpec.startZone + 1) % 4, - edgeAlign : `${oppositeEdge[zoneEdges[zone]]}-${zoneEdges[zone]}` - }); - zonesToTry.push({ - zone : zone = (zone + 2) % 4, - edgeAlign : `${oppositeEdge[zoneEdges[zone]]}-${zoneEdges[zone]}` - }); - } + // Fall back to the other two zones. + zonesToTry.push({ + zone : zone = (alignSpec.startZone + 1) % 4, + edgeAlign : `${oppositeEdge[zoneEdges[zone]]}-${zoneEdges[zone]}` + }); + zonesToTry.push({ + zone : zone = (zone + 2) % 4, + edgeAlign : `${oppositeEdge[zoneEdges[zone]]}-${zoneEdges[zone]}` + }); } else { // go through the other zones in order @@ -536,6 +530,11 @@ export default class Rectangle extends DOMRect { } } + // Add the configurable finishing touch. + if (offset) { + result.moveBy(offset); + } + return result; } From 6200d851445f1806cff5f5d913d187e77d34319d Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 12 Sep 2023 15:26:18 +0200 Subject: [PATCH 048/162] form.Container: adjustTreeLeaves() => sharper separation of the key & value realms #4893 --- apps/form/view/pages/Page4.mjs | 2 +- src/form/Container.mjs | 35 +++++++++++++++++++--------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/apps/form/view/pages/Page4.mjs b/apps/form/view/pages/Page4.mjs index 40dfe41ca..719c3569c 100644 --- a/apps/form/view/pages/Page4.mjs +++ b/apps/form/view/pages/Page4.mjs @@ -20,7 +20,7 @@ class Page4 extends FormPageContainer { groupRequired : true, labelText : null, labelWidth : 70, - name : 'fruits', + name : 'my.fruits[0].basket', showErrorTexts: false }, /** diff --git a/src/form/Container.mjs b/src/form/Container.mjs index ccc219bb5..f216dd653 100644 --- a/src/form/Container.mjs +++ b/src/form/Container.mjs @@ -37,25 +37,28 @@ class Container extends BaseContainer { * @param {Object} values * @param {String} configName * @param {String[]} fieldPaths + * @param {String} currentPath='' */ - static adjustTreeLeaves(values={}, configName, fieldPaths) { - let assign,type; + static adjustTreeLeaves(values={}, configName, fieldPaths, currentPath='') { + let assign, newPath, type; Object.entries(values).forEach(([key, value]) => { - assign = true; - type = Neo.typeOf(value); + assign = true; + newPath = currentPath === '' ? key : `${currentPath}.${key}`; + type = Neo.typeOf(value); - if (type === 'Array') { - assign = false; + if (type === 'Array' || type === 'Object') { + assign = fieldPaths.includes(newPath); - value.forEach(item => { - if (Neo.typeOf(item) === 'Object') { - this.adjustTreeLeaves(item, configName, fieldPaths) - } - }) - } else if (type === 'Object') { - assign = false; - this.adjustTreeLeaves(value, configName, fieldPaths) + if (type === 'Array') { + value.forEach((item, index) => { + if (Neo.typeOf(item) === 'Object') { + this.adjustTreeLeaves(item, configName, fieldPaths, `${newPath}[${index}]`) + } + }) + } else if (type === 'Object') { + this.adjustTreeLeaves(value, configName, fieldPaths, newPath) + } } if (assign) { @@ -316,8 +319,10 @@ class Container extends BaseContainer { // => using NeoArray to ensure they only get added once fields.map(field => NeoArray.add(fieldPaths, field.getPath())); - Container.adjustTreeLeaves(values, 'value', fieldPaths); + values = Neo.clone(values, true); + Container.adjustTreeLeaves(values, 'value', fieldPaths); +console.log(values); await this.setConfigs(values, suspendEvents) } From 41db2d67af3d4ce405aac761a43bd17c8f246886 Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 12 Sep 2023 15:27:14 +0200 Subject: [PATCH 049/162] #4893 removed a testing log --- src/form/Container.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/form/Container.mjs b/src/form/Container.mjs index f216dd653..237a83972 100644 --- a/src/form/Container.mjs +++ b/src/form/Container.mjs @@ -322,7 +322,7 @@ class Container extends BaseContainer { values = Neo.clone(values, true); Container.adjustTreeLeaves(values, 'value', fieldPaths); -console.log(values); + await this.setConfigs(values, suspendEvents) } From a45aa9c51a77555f666e3b7ddf85cdf6b233dd36 Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 12 Sep 2023 15:31:30 +0200 Subject: [PATCH 050/162] component.Base: not registering needed updates to parents before a vnode is there => mounted true #4897 --- src/component/Base.mjs | 56 ++++++++++++++---------------------------- 1 file changed, 19 insertions(+), 37 deletions(-) diff --git a/src/component/Base.mjs b/src/component/Base.mjs index 0804ad2e2..877d8816d 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -349,23 +349,6 @@ class Base extends CoreBase { return Neo.apps[this.appName] || null } - /** - * Convenience method - * @returns {Boolean} - */ - get isVdomUpdating() { - // The VDOM is being updated if we have the promise that executeVdomUpdate uses - return Boolean(this.vdomUpdate); - } - // Allow the Component to be set to the isVdomUpdating state - set isVdomUpdating(isVdomUpdating) { - isVdomUpdating = Boolean(isVdomUpdating); - - if (isVdomUpdating !== this.isVdomUpdating) { - this.vdomUpdate = isVdomUpdating; - } - } - /** * Apply component based listeners * @member {Object} listeners={} @@ -967,7 +950,7 @@ class Base extends CoreBase { // Just a simple 't-b' if (typeof align === 'string') { align = { - edgeAlign : align + edgeAlign: align }; } // merge the incoming alignment specification into the configured default @@ -987,8 +970,8 @@ class Base extends CoreBase { /** * Triggered before the controller config gets changed. * Creates a controller.Component instance if needed. - * @param {Object} value - * @param {Object} oldValue + * @param {Neo.controller.Component|Object} value + * @param {Neo.controller.Component|null} oldValue * @returns {Neo.controller.Component} * @protected */ @@ -1105,7 +1088,14 @@ class Base extends CoreBase { return (Neo.isNumber(oldValue) && oldValue > 0) ? (oldValue - 1) : 0 } - beforeSetStyle(value) { + /** + * Triggered before the style config gets changed. + * @param {Object} value + * @param {Object} oldValue + * @returns {Object} + * @protected + */ + beforeSetStyle(value, oldValue) { let me = this; if (typeof value === 'object') { @@ -1241,26 +1231,20 @@ class Base extends CoreBase { opts.appName = me.appName } - /** - * If a VDOM update is in flight, this is the Promise that will resolve when - * the update is completed. - * @member {Promise|null} vdomUpdate - * @protected - */ - me.vdomUpdate = Neo.vdom.Helper.update(opts); + me.isVdomUpdating = true; // we can not set the config directly => it could already be false, // and we still want to pass it further into subtrees me._needsVdomUpdate = false; me.afterSetNeedsVdomUpdate?.(false, true) - me.vdomUpdate.catch(err => { - me.vdomUpdate = null; + Neo.vdom.Helper.update(opts).catch(err => { + me.isVdomUpdating = false; console.log('Error attempting to update component dom', err, me); reject?.() }).then(data => { - me.vdomUpdate = null; + me.isVdomUpdating = false; // checking if the component got destroyed before the update cycle is done if (me.id) { // console.log('Component vnode updated', data); @@ -1276,9 +1260,7 @@ class Base extends CoreBase { me.resolveVdomUpdate(resolve) } } - }); - - return me.vdomUpdate; + }) } /** @@ -2289,10 +2271,10 @@ class Base extends CoreBase { } if ( - mounted - && vnode - && !me.needsParentUpdate(me.parentId, resolve) + !me.needsParentUpdate(me.parentId, resolve) && !me.isParentVdomUpdating(me.parentId, resolve) + && mounted + && vnode ) { me.#executeVdomUpdate(vdom, vnode, resolve, reject) } From 597fba674b861e16d4098cb134b8eaf4db141548 Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 12 Sep 2023 15:34:37 +0200 Subject: [PATCH 051/162] v6.5.0 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index 29b9849d9..9a5192d41 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.4.3' + * @member {String} version='6.5.0' */ - version: '6.4.3' + version: '6.5.0' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index 29b9849d9..9a5192d41 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.4.3' + * @member {String} version='6.5.0' */ - version: '6.4.3' + version: '6.5.0' } /** diff --git a/package.json b/package.json index de4f46eac..22a360482 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.4.3", + "version": "6.5.0", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index 8b5df76a7..ca1973cf4 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.4.3' + * @default '6.5.0' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.4.3' + version: '6.5.0' }; Object.assign(DefaultConfig, { From 8eb4f6112475cdab8020d713113656014f48d845 Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 12 Sep 2023 16:54:35 +0200 Subject: [PATCH 052/162] field.Base: getPath() => return null in case a field has no name #4899 --- src/form/Container.mjs | 8 ++++++-- src/form/field/Base.mjs | 7 +++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/form/Container.mjs b/src/form/Container.mjs index 237a83972..926e88f99 100644 --- a/src/form/Container.mjs +++ b/src/form/Container.mjs @@ -313,11 +313,15 @@ class Container extends BaseContainer { */ async setValues(values={}, suspendEvents=false) { let fields = await this.getFields(), - fieldPaths = []; + fieldPaths = [], + path; // Grouped CheckBoxes & Radios can have the same path // => using NeoArray to ensure they only get added once - fields.map(field => NeoArray.add(fieldPaths, field.getPath())); + fields.map(field => { + path = field.getPath(); + path && NeoArray.add(fieldPaths, path) + }); values = Neo.clone(values, true); diff --git a/src/form/field/Base.mjs b/src/form/field/Base.mjs index c08ae4cac..e353d6d2f 100644 --- a/src/form/field/Base.mjs +++ b/src/form/field/Base.mjs @@ -184,6 +184,13 @@ class Base extends Component { let me = this, path; + // fields could have formGroups, but no name. + // returning the namespace can confuse form.Container.adjustTreeLeaves(), + // since namespaces could be considered as field instances. + if (!me.name) { + return null + } + if (!me.path) { path = me.formGroup ? me.formGroup.split('.') : []; From 987bbc126d67d79514b14790ece1846ae08e5ce4 Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 12 Sep 2023 16:55:19 +0200 Subject: [PATCH 053/162] v6.5.1 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index 9a5192d41..58e883540 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.0' + * @member {String} version='6.5.1' */ - version: '6.5.0' + version: '6.5.1' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index 9a5192d41..58e883540 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.0' + * @member {String} version='6.5.1' */ - version: '6.5.0' + version: '6.5.1' } /** diff --git a/package.json b/package.json index 22a360482..ee15447cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.5.0", + "version": "6.5.1", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index ca1973cf4..ed5d7707a 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.5.0' + * @default '6.5.1' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.5.0' + version: '6.5.1' }; Object.assign(DefaultConfig, { From e62472b094d9f8f5820b671d9d833fea494f3a60 Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 12 Sep 2023 17:21:03 +0200 Subject: [PATCH 054/162] form.Container: adjustTreeLeaves() comment update --- src/form/Container.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/form/Container.mjs b/src/form/Container.mjs index 926e88f99..64bbeec43 100644 --- a/src/form/Container.mjs +++ b/src/form/Container.mjs @@ -33,7 +33,6 @@ class Container extends BaseContainer { /** * Helper function used by setValues() which wraps the leaves of a tree structure into a new property. - * The logic assumes that field config values must not be objects (separation between the key & value realm). * @param {Object} values * @param {String} configName * @param {String[]} fieldPaths From 220ce383d9b9e5d88b3d23577947da3e07f8de28 Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 14 Sep 2023 11:30:15 +0200 Subject: [PATCH 055/162] util.Function: debounce #4901 --- src/util/Function.mjs | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/util/Function.mjs b/src/util/Function.mjs index e7c6778a9..2056f73d9 100644 --- a/src/util/Function.mjs +++ b/src/util/Function.mjs @@ -23,7 +23,7 @@ class NeoFunction extends Base { args = [].slice.call(arguments).slice(1); return function() { - return fn.apply(scope, [].slice.call(arguments).concat(args)); + return fn.apply(scope, [].slice.call(arguments).concat(args)) } } @@ -39,8 +39,8 @@ class NeoFunction extends Base { let targetMethod = target[targetMethodName]; return (target[targetMethodName] = function(value) { - return targetMethod.call(target, interceptFunction.call(scope || target, value)); - }); + return targetMethod.call(target, interceptFunction.call(scope || target, value)) + }) } /** @@ -56,7 +56,26 @@ class NeoFunction extends Base { return (target[methodName] = function() { method.apply(this, arguments); return fn.apply(scope || this, arguments); - }); + }) + } + + /** + * @param {Function} func + * @param {Neo.core.Base} scope + * @param {Number} timeout + * @returns {Function} + */ + static debounce(func, scope, timeout=300) { + let debounceTimer; + + return async function(...args) { + clearTimeout(debounceTimer); + + debounceTimer = setTimeout(() => { + // we need to check if the scope (instance) did not get destroyed yet + scope?.id && func.apply(scope, args); + }, timeout) + } } /** @@ -74,8 +93,8 @@ class NeoFunction extends Base { return (target[targetMethodName] = function() { return (interceptFunction.apply(scope || target, arguments) === false) ? preventedReturnValue - : targetMethod.apply(target, arguments); - }); + : targetMethod.apply(target, arguments) + }) } } From 69f060c7f682d3583ad72a8bf50344509c7a5a61 Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 14 Sep 2023 12:36:06 +0200 Subject: [PATCH 056/162] util.Function: convert the class into named exports #4902 --- src/data/connection/WebSocket.mjs | 8 +- src/util/Function.mjs | 156 +++++++++++++----------------- 2 files changed, 72 insertions(+), 92 deletions(-) diff --git a/src/data/connection/WebSocket.mjs b/src/data/connection/WebSocket.mjs index d115bf2b4..a6de1a107 100644 --- a/src/data/connection/WebSocket.mjs +++ b/src/data/connection/WebSocket.mjs @@ -1,6 +1,6 @@ -import Base from '../../core/Base.mjs'; -import NeoFunction from '../../util/Function.mjs'; -import Observable from '../../core/Observable.mjs'; +import Base from '../../core/Base.mjs'; +import {createInterceptor} from '../../util/Function.mjs'; +import Observable from '../../core/Observable.mjs'; /** * @class Neo.data.connection.WebSocket @@ -120,7 +120,7 @@ class Socket extends Base { onopen : me.onOpen .bind(me) }); - NeoFunction.createInterceptor(value, 'send', me.beforeSend, me); + createInterceptor(value, 'send', me.beforeSend, me); } return value; diff --git a/src/util/Function.mjs b/src/util/Function.mjs index 2056f73d9..60f851101 100644 --- a/src/util/Function.mjs +++ b/src/util/Function.mjs @@ -1,103 +1,83 @@ -import Base from '../core/Base.mjs'; - /** - * @class Neo.util.Function - * @extends Neo.core.Base + * Append args instead of prepending them + * @param {Object} scope + * @returns {Function} */ -class NeoFunction extends Base { - static config = { - /** - * @member {String} className='Neo.util.Function' - * @protected - */ - className: 'Neo.util.Function' - } - - /** - * Append args instead of prepending them - * @param {Object} scope - * @returns {Function} - */ - static bindAppend(scope) { - const fn = this, - args = [].slice.call(arguments).slice(1); - - return function() { - return fn.apply(scope, [].slice.call(arguments).concat(args)) - } - } +export function bindAppend(scope) { + const fn = this, + args = [].slice.call(arguments).slice(1); - /** - * Intended for functions with 1 param where the interceptor can change the value - * @param {Object} target - * @param {String} targetMethodName - * @param {Function} interceptFunction - * @param {Object} scope=target - * @returns {Function} - */ - static createInterceptor(target, targetMethodName, interceptFunction, scope) { - let targetMethod = target[targetMethodName]; - - return (target[targetMethodName] = function(value) { - return targetMethod.call(target, interceptFunction.call(scope || target, value)) - }) + return function() { + return fn.apply(scope, [].slice.call(arguments).concat(args)) } +} - /** - * @param {Neo.core.Base} target - * @param {String} methodName - * @param {Function} fn - * @param {Object} scope - * @returns {Function} - */ - static createSequence(target, methodName, fn, scope) { - let method = target[methodName] || Neo.emptyFn; +/** + * Intended for functions with 1 param where the interceptor can change the value + * @param {Object} target + * @param {String} targetMethodName + * @param {Function} interceptFunction + * @param {Object} scope=target + * @returns {Function} + */ +export function createInterceptor(target, targetMethodName, interceptFunction, scope) { + let targetMethod = target[targetMethodName]; - return (target[methodName] = function() { - method.apply(this, arguments); - return fn.apply(scope || this, arguments); - }) - } + return (target[targetMethodName] = function(value) { + return targetMethod.call(target, interceptFunction.call(scope || target, value)) + }) +} - /** - * @param {Function} func - * @param {Neo.core.Base} scope - * @param {Number} timeout - * @returns {Function} - */ - static debounce(func, scope, timeout=300) { - let debounceTimer; +/** + * @param {Neo.core.Base} target + * @param {String} methodName + * @param {Function} fn + * @param {Object} scope + * @returns {Function} + */ +export function createSequence(target, methodName, fn, scope) { + let method = target[methodName] || Neo.emptyFn; - return async function(...args) { - clearTimeout(debounceTimer); + return (target[methodName] = function() { + method.apply(this, arguments); + return fn.apply(scope || this, arguments); + }) +} - debounceTimer = setTimeout(() => { - // we need to check if the scope (instance) did not get destroyed yet - scope?.id && func.apply(scope, args); - }, timeout) - } - } +/** + * @param {Function} func + * @param {Neo.core.Base} scope + * @param {Number} timeout + * @returns {Function} + */ +export function debounce(func, scope, timeout=300) { + let debounceTimer; - /** - * The interceptor can prevent the targetMethod from getting executed in case it returns false. - * @param {Object} target - * @param {String} targetMethodName - * @param {Function} interceptFunction - * @param {Object} scope=target - * @param {*} preventedReturnValue=null The value to return in case the interceptFunction returns false - * @returns {Function} - */ - static intercept(target, targetMethodName, interceptFunction, scope, preventedReturnValue=null) { - let targetMethod = target[targetMethodName]; + return async function(...args) { + clearTimeout(debounceTimer); - return (target[targetMethodName] = function() { - return (interceptFunction.apply(scope || target, arguments) === false) - ? preventedReturnValue - : targetMethod.apply(target, arguments) - }) + debounceTimer = setTimeout(() => { + // we need to check if the scope (instance) did not get destroyed yet + scope?.id && func.apply(scope, args); + }, timeout) } } -Neo.applyClassConfig(NeoFunction); +/** + * The interceptor can prevent the targetMethod from getting executed in case it returns false. + * @param {Object} target + * @param {String} targetMethodName + * @param {Function} interceptFunction + * @param {Object} scope=target + * @param {*} preventedReturnValue=null The value to return in case the interceptFunction returns false + * @returns {Function} + */ +export function intercept(target, targetMethodName, interceptFunction, scope, preventedReturnValue=null) { + let targetMethod = target[targetMethodName]; -export default NeoFunction; + return (target[targetMethodName] = function() { + return (interceptFunction.apply(scope || target, arguments) === false) + ? preventedReturnValue + : targetMethod.apply(target, arguments) + }) +} From a4b88df624f283adf0639104c56f9b6d6fbb01a2 Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 14 Sep 2023 13:03:59 +0200 Subject: [PATCH 057/162] core.Base: static delayable #4903 --- apps/form/view/pages/Page1.mjs | 14 ++++++++++++++ src/core/Base.mjs | 34 ++++++++++++++++++++++++++++++++++ src/form/field/Text.mjs | 11 +++++++++++ 3 files changed, 59 insertions(+) diff --git a/apps/form/view/pages/Page1.mjs b/apps/form/view/pages/Page1.mjs index 9cfa2530e..48e5deb39 100644 --- a/apps/form/view/pages/Page1.mjs +++ b/apps/form/view/pages/Page1.mjs @@ -1,3 +1,4 @@ +import Button from '../../../../src/button/Base.mjs'; import FormPageContainer from '../FormPageContainer.mjs'; import TextField from '../../../../src/form/field/Text.mjs'; @@ -31,8 +32,21 @@ class Page1 extends FormPageContainer { name : 'status', readOnly : true, value : 'Active' + }, { + module : Button, + handler: Page1.buttonHandler, + style : {marginTop: '2em', maxWidth: '300px'}, + text : 'Change values' }] } + + static buttonHandler(data) { + let container = data.component.up(); + + container.items[0].value = Math.random(); + container.items[1].value = Math.random(); + container.items[2].value = Math.random() + } } Neo.applyClassConfig(Page1); diff --git a/src/core/Base.mjs b/src/core/Base.mjs index 9de8696d0..7b2e537f8 100644 --- a/src/core/Base.mjs +++ b/src/core/Base.mjs @@ -1,3 +1,4 @@ +import {debounce} from '../util/Function.mjs'; import IdGenerator from './IdGenerator.mjs' const configSymbol = Symbol.for('configSymbol'), @@ -17,6 +18,20 @@ class Base { * @static */ static methodNameRegex = /\n.*\n\s+at\s+.*\.(\w+)\s+.*/ + /** + * You can define methods which should get delayed + * @example + * delayable: { + * fireChangeEvent: { + * type : 'debounce', + * timer: 300 + * } + * } + * @member {Object} delayable={} + * @protected + * @static + */ + static delayable = {} /** * True automatically applies the core.Observable mixin * @member {Boolean} observable=false @@ -126,6 +141,8 @@ class Base { value : true }); + me.applyDelayable(); + me.remote && setTimeout(me.initRemote.bind(me), 1) } @@ -157,6 +174,23 @@ class Base { } } + /** + * Adjusts all methods inside static delayable + */ + applyDelayable() { + let me = this; + + Object.entries(me.constructor.delayable).forEach(([key, value]) => { + let map = { + debounce() { + me[key] = new debounce(me[key], me, value.timer) + } + }; + + map[value.type]?.() + }) + } + /** * Applying overwrites and adding overwrittenMethods to the class constructors * @param {Object} cfg diff --git a/src/form/field/Text.mjs b/src/form/field/Text.mjs index 25157a31f..1f30968cb 100644 --- a/src/form/field/Text.mjs +++ b/src/form/field/Text.mjs @@ -17,6 +17,17 @@ class Text extends Base { * @static */ static autoCapitalizeValues = ['characters', 'none', 'on', 'off', 'sentences', 'words'] + /** + * @member {Object} delayable + * @protected + * @static + */ + static delayable = { + fireChangeEvent: { + type : 'debounce', + timer: 300 + } + } /** * Valid values for labelPosition * @member {String[]} labelPositions=['bottom','inline','left','right','top'] From b0a13887c91f7636a07d611e28b292f304d21280 Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 14 Sep 2023 15:10:43 +0200 Subject: [PATCH 058/162] util.Function: debounce #4901 leading edge --- src/util/Function.mjs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/util/Function.mjs b/src/util/Function.mjs index 60f851101..fe5369006 100644 --- a/src/util/Function.mjs +++ b/src/util/Function.mjs @@ -53,13 +53,23 @@ export function createSequence(target, methodName, fn, scope) { export function debounce(func, scope, timeout=300) { let debounceTimer; - return async function(...args) { - clearTimeout(debounceTimer); - - debounceTimer = setTimeout(() => { + return function(...args) { + // leading edge => trigger the first call right away + if (!Neo.isNumber(debounceTimer)) { // we need to check if the scope (instance) did not get destroyed yet scope?.id && func.apply(scope, args); - }, timeout) + + // we still want to start a timer, do delay the 2nd+ update + debounceTimer = setTimeout(() => {debounceTimer = null}, timeout) + } else { + clearTimeout(debounceTimer); + + debounceTimer = setTimeout(() => { + // we need to check if the scope (instance) did not get destroyed yet + scope?.id && func.apply(scope, args); + debounceTimer = setTimeout(() => {debounceTimer = null}, timeout) + }, timeout) + } } } From 36fbc789b1e2f5ae5ada19ae1dcbb26ba87b092b Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 14 Sep 2023 16:25:51 +0200 Subject: [PATCH 059/162] util.Rectangle: dropping in Nige's latest changes --- src/util/Rectangle.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/Rectangle.mjs b/src/util/Rectangle.mjs index 77d3e9f5d..be359ac19 100644 --- a/src/util/Rectangle.mjs +++ b/src/util/Rectangle.mjs @@ -451,11 +451,11 @@ export default class Rectangle extends DOMRect { // Fall back to the other two zones. zonesToTry.push({ - zone : zone = (alignSpec.startZone + 1) % 4, + zone : zone = (edges.theirEdgeZone + 1) % 4, edgeAlign : `${oppositeEdge[zoneEdges[zone]]}-${zoneEdges[zone]}` }); zonesToTry.push({ - zone : zone = (zone + 2) % 4, + zone : zone = (edges.theirEdgeZone + 3) % 4, edgeAlign : `${oppositeEdge[zoneEdges[zone]]}-${zoneEdges[zone]}` }); } From bf182fe5e37faf04cfb33707b54222460e7b97e8 Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 14 Sep 2023 16:26:17 +0200 Subject: [PATCH 060/162] v6.5.2 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index 58e883540..e21684d19 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.1' + * @member {String} version='6.5.2' */ - version: '6.5.1' + version: '6.5.2' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index 58e883540..e21684d19 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.1' + * @member {String} version='6.5.2' */ - version: '6.5.1' + version: '6.5.2' } /** diff --git a/package.json b/package.json index ee15447cc..3eee7c8ea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.5.1", + "version": "6.5.2", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index ed5d7707a..69893a4f9 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.5.1' + * @default '6.5.2' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.5.1' + version: '6.5.2' }; Object.assign(DefaultConfig, { From f30616055cb785a6e8e5fea537ee881e5bb76d79 Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 14 Sep 2023 16:40:07 +0200 Subject: [PATCH 061/162] dependencies update --- package-lock.json | 50 +++++++++++++++++++++++------------------------ package.json | 6 +++--- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index 56a4390da..57d8a2185 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "neo.mjs", - "version": "6.1.5", + "version": "6.5.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "neo.mjs", - "version": "6.1.5", + "version": "6.5.2", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-free": "^6.4.2", @@ -20,11 +20,11 @@ "envinfo": "^7.10.0", "fs-extra": "^11.1.1", "highlightjs-line-numbers.js": "^2.8.0", - "inquirer": "^9.2.10", + "inquirer": "^9.2.11", "neo-jsdoc": "1.0.1", "neo-jsdoc-x": "1.0.5", "postcss": "^8.4.29", - "sass": "^1.66.1", + "sass": "^1.67.0", "showdown": "^2.1.0", "webpack": "^5.88.2", "webpack-cli": "^5.1.4", @@ -37,7 +37,7 @@ }, "devDependencies": { "siesta-lite": "5.5.2", - "url": "^0.11.1" + "url": "^0.11.2" }, "funding": { "type": "GitHub Sponsors", @@ -3675,9 +3675,9 @@ "license": "ISC" }, "node_modules/inquirer": { - "version": "9.2.10", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.10.tgz", - "integrity": "sha512-tVVNFIXU8qNHoULiazz612GFl+yqNfjMTbLuViNJE/d860Qxrd3NMrse8dm40VUQLOQeULvaQF8lpAhvysjeyA==", + "version": "9.2.11", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.11.tgz", + "integrity": "sha512-B2LafrnnhbRzCWfAdOXisUzL89Kg8cVJlYmhqoi3flSiV/TveO+nsXwgKr9h9PIo+J1hz7nBSk6gegRIMBBf7g==", "dependencies": { "@ljharb/through": "^2.3.9", "ansi-escapes": "^4.3.2", @@ -6139,9 +6139,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.66.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.66.1.tgz", - "integrity": "sha512-50c+zTsZOJVgFfTgwwEzkjA3/QACgdNsKueWPyAR0mRINIvLAStVQBbPg14iuqEQ74NPDbXzJARJ/O4SI1zftA==", + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.67.0.tgz", + "integrity": "sha512-SVrO9ZeX/QQyEGtuZYCVxoeAL5vGlYjJ9p4i4HFuekWl8y/LtJ7tJc10Z+ck1c8xOuoBm2MYzcLfTAffD0pl/A==", "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -7058,13 +7058,13 @@ } }, "node_modules/url": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.1.tgz", - "integrity": "sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==", + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.2.tgz", + "integrity": "sha512-7yIgNnrST44S7PJ5+jXbdIupfU1nWUdQJBFBeJRclPXiWgCvrSq5Frw8lr/i//n5sqDfzoKmBymMS81l4U/7cg==", "dev": true, "dependencies": { "punycode": "^1.4.1", - "qs": "^6.11.0" + "qs": "^6.11.2" } }, "node_modules/url-parse-lax": { @@ -10120,9 +10120,9 @@ "dev": true }, "inquirer": { - "version": "9.2.10", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.10.tgz", - "integrity": "sha512-tVVNFIXU8qNHoULiazz612GFl+yqNfjMTbLuViNJE/d860Qxrd3NMrse8dm40VUQLOQeULvaQF8lpAhvysjeyA==", + "version": "9.2.11", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.11.tgz", + "integrity": "sha512-B2LafrnnhbRzCWfAdOXisUzL89Kg8cVJlYmhqoi3flSiV/TveO+nsXwgKr9h9PIo+J1hz7nBSk6gegRIMBBf7g==", "requires": { "@ljharb/through": "^2.3.9", "ansi-escapes": "^4.3.2", @@ -11658,9 +11658,9 @@ "version": "2.1.2" }, "sass": { - "version": "1.66.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.66.1.tgz", - "integrity": "sha512-50c+zTsZOJVgFfTgwwEzkjA3/QACgdNsKueWPyAR0mRINIvLAStVQBbPg14iuqEQ74NPDbXzJARJ/O4SI1zftA==", + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.67.0.tgz", + "integrity": "sha512-SVrO9ZeX/QQyEGtuZYCVxoeAL5vGlYjJ9p4i4HFuekWl8y/LtJ7tJc10Z+ck1c8xOuoBm2MYzcLfTAffD0pl/A==", "requires": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -12257,13 +12257,13 @@ } }, "url": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.1.tgz", - "integrity": "sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==", + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.2.tgz", + "integrity": "sha512-7yIgNnrST44S7PJ5+jXbdIupfU1nWUdQJBFBeJRclPXiWgCvrSq5Frw8lr/i//n5sqDfzoKmBymMS81l4U/7cg==", "dev": true, "requires": { "punycode": "^1.4.1", - "qs": "^6.11.0" + "qs": "^6.11.2" }, "dependencies": { "punycode": { diff --git a/package.json b/package.json index 3eee7c8ea..bee1a9483 100644 --- a/package.json +++ b/package.json @@ -53,11 +53,11 @@ "envinfo": "^7.10.0", "fs-extra": "^11.1.1", "highlightjs-line-numbers.js": "^2.8.0", - "inquirer": "^9.2.10", + "inquirer": "^9.2.11", "neo-jsdoc": "1.0.1", "neo-jsdoc-x": "1.0.5", "postcss": "^8.4.29", - "sass": "^1.66.1", + "sass": "^1.67.0", "showdown": "^2.1.0", "webpack": "^5.88.2", "webpack-cli": "^5.1.4", @@ -67,7 +67,7 @@ }, "devDependencies": { "siesta-lite": "5.5.2", - "url": "^0.11.1" + "url": "^0.11.2" }, "funding": { "type": "GitHub Sponsors", From 0b8f360b4180b7acf93407533bcdc430e95cf981 Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 14 Sep 2023 16:42:46 +0200 Subject: [PATCH 062/162] util.Function: debounce() => doc comments update --- src/util/Function.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/Function.mjs b/src/util/Function.mjs index fe5369006..8baad24e3 100644 --- a/src/util/Function.mjs +++ b/src/util/Function.mjs @@ -47,7 +47,7 @@ export function createSequence(target, methodName, fn, scope) { /** * @param {Function} func * @param {Neo.core.Base} scope - * @param {Number} timeout + * @param {Number} timeout=300 * @returns {Function} */ export function debounce(func, scope, timeout=300) { From a12038cc6b13033f0a85edc6a12b1a6567cae6e0 Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 14 Sep 2023 20:31:06 +0200 Subject: [PATCH 063/162] form.field.Checkbox, form.field.Text: Fire userChange event #4818 --- apps/form/view/FormContainerController.mjs | 10 +++--- src/form/field/Base.mjs | 40 +++++++++++++++++++--- src/form/field/CheckBox.mjs | 4 ++- src/form/field/Text.mjs | 22 ++++-------- 4 files changed, 49 insertions(+), 27 deletions(-) diff --git a/apps/form/view/FormContainerController.mjs b/apps/form/view/FormContainerController.mjs index 7fa3aaf93..7713722ee 100644 --- a/apps/form/view/FormContainerController.mjs +++ b/apps/form/view/FormContainerController.mjs @@ -19,12 +19,10 @@ class FormContainerController extends Component { onComponentConstructed() { super.onComponentConstructed(); - this.component.on('fieldFocusLeave', data => { - console.log('fieldFocusLeave', data); - }) - - this.component.on('fieldChange', data => { - console.log('fieldChange', data); + this.component.on({ + fieldChange : data => console.log('fieldChange' , data), + fieldUserChange: data => console.log('fieldUserChange', data), + fieldFocusLeave: data => console.log('fieldFocusLeave', data) }) } diff --git a/src/form/field/Base.mjs b/src/form/field/Base.mjs index e353d6d2f..c6612748b 100644 --- a/src/form/field/Base.mjs +++ b/src/form/field/Base.mjs @@ -7,6 +7,16 @@ import ComponentManager from '../../manager/Component.mjs'; * @extends Neo.component.Base */ class Base extends Component { + /** + * @member {Object} delayable + * @protected + * @static + */ + static delayable = { + fireChangeEvent : {type: 'debounce', timer: 300}, + fireUserChangeEvent: {type: 'debounce', timer: 300} + } + static config = { /** * @member {String} className='Neo.form.field.Base' @@ -127,29 +137,49 @@ class Base extends Component { /** * Override this method as needed - * @param {*} value - * @param {*} oldValue + * @param {*} value + * @param {*} oldValue + * @param {String} eventName */ - fireChangeEvent(value, oldValue) { + doFireChangeEvent(value, oldValue, eventName) { let me = this, FormContainer = Neo.form?.Container, + formEvent = 'field' + Neo.capitalize(eventName), opts = {component: me, oldValue, value}; if (Neo.isFunction(me.getGroupValue)) { opts.groupValue = me.getGroupValue() } - me.fire('change', opts); + me.fire(eventName, opts); if (!me.suspendEvents) { ComponentManager.getParents(me).forEach(parent => { if (FormContainer && parent instanceof FormContainer) { - parent.fire('fieldChange', opts) + parent.fire(formEvent, opts) } }) } } + /** + * Override this method as needed + * @param {*} value + * @param {*} oldValue + */ + fireChangeEvent(value, oldValue) { + this.doFireChangeEvent(value, oldValue, 'change') + } + + /** + * Override this method as needed + * @param {*} value + * @param {*} oldValue + */ + fireUserChangeEvent(value, oldValue) { + this.doFireChangeEvent(value, oldValue, 'userChange') + } + /** * Forms in neo can be nested. This method will return the closest parent which is a form.Container or null. * @returns {Neo.form.Container|null} diff --git a/src/form/field/CheckBox.mjs b/src/form/field/CheckBox.mjs index d58b32391..e12b6bf6b 100644 --- a/src/form/field/CheckBox.mjs +++ b/src/form/field/CheckBox.mjs @@ -525,7 +525,9 @@ class CheckBox extends Base { // keep the vdom & vnode in sync for future updates me.vnode.childNodes[0].childNodes[me.hideLabel ? 0 : 1].attributes.checked = `${checked}`; - me.checked = checked + me.checked = checked; + + me.fireUserChangeEvent(me.getValue(), me.getOldValue()) } /** diff --git a/src/form/field/Text.mjs b/src/form/field/Text.mjs index 1f30968cb..61c1121a8 100644 --- a/src/form/field/Text.mjs +++ b/src/form/field/Text.mjs @@ -17,17 +17,6 @@ class Text extends Base { * @static */ static autoCapitalizeValues = ['characters', 'none', 'on', 'off', 'sentences', 'words'] - /** - * @member {Object} delayable - * @protected - * @static - */ - static delayable = { - fireChangeEvent: { - type : 'debounce', - timer: 300 - } - } /** * Valid values for labelPosition * @member {String[]} labelPositions=['bottom','inline','left','right','top'] @@ -1242,16 +1231,19 @@ class Text extends Base { * @protected */ onInputValueChange(data) { - let me = this, - value = data.value, - vnode = VNodeUtil.findChildVnode(me.vnode, {nodeName: 'input'}); + let me = this, + oldValue = me.value, + value = data.value, + vnode = VNodeUtil.findChildVnode(me.vnode, {nodeName: 'input'}); if (vnode) { // required for validation -> revert a wrong user input vnode.vnode.attributes.value = value; } - me.value = me.inputValueAdjustor(value) + me.value = me.inputValueAdjustor(value); + + me.fireUserChangeEvent(value, oldValue) } /** From 23b51a7261a2bcbbf57597e4a17263bdb94544be Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 14 Sep 2023 20:38:16 +0200 Subject: [PATCH 064/162] Form.view.FormContainerController: cleanup --- apps/form/view/FormContainerController.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/form/view/FormContainerController.mjs b/apps/form/view/FormContainerController.mjs index 7713722ee..50d032b86 100644 --- a/apps/form/view/FormContainerController.mjs +++ b/apps/form/view/FormContainerController.mjs @@ -21,8 +21,8 @@ class FormContainerController extends Component { this.component.on({ fieldChange : data => console.log('fieldChange' , data), - fieldUserChange: data => console.log('fieldUserChange', data), - fieldFocusLeave: data => console.log('fieldFocusLeave', data) + fieldFocusLeave: data => console.log('fieldFocusLeave', data), + fieldUserChange: data => console.log('fieldUserChange', data) }) } From efe9541049b33f00a2fbc3feef5f1cb2c095c0e3 Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 14 Sep 2023 20:39:29 +0200 Subject: [PATCH 065/162] v6.5.3 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index e21684d19..7aad6a487 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.2' + * @member {String} version='6.5.3' */ - version: '6.5.2' + version: '6.5.3' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index e21684d19..7aad6a487 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.2' + * @member {String} version='6.5.3' */ - version: '6.5.2' + version: '6.5.3' } /** diff --git a/package.json b/package.json index bee1a9483..d6341ee75 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.5.2", + "version": "6.5.3", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index 69893a4f9..179619a3f 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.5.2' + * @default '6.5.3' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.5.2' + version: '6.5.3' }; Object.assign(DefaultConfig, { From 9f59d8468a4177640bced9e538fcddacca6bf367 Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 14 Sep 2023 21:17:21 +0200 Subject: [PATCH 066/162] util.Function: throttle #4908 --- src/core/Base.mjs | 9 ++++----- src/util/Function.mjs | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/core/Base.mjs b/src/core/Base.mjs index 7b2e537f8..9e24cfb2d 100644 --- a/src/core/Base.mjs +++ b/src/core/Base.mjs @@ -1,5 +1,5 @@ -import {debounce} from '../util/Function.mjs'; -import IdGenerator from './IdGenerator.mjs' +import {debounce, throttle} from '../util/Function.mjs'; +import IdGenerator from './IdGenerator.mjs' const configSymbol = Symbol.for('configSymbol'), forceAssignConfigs = Symbol('forceAssignConfigs'), @@ -182,9 +182,8 @@ class Base { Object.entries(me.constructor.delayable).forEach(([key, value]) => { let map = { - debounce() { - me[key] = new debounce(me[key], me, value.timer) - } + debounce() {me[key] = new debounce(me[key], me, value.timer)}, + throttle() {me[key] = new throttle(me[key], me, value.timer)} }; map[value.type]?.() diff --git a/src/util/Function.mjs b/src/util/Function.mjs index 8baad24e3..b3a4ec6be 100644 --- a/src/util/Function.mjs +++ b/src/util/Function.mjs @@ -91,3 +91,24 @@ export function intercept(target, targetMethodName, interceptFunction, scope, pr : targetMethod.apply(target, arguments) }) } + +/** + * @param {Function} callback + * @param {Neo.core.Base} scope + * @param {Number} delay=300 + * @returns {Function} + */ +export function throttle(callback, scope, delay=300) { + let wait = false; + + return function(...args) { + if (!wait) { + wait = true; + + // we need to check if the scope (instance) did not get destroyed yet + scope?.id && callback.apply(scope, args); + + setTimeout(() => {wait = false}, delay) + } + } +} From 84c77f91b045065a21d320efd41a4472f178367f Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 14 Sep 2023 21:30:57 +0200 Subject: [PATCH 067/162] util.Function: debounce => cleanup to be consistent to throttle --- src/util/Function.mjs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/util/Function.mjs b/src/util/Function.mjs index b3a4ec6be..d3a6b8be6 100644 --- a/src/util/Function.mjs +++ b/src/util/Function.mjs @@ -45,30 +45,30 @@ export function createSequence(target, methodName, fn, scope) { } /** - * @param {Function} func + * @param {Function} callback * @param {Neo.core.Base} scope - * @param {Number} timeout=300 + * @param {Number} delay=300 * @returns {Function} */ -export function debounce(func, scope, timeout=300) { +export function debounce(callback, scope, delay=300) { let debounceTimer; return function(...args) { // leading edge => trigger the first call right away if (!Neo.isNumber(debounceTimer)) { // we need to check if the scope (instance) did not get destroyed yet - scope?.id && func.apply(scope, args); + scope?.id && callback.apply(scope, args); // we still want to start a timer, do delay the 2nd+ update - debounceTimer = setTimeout(() => {debounceTimer = null}, timeout) + debounceTimer = setTimeout(() => {debounceTimer = null}, delay) } else { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { // we need to check if the scope (instance) did not get destroyed yet - scope?.id && func.apply(scope, args); - debounceTimer = setTimeout(() => {debounceTimer = null}, timeout) - }, timeout) + scope?.id && callback.apply(scope, args); + debounceTimer = setTimeout(() => {debounceTimer = null}, delay) + }, delay) } } } From f5fc6a4a4d7caa6576506580d38893d096de625c Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 14 Sep 2023 21:31:35 +0200 Subject: [PATCH 068/162] v6.5.4 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index 7aad6a487..aeec67dd5 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.3' + * @member {String} version='6.5.4' */ - version: '6.5.3' + version: '6.5.4' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index 7aad6a487..aeec67dd5 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.3' + * @member {String} version='6.5.4' */ - version: '6.5.3' + version: '6.5.4' } /** diff --git a/package.json b/package.json index d6341ee75..5073e3743 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.5.3", + "version": "6.5.4", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index 179619a3f..9587e68a4 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.5.3' + * @default '6.5.4' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.5.3' + version: '6.5.4' }; Object.assign(DefaultConfig, { From 5399ed93eb6739194b021be600188c3d2dcedc28 Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 15 Sep 2023 09:21:01 +0200 Subject: [PATCH 069/162] util.Function: throttle() => don't lose the last tick #4910 --- src/util/Function.mjs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/util/Function.mjs b/src/util/Function.mjs index d3a6b8be6..65ea16a26 100644 --- a/src/util/Function.mjs +++ b/src/util/Function.mjs @@ -99,16 +99,25 @@ export function intercept(target, targetMethodName, interceptFunction, scope, pr * @returns {Function} */ export function throttle(callback, scope, delay=300) { - let wait = false; + let lastRanDate, timeoutId; return function(...args) { - if (!wait) { - wait = true; - + if (!lastRanDate) { // we need to check if the scope (instance) did not get destroyed yet scope?.id && callback.apply(scope, args); - setTimeout(() => {wait = false}, delay) + lastRanDate = Date.now() + } else { + clearTimeout(timeoutId) + + timeoutId = setTimeout(function() { + if ((Date.now() - lastRanDate) >= delay) { + // we need to check if the scope (instance) did not get destroyed yet + scope?.id && callback.apply(scope, args); + + lastRanDate = Date.now() + } + }, delay - (Date.now() - lastRanDate)) } } } From 4b7152f38f2d7530c1c5df88378450e4f8724b6d Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 15 Sep 2023 09:23:10 +0200 Subject: [PATCH 070/162] form.field.Base: remove the delayable => now covered inside field.Base #4911 --- src/form/field/Text.mjs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/form/field/Text.mjs b/src/form/field/Text.mjs index fd0fdeafb..61c1121a8 100644 --- a/src/form/field/Text.mjs +++ b/src/form/field/Text.mjs @@ -17,17 +17,6 @@ class Text extends Base { * @static */ static autoCapitalizeValues = ['characters', 'none', 'on', 'off', 'sentences', 'words'] - /** - * @member {Object} delayable - * @protected - * @static - */ - static delayable = { - fireChangeEvent: { - type : 'debounce', - timer: 300 - } - } /** * Valid values for labelPosition * @member {String[]} labelPositions=['bottom','inline','left','right','top'] From a75885b1d52508780946159ebb1d296fd5c3fc31 Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 15 Sep 2023 09:25:52 +0200 Subject: [PATCH 071/162] v6.5.5 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index aeec67dd5..dd85f59a2 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.4' + * @member {String} version='6.5.5' */ - version: '6.5.4' + version: '6.5.5' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index aeec67dd5..dd85f59a2 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.4' + * @member {String} version='6.5.5' */ - version: '6.5.4' + version: '6.5.5' } /** diff --git a/package.json b/package.json index 5073e3743..21c9cdd1e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.5.4", + "version": "6.5.5", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index 9587e68a4..ac537fc0c 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.5.4' + * @default '6.5.5' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.5.4' + version: '6.5.5' }; Object.assign(DefaultConfig, { From dd0936a2f9820191b76bc08620fc65942f709dfd Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 15 Sep 2023 10:52:58 +0200 Subject: [PATCH 072/162] util.Function: comments cleanup --- src/util/Function.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/Function.mjs b/src/util/Function.mjs index 65ea16a26..6fed20118 100644 --- a/src/util/Function.mjs +++ b/src/util/Function.mjs @@ -59,7 +59,7 @@ export function debounce(callback, scope, delay=300) { // we need to check if the scope (instance) did not get destroyed yet scope?.id && callback.apply(scope, args); - // we still want to start a timer, do delay the 2nd+ update + // we still want to start a timer to delay the 2nd+ update debounceTimer = setTimeout(() => {debounceTimer = null}, delay) } else { clearTimeout(debounceTimer); From 3812cf9f00be64dcc71bfebe139cd9bb1434ca14 Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 15 Sep 2023 10:54:54 +0200 Subject: [PATCH 073/162] dependencies update --- package-lock.json | 18 +++++++++--------- package.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 57d8a2185..233846861 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "neo.mjs", - "version": "6.5.2", + "version": "6.5.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "neo.mjs", - "version": "6.5.2", + "version": "6.5.5", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-free": "^6.4.2", @@ -37,7 +37,7 @@ }, "devDependencies": { "siesta-lite": "5.5.2", - "url": "^0.11.2" + "url": "^0.11.3" }, "funding": { "type": "GitHub Sponsors", @@ -7058,9 +7058,9 @@ } }, "node_modules/url": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.2.tgz", - "integrity": "sha512-7yIgNnrST44S7PJ5+jXbdIupfU1nWUdQJBFBeJRclPXiWgCvrSq5Frw8lr/i//n5sqDfzoKmBymMS81l4U/7cg==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", + "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", "dev": true, "dependencies": { "punycode": "^1.4.1", @@ -12257,9 +12257,9 @@ } }, "url": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.2.tgz", - "integrity": "sha512-7yIgNnrST44S7PJ5+jXbdIupfU1nWUdQJBFBeJRclPXiWgCvrSq5Frw8lr/i//n5sqDfzoKmBymMS81l4U/7cg==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", + "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", "dev": true, "requires": { "punycode": "^1.4.1", diff --git a/package.json b/package.json index 21c9cdd1e..280aba095 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ }, "devDependencies": { "siesta-lite": "5.5.2", - "url": "^0.11.2" + "url": "^0.11.3" }, "funding": { "type": "GitHub Sponsors", From 3b06b351c8225591060bb954eb6041bc507676f1 Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 15 Sep 2023 11:04:08 +0200 Subject: [PATCH 074/162] calendar.view.week.Component: afterSetMounted() sometimes sends request to main without an id #4914 --- src/calendar/view/week/Component.mjs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/calendar/view/week/Component.mjs b/src/calendar/view/week/Component.mjs index c60a859c3..45f495964 100644 --- a/src/calendar/view/week/Component.mjs +++ b/src/calendar/view/week/Component.mjs @@ -444,7 +444,7 @@ class Component extends BaseComponent { super.afterSetMounted(value, oldValue); let me = this, - rect; + scrollContainerId, rect; if (value) { if (me.needsEventUpdate) { @@ -452,14 +452,15 @@ class Component extends BaseComponent { me.needsEventUpdate = false; } - await me.timeout(70); + await me.timeout(100); - rect = await me.getDomRect(me.getColumnContainer().id); + rect = await me.getDomRect(me.getColumnContainer().id); + scrollContainerId = me.getScrollContainer().id; - Neo.main.DomAccess.scrollBy({ + scrollContainerId && Neo.main.DomAccess.scrollBy({ appName : me.appName, direction: 'left', - id : me.getScrollContainer().id, + id : scrollContainerId, value : rect.width * me.columnsBuffer / me.columnsVisible / 3 }); } From e4703d760ac853ad2efad66e2136b9cd52cf6f7f Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 15 Sep 2023 11:07:57 +0200 Subject: [PATCH 075/162] component.DateSelector: onComponentWheel() => deltaX, deltaY vars --- src/component/DateSelector.mjs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/component/DateSelector.mjs b/src/component/DateSelector.mjs index 5cd4831ac..4e05648ab 100644 --- a/src/component/DateSelector.mjs +++ b/src/component/DateSelector.mjs @@ -759,15 +759,17 @@ class DateSelector extends Component { */ onComponentWheel(data) { let me = this, + deltaX = data.deltaX, + deltaY = data.deltaY, wheelDelta = me.mouseWheelDelta, date, monthIncrement, yearIncrement; - if (Math.abs(data.deltaY) >= Math.abs(data.deltaX)) { - if (data.deltaY >= wheelDelta) {yearIncrement = 1;} - else if (data.deltaY <= -wheelDelta) {yearIncrement = -1;} + if (Math.abs(deltaY) >= Math.abs(deltaX)) { + if (deltaY >= wheelDelta) {yearIncrement = 1;} + else if (deltaY <= -wheelDelta) {yearIncrement = -1;} } else { - if (data.deltaX >= wheelDelta) {monthIncrement = 1;} - else if (data.deltaX <= -wheelDelta) {monthIncrement = -1;} + if (deltaX >= wheelDelta) {monthIncrement = 1;} + else if (deltaX <= -wheelDelta) {monthIncrement = -1;} } if (monthIncrement) { From 6a5570032eb5fb50a96fbeac83e76cbbb3abe62e Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 18 Sep 2023 12:33:51 +0200 Subject: [PATCH 076/162] component.Base: reference_ config => mapping reference names into the vdom root => data-ref DOM property #4917 --- src/component/Base.mjs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/component/Base.mjs b/src/component/Base.mjs index 877d8816d..aadb84b96 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -264,6 +264,13 @@ class Base extends CoreBase { * @protected */ plugins_: null, + /** + * Set a reference for accessing the component inside view controllers. + * References will also get mapped into the vdom root (data-ref: value). + * @member {String|null} reference_=null + * @protected + */ + reference_: null, /** * True in case the component is rendering the vnode * @member {Boolean} rendering_=false @@ -702,6 +709,16 @@ class Base extends CoreBase { } } + /** + * Triggered after the reference config got changed + * @param {String|null} value + * @param {String|null} oldValue + * @protected + */ + afterSetReference(value, oldValue) { + value && this.changeVdomRootKey('data-ref', value) + } + /** * Triggered after the role config got changed * @param {String|null} value From b51e9483877bd510c5f94eefe28d678ee9c12ce5 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 18 Sep 2023 12:36:52 +0200 Subject: [PATCH 077/162] v6.5.6 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index dd85f59a2..6b139c897 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.5' + * @member {String} version='6.5.6' */ - version: '6.5.5' + version: '6.5.6' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index dd85f59a2..6b139c897 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.5' + * @member {String} version='6.5.6' */ - version: '6.5.5' + version: '6.5.6' } /** diff --git a/package.json b/package.json index 280aba095..16e99eb88 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.5.5", + "version": "6.5.6", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index ac537fc0c..009e04907 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.5.5' + * @default '6.5.6' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.5.5' + version: '6.5.6' }; Object.assign(DefaultConfig, { From 8725df8a85c6236b3da6645524d70c3596246dc7 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 18 Sep 2023 14:23:59 +0200 Subject: [PATCH 078/162] button.Base: afterSetItems() => we need a more flexible way to handle menu configs #4919 --- src/button/Base.mjs | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/button/Base.mjs b/src/button/Base.mjs index 660164dd7..868cf06ed 100644 --- a/src/button/Base.mjs +++ b/src/button/Base.mjs @@ -86,10 +86,6 @@ class Base extends Component { * @member {Object|Object[]|null} menu_=null */ menu_: null, - /** - * @member {Object} menuListConfig=null - */ - menuListConfig: null, /** * The pressed state of the Button * @member {Boolean} pressed_=false @@ -259,24 +255,25 @@ class Base extends Component { let me = this, isArray = Array.isArray(value), items = isArray ? value : value.items, - menuConfig = isArray ? {} : value; - - me.menuList = Neo.create({ - align : { - edgeAlign : 't0-b0', - target : me.id - }, - ...menuConfig, + menuConfig = isArray ? {} : value, + + config = { module : module.default, + align : {edgeAlign : 't0-b0', target: me.id}, appName : me.appName, displayField : 'text', floating : true, hidden : true, - items, parentComponent: me, theme : me.theme, - ...me.menuListConfig - }) + ...menuConfig + }; + + if (items) { + config.items = items + } + + me.menuList = Neo.create(config) }) } } From 129d93fc28d347353782d0e1aa823c2c7c8712fc Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 18 Sep 2023 14:42:37 +0200 Subject: [PATCH 079/162] list.Base: cleanup --- src/list/Base.mjs | 102 +++++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/src/list/Base.mjs b/src/list/Base.mjs index 8a7cc37a0..3bd45e296 100644 --- a/src/list/Base.mjs +++ b/src/list/Base.mjs @@ -163,7 +163,7 @@ class Base extends Component { me.addDomListeners({ click: me.onClick, scope: me - }); + }) } /** @@ -178,10 +178,10 @@ class Base extends Component { if (Neo.isNumber(value)) { selectionModel?.selectAt(value); - me.headerlessActiveIndex = me.getHeaderlessIndex(value); + me.headerlessActiveIndex = me.getHeaderlessIndex(value) } else if (Neo.isNumber(oldValue)) { selectionModel.deselectAll(); - me.headerlessActiveIndex = null; + me.headerlessActiveIndex = null } } @@ -203,8 +203,8 @@ class Base extends Component { ...me.pluginAnimateConfig }); - me.plugins = plugins; - }); + me.plugins = plugins + }) } /** @@ -214,7 +214,7 @@ class Base extends Component { * @protected */ afterSetDisableSelection(value, oldValue) { - value && this.rendered && this.selectionModel?.deselectAll(); + value && this.rendered && this.selectionModel?.deselectAll() } /** @@ -233,8 +233,8 @@ class Base extends Component { appName: me.appName, owner : me, ...me.dragZoneConfig - }); - }); + }) + }) } } @@ -251,9 +251,9 @@ class Base extends Component { if (Neo.isNumber(value)) { activeIndex = me.getActiveIndex(value); - me.activeIndex = activeIndex; + me.activeIndex = activeIndex } else if (Neo.isNumber(oldValue)) { - me.activeIndex = null; + me.activeIndex = null } } @@ -264,7 +264,7 @@ class Base extends Component { * @protected */ afterSetSelectionModel(value, oldValue) { - this.rendered && value.register(this); + this.rendered && value.register(this) } /** @@ -284,7 +284,7 @@ class Base extends Component { scope : me }); - value?.getCount() > 0 && me.onStoreLoad(); + value?.getCount() > 0 && me.onStoreLoad() } /** @@ -297,8 +297,8 @@ class Base extends Component { let me = this, cls = me.cls; - NeoArray[value ? 'add' : 'remove'](cls, 'neo-use-checkicons'); - me.cls = cls; + NeoArray.toggle(cls, 'neo-use-checkicons', !!value); + me.cls = cls } /** @@ -312,7 +312,7 @@ class Base extends Component { let me = this; me.vdom.tag = 'dl'; - me.itemTagName = 'dd'; + me.itemTagName = 'dd' } } @@ -331,7 +331,7 @@ class Base extends Component { NeoArray[value ? 'add' : 'remove'](wrapperCls, 'neo-list-wrapper'); me.wrapperCls = wrapperCls; - me.cls = cls; + me.cls = cls } /** @@ -343,7 +343,7 @@ class Base extends Component { */ beforeSetSelectionModel(value, oldValue) { oldValue?.destroy(); - return ClassSystemUtil.beforeSetInstance(value, ListModel); + return ClassSystemUtil.beforeSetInstance(value, ListModel) } /** @@ -355,7 +355,7 @@ class Base extends Component { */ beforeSetStore(value, oldValue) { oldValue?.destroy(); - return ClassSystemUtil.beforeSetInstance(value, Store); + return ClassSystemUtil.beforeSetInstance(value, Store) } /** @@ -399,7 +399,7 @@ class Base extends Component { }; if (me.itemRole) { - item.role = me.itemRole; + item.role = me.itemRole } switch (Neo.typeOf(itemContent)) { @@ -427,15 +427,15 @@ class Base extends Component { item.style = item.style || {}; if (hasItemHeight && !item.hasOwnProperty('height')) { - item.style.height = `${me.itemHeight}px`; + item.style.height = `${me.itemHeight}px` } if (hasItemWidth && !item.hasOwnProperty('width')) { - item.style.width = `${me.itemWidth}px`; + item.style.width = `${me.itemWidth}px` } } - return item; + return item } /** @@ -454,12 +454,12 @@ class Base extends Component { if (filter && filter.value !== null && filter.value !== '') { itemText = itemText.replace(new RegExp(filter.value, 'gi'), function(match) { - return '' + match + ''; - }); + return '' + match + '' + }) } } - return itemText; + return itemText } /** @@ -474,7 +474,7 @@ class Base extends Component { // in case we set headerlessActiveIndex before the store was loaded, activeIndex can be null // and the wanted selection is not initially there if (Neo.isNumber(headerlessActiveIndex) && !Neo.isNumber(me.activeIndex)) { - me.afterSetHeaderlessActiveIndex(headerlessActiveIndex, null); + me.afterSetHeaderlessActiveIndex(headerlessActiveIndex, null) } if (!(me.animate && !me.getPlugin('animate'))) { @@ -482,12 +482,12 @@ class Base extends Component { me.store.items.forEach((item, index) => { listItem = me.createItem(item, index); - listItem && vdom.cn.push(listItem); + listItem && vdom.cn.push(listItem) }); !silent && me.promiseUpdate().then(() => { - me.fire('createItems'); - }); + me.fire('createItems') + }) } } @@ -501,7 +501,7 @@ class Base extends Component { me.autoDestroyStore && me.store?.destroy(); - super.destroy(...args); + super.destroy(...args) } /** @@ -532,17 +532,17 @@ class Base extends Component { len = headerlessIndex; if (records.length < 1) { - return null; + return null } for (; i <= len; i++) { if (records[i].isHeader) { delta++; - len++; + len++ } } - return headerlessIndex + delta; + return headerlessIndex + delta } /** @@ -557,11 +557,11 @@ class Base extends Component { for (; i < index; i++) { if (!records[i].isHeader) { - headerlessIndex++; + headerlessIndex++ } } - return headerlessIndex; + return headerlessIndex } /** @@ -569,7 +569,7 @@ class Base extends Component { * @returns {String} */ getItemId(recordId) { - return `${this.id}__${recordId}`; + return `${this.id}__${recordId}` } /** @@ -583,10 +583,10 @@ class Base extends Component { keyType = keyField?.type?.toLowerCase(); if (keyType === 'integer' || keyType === 'number') { - itemId = parseInt(itemId); + itemId = parseInt(itemId) } - return itemId; + return itemId } /** @@ -594,7 +594,7 @@ class Base extends Component { * @returns {String} */ getKeyProperty() { - return this.store.keyProperty || this.store.model.keyProperty; + return this.store.keyProperty || this.store.model.keyProperty } /** @@ -605,12 +605,12 @@ class Base extends Component { item; if (data.path[0].id === me.id) { - me.onContainerClick(data); + me.onContainerClick(data) } else { for (item of data.path) { if (item.cls.includes(me.itemCls)) { me.onItemClick(item, data); - break; + break } } } @@ -622,7 +622,7 @@ class Base extends Component { onConstructed() { super.onConstructed(); - this.selectionModel?.register(this); + this.selectionModel?.register(this) } /** @@ -637,7 +637,7 @@ class Base extends Component { * @param {String[]} path the event path * @returns {Object} */ - this.fire('containerClick', data); + this.fire('containerClick', data) } /** @@ -652,7 +652,7 @@ class Base extends Component { data.record = record; if (!me.disableSelection && (!me.useHeaders || !record.isHeader)) { - me.selectionModel?.select(node.id); + me.selectionModel?.select(node.id) } /** @@ -661,14 +661,14 @@ class Base extends Component { * @param {String} id the record matching the list item * @returns {Object} */ - me.fire('itemClick', record); + me.fire('itemClick', record) } /** * */ onStoreFilter() { - this.createItems(); + this.createItems() } /** @@ -681,10 +681,10 @@ class Base extends Component { if (!me.mounted && me.rendering) { listenerId = me.on('mounted', () => { me.un('mounted', listenerId); - me.createItems(); + me.createItems() }); } else { - me.createItems(); + me.createItems() } } @@ -703,7 +703,7 @@ class Base extends Component { // ignore changes for records which have not been added to the list yet if (index > -1) { me.vdom.cn[index] = me.createItem(data.record, index); - me.update(); + me.update() } } @@ -714,7 +714,7 @@ class Base extends Component { * @param {Neo.data.Store} data.scope */ onStoreSort(data) { - this.createItems(); + this.createItems() } /** @@ -722,7 +722,7 @@ class Base extends Component { * @param {Number} index */ selectItem(index) { - !this.disableSelection && this.selectionModel?.selectAt(index); + !this.disableSelection && this.selectionModel?.selectAt(index) } } From e7bb67ea1ce241148683e0985b18a8fe5a74a2e8 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 18 Sep 2023 14:43:53 +0200 Subject: [PATCH 080/162] v6.5.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 16e99eb88..6a7cfeb47 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.5.6", + "version": "6.5.7", "description": "The webworkers driven UI framework", "type": "module", "repository": { From db6d34d66a4610fe3004cba5651d65bb95de7aac Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 18 Sep 2023 14:44:39 +0200 Subject: [PATCH 081/162] v6.5.7 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- src/DefaultConfig.mjs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index 6b139c897..9aa93bff0 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.6' + * @member {String} version='6.5.7' */ - version: '6.5.6' + version: '6.5.7' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index 6b139c897..9aa93bff0 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.6' + * @member {String} version='6.5.7' */ - version: '6.5.6' + version: '6.5.7' } /** diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index 009e04907..7ee5c1bfa 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.5.6' + * @default '6.5.7' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.5.6' + version: '6.5.7' }; Object.assign(DefaultConfig, { From 810916d0d7b8fc6a8e07ed525ac197757c59ebe8 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 18 Sep 2023 15:00:46 +0200 Subject: [PATCH 082/162] workflows: npm publish needs an update #4921 --- .github/workflows/npm-publish.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index bd2be78e1..8983ffa16 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -1,17 +1,17 @@ -name: Node.js Package +name: Publish Package to npmjs on: release: - types: [created] + types: [ published ] jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # Setup .npmrc file to publish to npm - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v3 with: - node-version: '12.x' + node-version: '16.x' registry-url: 'https://registry.npmjs.org' - run: npm publish env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} \ No newline at end of file + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From d87f1946c0430f0eca8048b81326f990cc5e11c0 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 18 Sep 2023 15:01:58 +0200 Subject: [PATCH 083/162] v6.5.8 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index 9aa93bff0..1823df827 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.7' + * @member {String} version='6.5.8' */ - version: '6.5.7' + version: '6.5.8' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index 9aa93bff0..1823df827 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.7' + * @member {String} version='6.5.8' */ - version: '6.5.7' + version: '6.5.8' } /** diff --git a/package.json b/package.json index 6a7cfeb47..860bae40b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.5.7", + "version": "6.5.8", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index 7ee5c1bfa..4fcc151f7 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.5.7' + * @default '6.5.8' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.5.7' + version: '6.5.8' }; Object.assign(DefaultConfig, { From 37159ac2c5b01f81e169fc09e928caf3311df800 Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 19 Sep 2023 10:39:36 +0200 Subject: [PATCH 084/162] button.Base: afterSetMenu() => pass the parent model, if available #4923 --- src/button/Base.mjs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/button/Base.mjs b/src/button/Base.mjs index 868cf06ed..26241c531 100644 --- a/src/button/Base.mjs +++ b/src/button/Base.mjs @@ -256,6 +256,7 @@ class Base extends Component { isArray = Array.isArray(value), items = isArray ? value : value.items, menuConfig = isArray ? {} : value, + model = me.getModel(), config = { module : module.default, @@ -273,6 +274,10 @@ class Base extends Component { config.items = items } + if (model) { + config.model = {parent: model} + } + me.menuList = Neo.create(config) }) } From fe69432dd75946c70d766a7194a03dabbbb79e65 Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 19 Sep 2023 10:46:31 +0200 Subject: [PATCH 085/162] menu.List: minor cleanup --- src/menu/List.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menu/List.mjs b/src/menu/List.mjs index 8e5e9586b..84ce9673f 100644 --- a/src/menu/List.mjs +++ b/src/menu/List.mjs @@ -364,13 +364,13 @@ class List extends BaseList { subMenuMap = me.subMenuMap || (me.subMenuMap = {}), subMenuMapId = me.getMenuMapId(recordId), subMenu = subMenuMap[subMenuMapId] || (subMenuMap[subMenuMapId] = Neo.create({ + module : List, align : { target : nodeId, edgeAlign : 'l0-r0', axisLock : true, targetMargin : me.subMenuGap }, - module : List, appName : me.appName, displayField : me.displayField, floating : true, From 05744c18f3b325165ef16e6089368638a61aa6cd Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 19 Sep 2023 10:48:02 +0200 Subject: [PATCH 086/162] dependencies update --- package-lock.json | 18 +++++++++--------- package.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 233846861..d3829d05c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "neo.mjs", - "version": "6.5.5", + "version": "6.5.8", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "neo.mjs", - "version": "6.5.5", + "version": "6.5.8", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-free": "^6.4.2", @@ -23,7 +23,7 @@ "inquirer": "^9.2.11", "neo-jsdoc": "1.0.1", "neo-jsdoc-x": "1.0.5", - "postcss": "^8.4.29", + "postcss": "^8.4.30", "sass": "^1.67.0", "showdown": "^2.1.0", "webpack": "^5.88.2", @@ -5387,9 +5387,9 @@ } }, "node_modules/postcss": { - "version": "8.4.29", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz", - "integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==", + "version": "8.4.30", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.30.tgz", + "integrity": "sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==", "funding": [ { "type": "opencollective", @@ -11195,9 +11195,9 @@ "dev": true }, "postcss": { - "version": "8.4.29", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz", - "integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==", + "version": "8.4.30", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.30.tgz", + "integrity": "sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==", "requires": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", diff --git a/package.json b/package.json index 860bae40b..acae95926 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "inquirer": "^9.2.11", "neo-jsdoc": "1.0.1", "neo-jsdoc-x": "1.0.5", - "postcss": "^8.4.29", + "postcss": "^8.4.30", "sass": "^1.67.0", "showdown": "^2.1.0", "webpack": "^5.88.2", From cea87862c7a5f927b931c9d633a21e9f6e3d65a0 Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 19 Sep 2023 10:48:49 +0200 Subject: [PATCH 087/162] v6.5.9 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index 1823df827..36ecfb715 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.8' + * @member {String} version='6.5.9' */ - version: '6.5.8' + version: '6.5.9' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index 1823df827..36ecfb715 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.8' + * @member {String} version='6.5.9' */ - version: '6.5.8' + version: '6.5.9' } /** diff --git a/package.json b/package.json index acae95926..972e82f82 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.5.8", + "version": "6.5.9", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index 4fcc151f7..a263d1288 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.5.8' + * @default '6.5.9' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.5.8' + version: '6.5.9' }; Object.assign(DefaultConfig, { From cd2af3d89cadeb91a44035fc73cba1698a4aff38 Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 19 Sep 2023 13:41:20 +0200 Subject: [PATCH 088/162] menu.List: afterSetItems() => proper way to remove old items #4925 --- src/menu/List.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menu/List.mjs b/src/menu/List.mjs index 84ce9673f..23e5b20fd 100644 --- a/src/menu/List.mjs +++ b/src/menu/List.mjs @@ -118,7 +118,7 @@ class List extends BaseList { afterSetItems(value, oldValue) { let store = this.store; - oldValue && store.remove(oldValue); + oldValue && store.clear(); // we can not use remove() here, since items are no records => often no id value && store.add(value) } From 7f97878dc3faf5e1a01ba2e68a27700fc11dc859 Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 19 Sep 2023 13:41:57 +0200 Subject: [PATCH 089/162] v6.5.10 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index 36ecfb715..0d10c4b77 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.9' + * @member {String} version='6.5.10' */ - version: '6.5.9' + version: '6.5.10' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index 36ecfb715..0d10c4b77 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.9' + * @member {String} version='6.5.10' */ - version: '6.5.9' + version: '6.5.10' } /** diff --git a/package.json b/package.json index 972e82f82..3959cafd9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.5.9", + "version": "6.5.10", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index a263d1288..1d81bbb06 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.5.9' + * @default '6.5.10' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.5.9' + version: '6.5.10' }; Object.assign(DefaultConfig, { From edfa87b9d454e12cf89ac1872acc772b618e36e5 Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 20 Sep 2023 16:25:49 +0200 Subject: [PATCH 090/162] component.Base: get parent() convenience shortcut #4927 --- src/component/Base.mjs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/component/Base.mjs b/src/component/Base.mjs index aadb84b96..cab9dac60 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -367,6 +367,14 @@ class Base extends CoreBase { this._listeners = value } + /** + * Convenience method to access the parent component + * @returns {Neo.component.Base|null} + */ + get parent() { + return this.parentId !== 'document.body' ? Neo.getComponent(this.parentId) : null + } + /** * True after the component render() method was called. Also fires the rendered event. * @member {Boolean} rendered=false From 6bc01cff63be9fd8ad4bad7a40349e7882f081e9 Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 20 Sep 2023 16:39:23 +0200 Subject: [PATCH 091/162] #4927 adjusting relevant spots --- apps/sharedcovid/view/MainContainerController.mjs | 6 +++--- examples/tabs/MainContainer.mjs | 4 ++-- src/calendar/view/SettingsContainer.mjs | 4 ++-- src/component/Splitter.mjs | 8 ++++---- src/controller/Component.mjs | 8 ++------ src/grid/View.mjs | 2 +- src/model/Component.mjs | 8 ++------ 7 files changed, 16 insertions(+), 24 deletions(-) diff --git a/apps/sharedcovid/view/MainContainerController.mjs b/apps/sharedcovid/view/MainContainerController.mjs index 3183e3c71..47b897f7f 100644 --- a/apps/sharedcovid/view/MainContainerController.mjs +++ b/apps/sharedcovid/view/MainContainerController.mjs @@ -255,10 +255,10 @@ class MainContainerController extends ComponentController { console.log('onAppConnect', name); - switch (name) { + switch(name) { case 'SharedCovidChart': - view = me.getReference('controls-panel'); - parentView = Neo.getComponent(view.parentId); + view = me.getReference('controls-panel'); + parentView = view.parent; parentView.storeReferences(); toolbar = me.getReference('controls-panel-header'); diff --git a/examples/tabs/MainContainer.mjs b/examples/tabs/MainContainer.mjs index 0231e6521..c9153d1d3 100644 --- a/examples/tabs/MainContainer.mjs +++ b/examples/tabs/MainContainer.mjs @@ -97,7 +97,7 @@ class MainContainer extends TabContainer { text : 'Move Fields', handler: function () { let field = Neo.getComponent('firstNameField'), - parent = Neo.getComponent(field.parentId), + parent = field.parent, cn = parent.vdom.cn, tmp = cn[1]; @@ -114,7 +114,7 @@ class MainContainer extends TabContainer { text : 'Insert Textfield', handler: function () { let button = Neo.getComponent('firstNameField'), - parent = Neo.getComponent(button.parentId); + parent = button.parent; // global variable for testing if (!this.fieldCount) { diff --git a/src/calendar/view/SettingsContainer.mjs b/src/calendar/view/SettingsContainer.mjs index 17e70fba7..3c5808e3f 100644 --- a/src/calendar/view/SettingsContainer.mjs +++ b/src/calendar/view/SettingsContainer.mjs @@ -57,7 +57,7 @@ class SettingsContainer extends Container { me._style = style; // silent update me._vdom.style = style; // silent update - Neo.getComponent(me.parentId).promiseUpdate().then(() => { + me.parent.promiseUpdate().then(() => { setTimeout(() => { me.collapsed = true; @@ -145,7 +145,7 @@ class SettingsContainer extends Container { delete me.vdom.removeDom; - Neo.getComponent(me.parentId).promiseUpdate().then(() => { + me.parent.promiseUpdate().then(() => { me.collapsed = false; me.mounted = true; diff --git a/src/component/Splitter.mjs b/src/component/Splitter.mjs index ad9a56b35..53836bbe8 100644 --- a/src/component/Splitter.mjs +++ b/src/component/Splitter.mjs @@ -145,12 +145,13 @@ class Splitter extends Component { onDragEnd(data) { let me = this, style = me.style || {}, + parent = me.parent, parentId = me.parentId, resizeNext = me.resizeTarget === 'next', size = me.size, - index, newSize, sibling, parent; + index, newSize, sibling; - Neo.getComponent(parentId).disabled = false; + parent.disabled = false; me.dragZone.dragEnd(data); @@ -159,7 +160,6 @@ class Splitter extends Component { me.style = style; me.getDomRect(parentId).then(parentRect => { - parent = Neo.getComponent(parentId); index = parent.indexOf(me); sibling = parent.items[resizeNext ? index + 1 :index - 1]; style = sibling.style || {}; @@ -204,7 +204,7 @@ class Splitter extends Component { style = me.style || {}, vertical = me.direction === 'vertical'; - Neo.getComponent(me.parentId).disabled = true; + me.parent.disabled = true; if (!me.dragZone) { me.dragZone = Neo.create({ diff --git a/src/controller/Component.mjs b/src/controller/Component.mjs index fd5d033e9..a08f5981c 100644 --- a/src/controller/Component.mjs +++ b/src/controller/Component.mjs @@ -93,17 +93,13 @@ class Component extends Base { * @returns {Neo.controller.Component|null} */ getParent() { - let me = this, - parentComponent, parentId; + let me = this; if (me.parent) { return me.parent; } - parentId = me.component.parentId; - parentComponent = parentId && Neo.getComponent(parentId); - - return parentComponent?.getController() || null + return me.component.parent?.getController() || null } /** diff --git a/src/grid/View.mjs b/src/grid/View.mjs index 108a6a115..2c2b0c7f9 100644 --- a/src/grid/View.mjs +++ b/src/grid/View.mjs @@ -29,7 +29,7 @@ class View extends Component { createViewData(inputData) { let me = this, amountRows = inputData.length, - container = Neo.getComponent(me.parentId), + container = me.parent, columns = container.items[0].items, colCount = columns.length, data = [], diff --git a/src/model/Component.mjs b/src/model/Component.mjs index dfa8a5d99..207654dd7 100644 --- a/src/model/Component.mjs +++ b/src/model/Component.mjs @@ -445,17 +445,13 @@ class Component extends Base { * @returns {Neo.model.Component|null} */ getParent() { - let me = this, - parentComponent, parentId; + let me = this; if (me.parent) { return me.parent } - parentId = me.component.parentId; - parentComponent = parentId && Neo.getComponent(parentId); - - return parentComponent?.getModel() || null + return me.component.parent?.getModel() || null } /** From f43c92efca252573d8d46419dd0eef0e27aabf50 Mon Sep 17 00:00:00 2001 From: Nige White Date: Fri, 15 Sep 2023 17:15:48 +0200 Subject: [PATCH 092/162] WIP --- examples/dialog/DemoDialog.mjs | 39 ++++- examples/dialog/MainContainer.mjs | 10 +- resources/scss/src/dialog/Base.scss | 37 ++--- src/component/Base.mjs | 30 ++-- src/dialog/Base.mjs | 211 ++++++++++++++++------------ src/main/DomAccess.mjs | 32 +++++ 6 files changed, 235 insertions(+), 124 deletions(-) diff --git a/examples/dialog/DemoDialog.mjs b/examples/dialog/DemoDialog.mjs index 0041eac9c..3eadfd201 100644 --- a/examples/dialog/DemoDialog.mjs +++ b/examples/dialog/DemoDialog.mjs @@ -1,5 +1,6 @@ import Dialog from '../../src/dialog/Base.mjs'; import SelectField from '../../src/form/field/Select.mjs'; +import Button from '../../src/button/Base.mjs'; /** * @class Neo.examples.dialog.DemoDialog @@ -13,9 +14,15 @@ class DemoDialog extends Dialog { wrapperStyle: { width : '40%' - }, + } + } + + construct() { + super.construct(...arguments); + + const me = this; - items : [{ + me.items = [{ module : SelectField, labelText: 'Select', @@ -33,7 +40,33 @@ class DemoDialog extends Dialog { return result; })() } - }] + }, { + module : Button, + handler: me.createDialog.bind(me), + iconCls: 'fa fa-window-maximize', + text : 'Create new modal Dialog', + }]; + } + + createDialog(data) { + let me = this; + + data.component.disabled = true; + + me.dialog = Neo.create(DemoDialog, { + appName : me.appName, + boundaryContainerId: me.boundaryContainerId, + listeners : {close: me.onWindowClose, scope: me}, + modal : true + }); + } + + onWindowClose() { + let button = this.down({ + text: 'Create new modal Dialog' + }); + + button.disabled = false; } } diff --git a/examples/dialog/MainContainer.mjs b/examples/dialog/MainContainer.mjs index 516c56085..1baf91e31 100644 --- a/examples/dialog/MainContainer.mjs +++ b/examples/dialog/MainContainer.mjs @@ -50,6 +50,13 @@ class MainContainer extends Viewport { listeners : {change: me.onDragLimitChange, scope: me}, style : {marginLeft: '3em'}, valueLabelText: 'Limit Drag&Drop to the document.body' + }, { + module : CheckBox, + checked : true, + hideLabel : true, + hideValueLabel: false, + style : {marginLeft: '3em'}, + valueLabelText: 'Modal' }, '->', { module : Button, handler: me.switchTheme.bind(me), @@ -71,7 +78,8 @@ class MainContainer extends Viewport { animateTargetId : data.component.id, appName : me.appName, boundaryContainerId: me.boundaryContainerId, - listeners : {close: me.onWindowClose, scope: me} + listeners : {close: me.onWindowClose, scope: me}, + modal : me.down({ valueLabelText : 'Modal' }).checked }); } diff --git a/resources/scss/src/dialog/Base.scss b/resources/scss/src/dialog/Base.scss index 62180785e..0329ab9c0 100644 --- a/resources/scss/src/dialog/Base.scss +++ b/resources/scss/src/dialog/Base.scss @@ -17,20 +17,33 @@ } } -.neo-dialog-wrapper { - display : flex; - position: absolute; - z-index : 20; // ensure to be on top of table headers +.neo-dialog-modal-mask { + position : fixed; + top : 0; + left : 0; + bottom : 0; + right : 0; + background-color : rgba(100, 100, 100, 0.5); + backdrop-filter : blur(1px); + z-index : 1000; +} - transition-duration : 200ms; - transition-property : height, left, top, transform, width; - transition-timing-function: ease-out; +.neo-dialog { + border : 1px solid var(--dialog-border-color); + display : flex; + flex-direction: column; + + &.animated-hiding-showing { + transition-duration : 200ms; + transition-property : height, left, top, transform, width; + transition-timing-function: ease-out; + } &.neo-maximized { height : 98% !important; left : 1% !important; top : 1% !important; - transform: none; + transform: none!important; width : 98% !important; &.neo-panel { @@ -41,14 +54,6 @@ } } } -} - -.neo-dialog { - border : 1px solid var(--dialog-border-color); - display : flex; - flex : 1 0 auto; - flex-direction: column; - position : relative; &.neo-panel { .neo-footer-toolbar { diff --git a/src/component/Base.mjs b/src/component/Base.mjs index cab9dac60..afe414c60 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -902,20 +902,24 @@ class Base extends CoreBase { * @returns {Promise} */ async alignTo(spec={}) { - const me = this; + const + me = this, + align = { + ...me.align, + ...spec, + id : me.id, + configuredFlex : me.configuredFlex, + configuredWidth : me.configuredWidth, + configuredHeight : me.configuredHeight, + configuredMinWidth : me.configuredMinWidth, + configuredMinHeight : me.configuredMinHeight, + configuredMaxWidth : me.configuredMaxWidth, + configuredMaxHeight : me.configuredMaxHeight + }; - await Neo.main.DomAccess.align({ - ...me.align, - ...spec, - id : me.id, - configuredFlex : me.configuredFlex, - configuredWidth : me.configuredWidth, - configuredHeight : me.configuredHeight, - configuredMinWidth : me.configuredMinWidth, - configuredMinHeight : me.configuredMinHeight, - configuredMaxWidth : me.configuredMaxWidth, - configuredMaxHeight : me.configuredMaxHeight - }); + if (align.target) { + await Neo.main.DomAccess.align(align); + } } /** diff --git a/src/dialog/Base.mjs b/src/dialog/Base.mjs index bc450e543..b73a0d2ca 100644 --- a/src/dialog/Base.mjs +++ b/src/dialog/Base.mjs @@ -116,13 +116,12 @@ class Base extends Panel { * @member {String|null} title_=null */ title_: null, - /** - * @member {Object} _vdom - */ - _vdom: - {cls: ['neo-dialog-wrapper'], cn: [ - {cn: []} - ]} + + floating : true, + + modal_ : null, + + autoShow : true } /** @@ -133,11 +132,14 @@ class Base extends Panel { let me = this; - me.vdom.id = me.getWrapperId(); - me.createHeader(); - me.animateTargetId && me.animateShow(); + if (me.animateTargetId) { + me.animateShow(); + } + else if (me.autoShow) { + me.show(); + } } /** @@ -224,22 +226,15 @@ class Base extends Panel { me.update(); } - /** - * Triggered after the mounted config got changed - * @param {Boolean} value - * @param {Boolean} oldValue - * @protected - */ - afterSetMounted(value, oldValue) { - super.afterSetMounted(value, oldValue); + afterSetModal(modal) { + const + me = this, + { cls } = me.vdom; - let me = this; - - if (value && me.animateTargetId) { - Neo.applyDeltas(me.appName, { - action: 'removeNode', - id : me.getAnimateTargetId() - }) + NeoArray[modal ? 'add' : 'remove'](cls, 'neo-modal'); + me.update(); + if (me.rendered) { + me.syncModalMask(); } } @@ -286,18 +281,24 @@ class Base extends Panel { async animateHide() { let me = this, appName = me.appName, - id = me.getAnimateTargetId(), - rects = await me.getDomRect([me.id, me.animateTargetId]); + { id } = me, + rects = await me.getDomRect([id, me.animateTargetId]); await Neo.currentWorker.promiseMessage('main', { - action : 'mountDom', + action: 'updateDom', appName, - html : `
`, - parentId: 'document.body' + deltas: [{ + id, + style: { + height : `${rects[0].height}px`, + left : `${rects[0].left }px`, + top : `${rects[0].top }px`, + width : `${rects[0].width }px`, + transform : 'none' + } + }] }); - me.closeOrHide(false); - await me.timeout(30); await Neo.currentWorker.promiseMessage('main', { @@ -310,16 +311,29 @@ class Base extends Panel { left : `${rects[1].left }px`, top : `${rects[1].top }px`, width : `${rects[1].width }px` + }, + cls: { + add : ['animated-hiding-showing'] } }] }); await me.timeout(250); + me.closeOrHide(false); + await Neo.currentWorker.promiseMessage('main', { action: 'updateDom', appName, - deltas: [{action: 'removeNode', id}] + deltas: [{ + id, + cls : { + remove : ['animated-hiding-showing'] + } + }, { + id, + action: 'removeNode' + }] }); } @@ -327,38 +341,65 @@ class Base extends Panel { * */ async animateShow() { - let me = this, - appName = me.appName, - id = me.getAnimateTargetId(), - wrapperStyle = me.wrapperStyle, - rect = await me.getDomRect(me.animateTargetId); + let me = this, + appName = me.appName, + { style } = me, + rect = await me.getDomRect(me.animateTargetId); + await me.render(true); + + // Move to cover the animation target await Neo.currentWorker.promiseMessage('main', { - action : 'mountDom', + action: 'updateDom', appName, - html : `
`, - parentId: 'document.body' + deltas: [{ + id : me.id, + style : { + top : `${rect.top}px`, + left : `${rect.left}px`, + width : `${rect.width}px`, + height : `${rect.height}px` + } + }] }); - await me.timeout(30); + // Wait for the element to achieve its initial rectangle + await me.timeout(50); + // Expand to final state await Neo.currentWorker.promiseMessage('main', { action: 'updateDom', appName, deltas: [{ - id, - style: { - height : wrapperStyle?.height || '50%', - left : wrapperStyle?.left || '50%', - top : wrapperStyle?.top || '50%', - transform: wrapperStyle?.transform || 'translate(-50%, -50%)', - width : wrapperStyle?.width || '50%' + id : me.id, + style : { + height : style?.height || '', + left : style?.left || '50%', + top : style?.top || '50%', + transform: style?.transform || 'translate(-50%, -50%)', + width : style?.width || '50%' + }, + cls: { + add : ['animated-hiding-showing'], + remove: [] } }] }); await me.timeout(200); + // Remove the animation class + await Neo.currentWorker.promiseMessage('main', { + action: 'updateDom', + appName, + deltas: [{ + id : me.id, + cls : { + remove : ['animated-hiding-showing'] + } + }] + }); + me.show(false) } @@ -389,8 +430,12 @@ class Base extends Panel { /** * @param {Boolean} [animate=!!this.animateTargetId] */ - closeOrHide(animate=!!this.animateTargetId) { + async closeOrHide(animate=!!this.animateTargetId) { + const { id } = this; + this[this.closeAction](animate); + await this.timeout(30); + this.syncModalMask(id); } /** @@ -461,39 +506,13 @@ class Base extends Panel { getProxyVdom() { let vdom = VDomUtil.clone(this.vdom); - // this call expects a fixed dialog structure - // todo: a panel content container could get a flag which we can query for instead - vdom.cn[0].cn[1].cn = []; - return vdom; } - /** - * @returns {Object} The new vdom root - */ - getVdomRoot() { - return this.vdom.cn[0]; - } - - /** - * @returns {Object} The new vnode root - */ - getVnodeRoot() { - return this.vnode.childNodes[0]; - } - - /** - * Returns the id of the header toolbar - * @returns {String} - */ - getWrapperId() { - return this.id + '-wrapper'; - } - /** * @param {Boolean} [animate=!!this.animateTargetId] */ - hide(animate=!!this.animateTargetId) { + async hide(animate=!!this.animateTargetId) { let me = this; if (animate) { @@ -502,6 +521,8 @@ class Base extends Panel { me.unmount(); me.fire('hide'); } + await me.timeout(30); + me.syncModalMask(); } /** @@ -533,13 +554,13 @@ class Base extends Panel { */ onDragEnd(data) { let me = this, - initialTransitionProperty, wrapperStyle; + initialTransitionProperty, style; if (!me.maximized) { me.getDomRect(me.dragZone.dragProxy.id).then(rect => { - wrapperStyle = me.wrapperStyle; + style = me.style; - Object.assign(wrapperStyle, { + Object.assign(style, { height : `${rect.height}px`, left : `${rect.left}px`, opacity : 1, @@ -549,20 +570,20 @@ class Base extends Panel { }); if (!me.animateOnDragEnd) { - initialTransitionProperty = wrapperStyle.transitionProperty || null; + initialTransitionProperty = style.transitionProperty || null; - wrapperStyle.transitionProperty = 'none'; + style.transitionProperty = 'none'; setTimeout(() => { - wrapperStyle = me.wrapperStyle; + style = me.style; - wrapperStyle.transitionProperty = initialTransitionProperty; + style.transitionProperty = initialTransitionProperty; - me.wrapperStyle = wrapperStyle; + me.style = style; }, 50); } - me.wrapperStyle = wrapperStyle; + me.style = style; me.dragZone.dragEnd(data); @@ -585,8 +606,8 @@ class Base extends Panel { * @param data */ onDragStart(data) { - let me = this, - wrapperStyle = me.wrapperStyle || {}, + let me = this, + style = me.style || {}, resizablePlugin; if (!me.maximized) { @@ -621,9 +642,9 @@ class Base extends Panel { me.dragZone.dragStart(data); - wrapperStyle.opacity = 0.7; + style.opacity = 0.7; - me.wrapperStyle = wrapperStyle; + me.style = style; } } @@ -636,9 +657,17 @@ class Base extends Panel { if (animate) { me.animateShow(); } else { - me.render(true); + if (!me.rendered) { + me.render(true); + } me.fire('show'); } + me.syncModalMask(); + } + + syncModalMask(id = this.id) { + // This should sync the visibility and position of the modal mask element. + Neo.main.DomAccess.syncModalMask({ id, modal : this.modal }); } } diff --git a/src/main/DomAccess.mjs b/src/main/DomAccess.mjs index c60a4a538..1e0ef902b 100644 --- a/src/main/DomAccess.mjs +++ b/src/main/DomAccess.mjs @@ -77,6 +77,7 @@ class DomAccess extends Base { 'selectNode', 'setBodyCls', 'setStyle', + 'syncModalMask', 'windowScrollTo' ] }, @@ -813,6 +814,37 @@ class DomAccess extends Base { }) } + syncModalMask({ id, modal }) { + const el = id && this.getElement(id); + + // If we are visible and modal, the mask needs to be just below this element. + if (el && modal && el.ownerDocument.contains(el) && el.ownerDocument.defaultView.getComputedStyle(el).getPropertyValue('display') !== 'none') { + document.body.insertBefore(this.modalMask, el); + } + // Otherwise, the mask needs to be blow the next topmost modal dialog if possible, or hidden + else { + const + modals = document.querySelectorAll('.neo-modal'), + topmostModal = modals[modals.length - 1]; + + // Move the mask under the next topmost modal now modal "id" is gone. + if (topmostModal) { + this.syncModalMask({ id : topmostModal.id, modal : true }) + } + else { + this._modalMask?.remove(); + } + } + } + + get modalMask() { + if (!this._modalMask) { + this._modalMask = document.createElement('div'); + this._modalMask.className = 'neo-dialog-modal-mask'; + } + return this._modalMask; + } + /** * @param {Object} data * @param {String} [data.behavior='smooth'] // auto or smooth From e8130f1fc5cece22415132bf0adfadf858bf671f Mon Sep 17 00:00:00 2001 From: Nige White Date: Wed, 20 Sep 2023 17:40:57 +0200 Subject: [PATCH 093/162] Fix latest FileUpload bugs --- src/form/field/FileUpload.mjs | 56 ++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/src/form/field/FileUpload.mjs b/src/form/field/FileUpload.mjs index 6e2206e8e..e7a1afb35 100644 --- a/src/form/field/FileUpload.mjs +++ b/src/form/field/FileUpload.mjs @@ -459,6 +459,10 @@ class FileUpload extends Base { me.update(); me.state = 'uploading'; + // This means no progress as opposed to zero, but still during a currently successful ongoing upload. + // When it is NaN, the error display does not attempt to show progress. + me.progress = NaN; + fileData.append("file", file); // React to upload state changes @@ -517,28 +521,38 @@ class FileUpload extends Base { me.xhr = null; - if (loaded !== 0) { - const response = JSON.parse(xhr.response); - - if (response.success) { - me.documentId = response[me.documentIdParameter]; - - // The status check phase is optional. - // If no URL specified, the file is taken to be downloadable. - if (me.documentStatusUrl) { - me.state = 'processing'; - - // Start polling the server to see when the scan has a result; - me.checkDocumentStatus(); + // Successful network request. + // Check the resulting JSON packet for details and any error. + if (String(xhr.status).startsWith('2')) { + if (loaded !== 0) { + const response = JSON.parse(xhr.response); + + if (response.success) { + me.documentId = response[me.documentIdParameter]; + + // The status check phase is optional. + // If no URL specified, the file is taken to be downloadable. + if (me.documentStatusUrl) { + me.state = 'processing'; + + // Start polling the server to see when the scan has a result; + me.checkDocumentStatus(); + } + else { + me.state = 'downloadable'; + } } else { - me.state = 'downloadable'; + me.error = response.message; + me.state = 'upload-failed'; } } - else { - me.error = response.message; - me.state = 'upload-failed'; - } + } + // Failed network request + else { + me.progress = NaN; + me.error = `HTTP status : ${xhr.statusText}`; + me.state = 'upload-failed'; } } @@ -693,7 +707,7 @@ class FileUpload extends Base { anchor.href = ''; break; case 'upload-failed': - status.innerHTML = `${me.uploadFailed}... (${Math.round(me.progress * 100)}%)`; + status.innerHTML = `${me.uploadFailed}${isNaN(me.progress) ? '' : `... (${Math.round(me.progress * 100)}%)`}`; break; case 'processing': status.innerHTML = `${me.scanning}... (${me.formatSize(me.uploadSize)})`; @@ -806,9 +820,9 @@ class FileUpload extends Base { if (bytes) { const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'], - i = Math.min(parseInt(Math.floor(Math.log(bytes) / Math.log(1024)).toString(), 10), sizes.length - 1); + i = Math.min(parseInt(Math.floor(Math.log(bytes) / Math.log(1000)).toString(), 10), sizes.length - 1); - return `${(bytes / (1024 ** i)).toFixed(i ? 1 : 0)}${separator}${sizes[i]}${postFix}`; + return `${(bytes / (1000 ** i)).toFixed(i ? 1 : 0)}${separator}${sizes[i]}${postFix}`; } return 'n/a'; } From 38886e80c52ef6e3a0f2fd1da3f4fc4f5803d7f2 Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 20 Sep 2023 18:30:28 +0200 Subject: [PATCH 094/162] examples.dialog.DemoDialog: cleanup --- examples/dialog/DemoDialog.mjs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/examples/dialog/DemoDialog.mjs b/examples/dialog/DemoDialog.mjs index 3eadfd201..8e52776ce 100644 --- a/examples/dialog/DemoDialog.mjs +++ b/examples/dialog/DemoDialog.mjs @@ -1,6 +1,6 @@ +import Button from '../../src/button/Base.mjs'; import Dialog from '../../src/dialog/Base.mjs'; import SelectField from '../../src/form/field/Select.mjs'; -import Button from '../../src/button/Base.mjs'; /** * @class Neo.examples.dialog.DemoDialog @@ -17,8 +17,11 @@ class DemoDialog extends Dialog { } } - construct() { - super.construct(...arguments); + /** + * @param {Object} config + */ + construct(config) { + super.construct(config); const me = this; @@ -48,6 +51,9 @@ class DemoDialog extends Dialog { }]; } + /** + * @param {Object} data + */ createDialog(data) { let me = this; From 9171c69ba77e130c1cbdbc5fb55b671d0fceb9d6 Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 20 Sep 2023 19:00:32 +0200 Subject: [PATCH 095/162] dialog.Base: cleanup --- src/dialog/Base.mjs | 110 ++++++++++++++++++++++++-------------------- 1 file changed, 60 insertions(+), 50 deletions(-) diff --git a/src/dialog/Base.mjs b/src/dialog/Base.mjs index b73a0d2ca..6f692cdce 100644 --- a/src/dialog/Base.mjs +++ b/src/dialog/Base.mjs @@ -45,6 +45,10 @@ class Base extends Panel { * @member {Boolean} autoRender=true */ autoRender: true, + /** + * @member {Boolean} autoShow=true + */ + autoShow: true, /** * @member {String[]} baseCls=['neo-dialog','neo-panel','neo-container'] * @protected @@ -79,6 +83,10 @@ class Base extends Panel { * @member {Object} dragZoneConfig=null */ dragZoneConfig: null, + /** + * @member {Boolean} floating=true + */ + floating: true, /** * @member {Object} headerConfig=null */ @@ -104,6 +112,10 @@ class Base extends Panel { * @member {String} minimizeCls='far fa-window-minimize' */ minimizeCls: 'far fa-window-minimize', + /** + * @member {Boolean} modal_=false + */ + modal_: false, /** * @member {Boolean} resizable_=true */ @@ -115,13 +127,7 @@ class Base extends Panel { /** * @member {String|null} title_=null */ - title_: null, - - floating : true, - - modal_ : null, - - autoShow : true + title_: null } /** @@ -134,12 +140,7 @@ class Base extends Panel { me.createHeader(); - if (me.animateTargetId) { - me.animateShow(); - } - else if (me.autoShow) { - me.show(); - } + me.autoShow && me.show() } /** @@ -150,7 +151,7 @@ class Base extends Panel { */ afterSetAnimateTargetId(value, oldValue) { this.autoMount = !value; - this.autoRender = !value; + this.autoRender = !value } /** @@ -164,14 +165,14 @@ class Base extends Panel { resizable = me.getPlugin({flag: 'resizable'}); if (me.dragZone) { - me.dragZone.appName = value; + me.dragZone.appName = value } if (resizable) { - resizable.appName = value; + resizable.appName = value } - super.afterSetAppName(value, oldValue); + super.afterSetAppName(value, oldValue) } /** @@ -188,7 +189,7 @@ class Base extends Panel { if (oldValue !== undefined && me.headerToolbar) { cls = me.headerToolbar.cls; NeoArray[value ? 'add' : 'remove'](cls, 'neo-draggable'); - me.headerToolbar.cls = cls; + me.headerToolbar.cls = cls } value && import('../draggable/DragZone.mjs').then(module => { @@ -207,9 +208,9 @@ class Base extends Panel { } me.domListeners = domListeners; - me.dragListenersAdded = true; + me.dragListenersAdded = true } - }); + }) } /** @@ -219,22 +220,29 @@ class Base extends Panel { * @protected */ afterSetMaximized(value, oldValue) { - let me = this, - cls = me.vdom.cls; // todo: using wrapperCls + let me = this, + cls = me.vdom.cls; // todo: using wrapperCls - NeoArray[value ? 'add' : 'remove'](cls, 'neo-maximized'); + NeoArray.toggle(cls, 'neo-maximized', value); me.update(); } - afterSetModal(modal) { + /** + * Triggered after the modal config got changed + * @param {Boolean} value + * @param {Boolean} oldValue + * @protected + */ + afterSetModal(value, oldValue) { const me = this, { cls } = me.vdom; - NeoArray[modal ? 'add' : 'remove'](cls, 'neo-modal'); + NeoArray.toggle(cls, 'neo-modal', value); me.update(); + if (me.rendered) { - me.syncModalMask(); + me.syncModalMask() } } @@ -258,9 +266,9 @@ class Base extends Panel { ...me.resizablePluginConfig }); - me.plugins = plugins; + me.plugins = plugins } - }); + }) } /** @@ -271,7 +279,7 @@ class Base extends Panel { */ afterSetTitle(value, oldValue) { if (this.headerToolbar) { - this.headerToolbar.title = value; + this.headerToolbar.title = value } } @@ -334,7 +342,7 @@ class Base extends Panel { id, action: 'removeNode' }] - }); + }) } /** @@ -414,28 +422,28 @@ class Base extends Panel { } /** - * @param {Boolean} [animate=!!this.animateTargetId] + * @param {Boolean} animate=!!this.animateTargetId */ close(animate=!!this.animateTargetId) { let me = this; if (animate) { - me.animateHide(); + me.animateHide() } else { me.fire('close'); - me.destroy(true); + me.destroy(true) } } /** - * @param {Boolean} [animate=!!this.animateTargetId] + * @param {Boolean} animate=!!this.animateTargetId */ async closeOrHide(animate=!!this.animateTargetId) { const { id } = this; this[this.closeAction](animate); await this.timeout(30); - this.syncModalMask(id); + this.syncModalMask(id) } /** @@ -462,7 +470,7 @@ class Base extends Panel { headers.unshift(me.headerToolbar); - me.headers = headers; + me.headers = headers } /** @@ -489,7 +497,7 @@ class Base extends Panel { * @returns {String} */ getAnimateTargetId() { - return this.id + '-animate'; + return this.id + '-animate' } /** @@ -497,32 +505,32 @@ class Base extends Panel { * @returns {String} */ getHeaderToolbarId() { - return this.id + '-header-toolbar'; + return this.id + '-header-toolbar' } /** * @returns {Object} vdom */ getProxyVdom() { - let vdom = VDomUtil.clone(this.vdom); - - return vdom; + return VDomUtil.clone(this.vdom); } /** - * @param {Boolean} [animate=!!this.animateTargetId] + * @param {Boolean} animate=!!this.animateTargetId */ async hide(animate=!!this.animateTargetId) { let me = this; if (animate) { - me.animateHide(); + me.animateHide() } else { me.unmount(); - me.fire('hide'); + me.fire('hide') } + await me.timeout(30); - me.syncModalMask(); + + me.syncModalMask() } /** @@ -546,7 +554,7 @@ class Base extends Panel { me.headerToolbar = me.down({ id: me.getHeaderToolbarId() - }); + }) } /** @@ -649,7 +657,7 @@ class Base extends Panel { } /** - * @param {Boolean} [animate=!!this.animateTargetId] + * @param {Boolean} animate=!!this.animateTargetId */ show(animate=!!this.animateTargetId) { let me = this; @@ -658,11 +666,13 @@ class Base extends Panel { me.animateShow(); } else { if (!me.rendered) { - me.render(true); + me.render(true) } - me.fire('show'); + + me.fire('show') } - me.syncModalMask(); + + me.syncModalMask() } syncModalMask(id = this.id) { From 3cc28f49305918163d2aa7b9f4f60ab519d4a403 Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 20 Sep 2023 19:18:44 +0200 Subject: [PATCH 096/162] dialog.Base: replace Neo.currentWorker.promiseMessage() #4929 --- src/dialog/Base.mjs | 133 ++++++++++++++++---------------------------- 1 file changed, 49 insertions(+), 84 deletions(-) diff --git a/src/dialog/Base.mjs b/src/dialog/Base.mjs index 6f692cdce..0ce0f717b 100644 --- a/src/dialog/Base.mjs +++ b/src/dialog/Base.mjs @@ -292,121 +292,86 @@ class Base extends Panel { { id } = me, rects = await me.getDomRect([id, me.animateTargetId]); - await Neo.currentWorker.promiseMessage('main', { - action: 'updateDom', - appName, - deltas: [{ - id, - style: { - height : `${rects[0].height}px`, - left : `${rects[0].left }px`, - top : `${rects[0].top }px`, - width : `${rects[0].width }px`, - transform : 'none' - } - }] + await Neo.applyDeltas(appName, { + id, + style: { + height : `${rects[0].height}px`, + left : `${rects[0].left }px`, + top : `${rects[0].top }px`, + transform: 'none', + width : `${rects[0].width }px` + } }); await me.timeout(30); - await Neo.currentWorker.promiseMessage('main', { - action: 'updateDom', - appName, - deltas: [{ - id, - style: { - height: `${rects[1].height}px`, - left : `${rects[1].left }px`, - top : `${rects[1].top }px`, - width : `${rects[1].width }px` - }, - cls: { - add : ['animated-hiding-showing'] - } - }] + await Neo.applyDeltas(appName, { + id, + cls: { + add: ['animated-hiding-showing'] + }, + style: { + height: `${rects[1].height}px`, + left : `${rects[1].left }px`, + top : `${rects[1].top }px`, + width : `${rects[1].width }px` + } }); await me.timeout(250); me.closeOrHide(false); - await Neo.currentWorker.promiseMessage('main', { - action: 'updateDom', - appName, - deltas: [{ - id, - cls : { - remove : ['animated-hiding-showing'] - } - }, { - id, - action: 'removeNode' - }] - }) + await Neo.applyDeltas(appName, [ + {id, cls: {remove: ['animated-hiding-showing']}}, + {id, action: 'removeNode'} + ]) } /** * */ async animateShow() { - let me = this, - appName = me.appName, - { style } = me, - rect = await me.getDomRect(me.animateTargetId); + let me = this, + appName = me.appName, + { id, style } = me, + rect = await me.getDomRect(me.animateTargetId); await me.render(true); // Move to cover the animation target - await Neo.currentWorker.promiseMessage('main', { - action: 'updateDom', - appName, - deltas: [{ - id : me.id, - style : { - top : `${rect.top}px`, - left : `${rect.left}px`, - width : `${rect.width}px`, - height : `${rect.height}px` - } - }] + await Neo.applyDeltas(appName, { + id, + style : { + height: `${rect.height}px`, + left : `${rect.left }px`, + top : `${rect.top }px`, + width : `${rect.width }px` + } }); // Wait for the element to achieve its initial rectangle await me.timeout(50); // Expand to final state - await Neo.currentWorker.promiseMessage('main', { - action: 'updateDom', - appName, - deltas: [{ - id : me.id, - style : { - height : style?.height || '', - left : style?.left || '50%', - top : style?.top || '50%', - transform: style?.transform || 'translate(-50%, -50%)', - width : style?.width || '50%' - }, - cls: { - add : ['animated-hiding-showing'], - remove: [] - } - }] + await Neo.applyDeltas(appName, { + id, + cls: { + add: ['animated-hiding-showing'] + }, + style: { + height : style?.height || '', + left : style?.left || '50%', + top : style?.top || '50%', + transform: style?.transform || 'translate(-50%, -50%)', + width : style?.width || '50%' + } }); await me.timeout(200); // Remove the animation class - await Neo.currentWorker.promiseMessage('main', { - action: 'updateDom', - appName, - deltas: [{ - id : me.id, - cls : { - remove : ['animated-hiding-showing'] - } - }] - }); + await Neo.applyDeltas(appName, {id, cls: {remove: ['animated-hiding-showing']}}); me.show(false) } From f16839ad4085dc8778148292ab6c75789fd138f0 Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 20 Sep 2023 19:51:10 +0200 Subject: [PATCH 097/162] dialog.Base: cleanup --- src/dialog/Base.mjs | 50 ++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/dialog/Base.mjs b/src/dialog/Base.mjs index 0ce0f717b..8d9a04b6e 100644 --- a/src/dialog/Base.mjs +++ b/src/dialog/Base.mjs @@ -1,5 +1,5 @@ -import Panel from '../container/Panel.mjs'; import NeoArray from '../util/Array.mjs'; +import Panel from '../container/Panel.mjs'; import Toolbar from './header/Toolbar.mjs'; import VDomUtil from '../util/VDom.mjs'; @@ -204,7 +204,7 @@ class Base extends Panel { if (me.dragZoneConfig?.alwaysFireDragMove) { domListeners.push( {'drag:move': me.onDragMove, scope: me, delegate: '.neo-header-toolbar'} - ); + ) } me.domListeners = domListeners; @@ -383,7 +383,7 @@ class Base extends Panel { * @protected */ beforeSetCloseAction(value, oldValue) { - return this.beforeSetEnumValue(value, oldValue, 'closeAction'); + return this.beforeSetEnumValue(value, oldValue, 'closeAction') } /** @@ -404,11 +404,13 @@ class Base extends Panel { * @param {Boolean} animate=!!this.animateTargetId */ async closeOrHide(animate=!!this.animateTargetId) { - const { id } = this; + const + me = this, + { id } = me; - this[this.closeAction](animate); - await this.timeout(30); - this.syncModalMask(id) + me[me.closeAction](animate); + await me.timeout(30); + me.syncModalMask(id) } /** @@ -477,7 +479,7 @@ class Base extends Panel { * @returns {Object} vdom */ getProxyVdom() { - return VDomUtil.clone(this.vdom); + return VDomUtil.clone(this.vdom) } /** @@ -506,7 +508,7 @@ class Base extends Panel { data.component.iconCls = me.maximized ? me.maximizeCls : me.minimizeCls; - me.maximized = !me.maximized; + me.maximized = !me.maximized } /** @@ -552,8 +554,8 @@ class Base extends Panel { style.transitionProperty = initialTransitionProperty; - me.style = style; - }, 50); + me.style = style + }, 50) } me.style = style; @@ -562,8 +564,8 @@ class Base extends Panel { // we need a reset, otherwise we do not get a change event for the next onDragStart() call me.dragZone.boundaryContainerId = null; - me.isDragging = false; - }); + me.isDragging = false + }) } } @@ -572,7 +574,7 @@ class Base extends Panel { * @param data */ onDragMove(data) { - this.dragZone.dragMove(data); + this.dragZone.dragMove(data) } /** @@ -580,17 +582,12 @@ class Base extends Panel { */ onDragStart(data) { let me = this, - style = me.style || {}, - resizablePlugin; + style = me.style || {}; if (!me.maximized) { me.isDragging = true; - resizablePlugin = me.getPlugin({flag: 'resizable'}); - - if (resizablePlugin) { - resizablePlugin.removeAllNodes(); - } + me.getPlugin({flag: 'resizable'})?.removeAllNodes(); if (!me.dragZone) { me.dragZone = Neo.create({ @@ -608,16 +605,16 @@ class Base extends Panel { me.fire('dragZoneCreated', { dragZone: me.dragZone, id : me.id - }); + }) } else { - me.dragZone.boundaryContainerId = me.boundaryContainerId; + me.dragZone.boundaryContainerId = me.boundaryContainerId } me.dragZone.dragStart(data); style.opacity = 0.7; - me.style = style; + me.style = style } } @@ -640,7 +637,10 @@ class Base extends Panel { me.syncModalMask() } - syncModalMask(id = this.id) { + /** + * @param {String} id=this.id + */ + syncModalMask(id=this.id) { // This should sync the visibility and position of the modal mask element. Neo.main.DomAccess.syncModalMask({ id, modal : this.modal }); } From df25da9c3d779d472151594714235b2a830952cf Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 20 Sep 2023 20:07:35 +0200 Subject: [PATCH 098/162] main.DomAccess: cleanup --- src/dialog/Base.mjs | 2 +- src/main/DomAccess.mjs | 115 +++++++++++++++++++++-------------------- 2 files changed, 61 insertions(+), 56 deletions(-) diff --git a/src/dialog/Base.mjs b/src/dialog/Base.mjs index 8d9a04b6e..a8ce07ccb 100644 --- a/src/dialog/Base.mjs +++ b/src/dialog/Base.mjs @@ -642,7 +642,7 @@ class Base extends Panel { */ syncModalMask(id=this.id) { // This should sync the visibility and position of the modal mask element. - Neo.main.DomAccess.syncModalMask({ id, modal : this.modal }); + Neo.main.DomAccess.syncModalMask({ id, modal: this.modal }); } } diff --git a/src/main/DomAccess.mjs b/src/main/DomAccess.mjs index 1e0ef902b..4cdd4e660 100644 --- a/src/main/DomAccess.mjs +++ b/src/main/DomAccess.mjs @@ -1,22 +1,22 @@ import Base from '../core/Base.mjs'; import DeltaUpdates from './mixin/DeltaUpdates.mjs'; import Observable from '../core/Observable.mjs'; -import Rectangle from '../util/Rectangle.mjs'; +import Rectangle from '../util/Rectangle.mjs'; const lengthRE = /^\d+\w+$/, fontSizeProps = [ + 'font-family', + 'font-kerning', 'font-size', 'font-size-adjust', + 'font-stretch', 'font-style', 'font-weight', - 'font-family', - 'font-kerning', - 'font-stretch', + 'letter-spacing', 'line-height', - 'text-transform', 'text-decoration', - 'letter-spacing', + 'text-transform', 'word-break' ]; @@ -97,6 +97,20 @@ class DomAccess extends Base { ] } + /** + * @returns {HTMLElement} + */ + get modalMask() { + let me = this; + + if (!me._modalMask) { + me._modalMask = document.createElement('div'); + me._modalMask.className = 'neo-dialog-modal-mask'; + } + + return me._modalMask; + } + /** * @param {Object} config */ @@ -112,16 +126,16 @@ class DomAccess extends Base { node = document.getElementById('neo-delta-updates'); if (node) { - node.innerHTML = String(me.countDeltasPer250ms * 4); + node.innerHTML = String(me.countDeltasPer250ms * 4) } - me.countDeltasPer250ms = 0; + me.countDeltasPer250ms = 0 }, 250) } // Set up our aligning callback which is called when things change which may // mean that alignments need to be updated. - me.syncAligns = me.syncAligns.bind(me); + me.syncAligns = me.syncAligns.bind(me) } /** @@ -145,7 +159,7 @@ class DomAccess extends Base { // Realign when constraining element changes size if (constrainToElement) { - resizeObserver.observe(constrainToElement); + resizeObserver.observe(constrainToElement) } } @@ -155,7 +169,7 @@ class DomAccess extends Base { passive: true }); - me.hasDocumentScrollListener = true; + me.hasDocumentScrollListener = true } if (!me.documentMutationObserver) { @@ -180,12 +194,12 @@ class DomAccess extends Base { let script = document.createElement('script'); if (!data.hasOwnProperty('async')) { - data.async = true; + data.async = true } Object.assign(script, data); - document.head.appendChild(script); + document.head.appendChild(script) } /** @@ -628,18 +642,19 @@ class DomAccess extends Base { * Resets any DOM sizing configs to the last externally configured value. * * This is used during aligning to release any constraints applied by a previous alignment. + * @param {Object} align * @protected */ - async resetDimensions(align) { - const { style } = this.getElement(align.id); - - style.flex = align.configuredFlex; - style.width = align.configuredWidth; - style.height = align.configuredHeight; - style.minWidth = align.configuredMinWidth; - style.minHeight = align.configuredMinHeight; - style.maxWidth = align.configuredMaxWidth; - style.maxHeight = align.configuredMaxHeight; + resetDimensions(align) { + Object.assign(this.getElement(align.id).style, { + flex : align.configuredFlex, + width : align.configuredWidth, + height : align.configuredHeight, + minWidth : align.configuredMinWidth, + minHeight: align.configuredMinHeight, + maxWidth : align.configuredMaxWidth, + maxHeight: align.configuredMaxHeight + }) } /** @@ -656,7 +671,7 @@ class DomAccess extends Base { node[`scroll${Neo.capitalize(data.direction)}`] += data.value; } - return {id: data.id}; + return {id: data.id} } /** @@ -670,15 +685,13 @@ class DomAccess extends Base { scrollIntoView(data) { let node = this.getElement(data.id); - if (node) { - node.scrollIntoView({ - behavior: data.behavior || 'smooth', - block : data.block || 'start', - inline : data.inline || 'nearest' - }); - } + node?.scrollIntoView({ + behavior: data.behavior || 'smooth', + block : data.block || 'start', + inline : data.inline || 'nearest' + }); - return {id: data.id}; + return {id: data.id} } /** @@ -695,7 +708,7 @@ class DomAccess extends Base { node[`scroll${Neo.capitalize(data.direction)}`] = data.value; } - return {id: data.id}; + return {id: data.id} } /** @@ -715,12 +728,12 @@ class DomAccess extends Base { top = node.getBoundingClientRect().top; wrapperNode.scrollTo({ - top : top - tableTop - (data.hasOwnProperty('offset') ? data.offset : 34), - behavior: data.behavior || 'smooth' - }); + behavior: data.behavior || 'smooth', + top : top - tableTop - (data.hasOwnProperty('offset') ? data.offset : 34) + }) } - return {id: data.id}; + return {id: data.id} } /** @@ -740,7 +753,7 @@ class DomAccess extends Base { node.setSelectionRange(start, end); } - return {id: data.id}; + return {id: data.id} } /** @@ -768,14 +781,14 @@ class DomAccess extends Base { Object.entries(data.style).forEach(([key, value]) => { if (Neo.isString(value) && value.includes('!important')) { value = value.replace('!important', '').trim(); - node.style.setProperty(Neo.decamel(key), value, 'important'); + node.style.setProperty(Neo.decamel(key), value, 'important') } else { - node.style[Neo.decamel(key)] = value; + node.style[Neo.decamel(key)] = value } - }); + }) } - return {id: data.id}; + return {id: data.id} } /** @@ -803,13 +816,13 @@ class DomAccess extends Base { _alignResizeObserver.unobserve(align.offsetParent); _alignResizeObserver.unobserve(align.targetElement); if (constrainToElement) { - _alignResizeObserver.unobserve(constrainToElement); + _alignResizeObserver.unobserve(constrainToElement) } // Clear the last aligned class. align.subject.classList.remove(`neo-aligned-${align.result?.position}`); - _aligns.delete(align.id); + _aligns.delete(align.id) } }) } @@ -832,19 +845,11 @@ class DomAccess extends Base { this.syncModalMask({ id : topmostModal.id, modal : true }) } else { - this._modalMask?.remove(); + this._modalMask?.remove() } } } - get modalMask() { - if (!this._modalMask) { - this._modalMask = document.createElement('div'); - this._modalMask.className = 'neo-dialog-modal-mask'; - } - return this._modalMask; - } - /** * @param {Object} data * @param {String} [data.behavior='smooth'] // auto or smooth @@ -856,7 +861,7 @@ class DomAccess extends Base { behavior: data.behavior || 'smooth', left : data.left || 0, top : data.top || 0 - }); + }) } /** @@ -868,7 +873,7 @@ class DomAccess extends Base { index : data.parentIndex, outerHTML: data.html || data.outerHTML, parentId : data.parentId - }); + }) } } From 7779724dc246c6682871c1f00833ce2913747d39 Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 20 Sep 2023 20:18:52 +0200 Subject: [PATCH 099/162] v6.6.0 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index 0d10c4b77..c43a46f3f 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.10' + * @member {String} version='6.6.0' */ - version: '6.5.10' + version: '6.6.0' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index 0d10c4b77..c43a46f3f 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.5.10' + * @member {String} version='6.6.0' */ - version: '6.5.10' + version: '6.6.0' } /** diff --git a/package-lock.json b/package-lock.json index d3829d05c..419993f91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "neo.mjs", - "version": "6.5.8", + "version": "6.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "neo.mjs", - "version": "6.5.8", + "version": "6.6.0", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-free": "^6.4.2", diff --git a/package.json b/package.json index 3959cafd9..23b3247d9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.5.10", + "version": "6.6.0", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index 1d81bbb06..c567cdef8 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.5.10' + * @default '6.6.0' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.5.10' + version: '6.6.0' }; Object.assign(DefaultConfig, { From f72a2a13047fcc1907ceb98ad07e972a1b86736d Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 21 Sep 2023 16:17:45 +0200 Subject: [PATCH 100/162] core.Base: ordering static class fields --- src/core/Base.mjs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/core/Base.mjs b/src/core/Base.mjs index 9e24cfb2d..9cd115215 100644 --- a/src/core/Base.mjs +++ b/src/core/Base.mjs @@ -11,13 +11,6 @@ const configSymbol = Symbol.for('configSymbol'), * @class Neo.core.Base */ class Base { - /** - * Regex to grab the MethodName from an error - * which is a second generation function - * @member {RegExp} methodNameRegex - * @static - */ - static methodNameRegex = /\n.*\n\s+at\s+.*\.(\w+)\s+.*/ /** * You can define methods which should get delayed * @example @@ -32,6 +25,13 @@ class Base { * @static */ static delayable = {} + /** + * Regex to grab the MethodName from an error + * which is a second generation function + * @member {RegExp} methodNameRegex + * @static + */ + static methodNameRegex = /\n.*\n\s+at\s+.*\.(\w+)\s+.*/ /** * True automatically applies the core.Observable mixin * @member {Boolean} observable=false From 0753838875e006e1fd92614bbcd420dfe776e083 Mon Sep 17 00:00:00 2001 From: Nige White Date: Thu, 21 Sep 2023 16:00:48 +0200 Subject: [PATCH 101/162] Hide align subject on target not present in DOM --- src/main/DomAccess.mjs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/DomAccess.mjs b/src/main/DomAccess.mjs index 4cdd4e660..3ea8e86f8 100644 --- a/src/main/DomAccess.mjs +++ b/src/main/DomAccess.mjs @@ -801,12 +801,19 @@ class DomAccess extends Base { // Keep all registered aligns aligned on any detected change _aligns?.forEach(align => { + const targetPresent = document.contains(align.targetElement); + // Align subject and target still in the DOM - correct its alignment - if (document.contains(align.subject) && document.contains(align.targetElement)) { + if (document.contains(align.subject) && targetPresent) { me.align(align); } // Align subject or target no longer in the DOM - remove it. else { + // If target is no longer in the DOM, hide the subject component + if (!targetPresent) { + Neo.worker.App.setConfigs({ id : align.id, hidden : true }); + } + const { _alignResizeObserver } = me, { constrainToElement } = align; From 054c1eb1b3bfddd97352cd8991bd4e5dc7d7c09a Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 21 Sep 2023 19:03:49 +0200 Subject: [PATCH 102/162] main.DomAccess: minor cleanup --- src/main/DomAccess.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/DomAccess.mjs b/src/main/DomAccess.mjs index 3ea8e86f8..25f66abd2 100644 --- a/src/main/DomAccess.mjs +++ b/src/main/DomAccess.mjs @@ -811,7 +811,7 @@ class DomAccess extends Base { else { // If target is no longer in the DOM, hide the subject component if (!targetPresent) { - Neo.worker.App.setConfigs({ id : align.id, hidden : true }); + Neo.worker.App.setConfigs({ id: align.id, hidden: true }); } const From fc59a29887cf362d5074d5a53b460ed6fb787b1a Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 21 Sep 2023 19:04:31 +0200 Subject: [PATCH 103/162] v6.6.1 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index c43a46f3f..ecca94c27 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.6.0' + * @member {String} version='6.6.1' */ - version: '6.6.0' + version: '6.6.1' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index c43a46f3f..ecca94c27 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.6.0' + * @member {String} version='6.6.1' */ - version: '6.6.0' + version: '6.6.1' } /** diff --git a/package.json b/package.json index 23b3247d9..ac31cb693 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.6.0", + "version": "6.6.1", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index c567cdef8..4c3c37935 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.6.0' + * @default '6.6.1' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.6.0' + version: '6.6.1' }; Object.assign(DefaultConfig, { From 1abbe0967dbd54f21c5b37f3f02c188d8f5459bc Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 21 Sep 2023 20:02:07 +0200 Subject: [PATCH 104/162] form.field.FileUpload: doc comments for configs cleanup --- src/form/field/FileUpload.mjs | 83 ++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/src/form/field/FileUpload.mjs b/src/form/field/FileUpload.mjs index e7a1afb35..1c7074379 100644 --- a/src/form/field/FileUpload.mjs +++ b/src/form/field/FileUpload.mjs @@ -132,34 +132,37 @@ class FileUpload extends Base { ] }, - cls : ['neo-field-empty'], + cls: ['neo-field-empty'], /** * An Object containing a default set of headers to be passed to the server on every HTTP request. * @member {Object} headers */ - headers_ : {}, + headers_: {}, /** * An Object which allows the status text returned from the {@link #property-documentStatusUrl} to be * mapped to the corresponding next widget state. * @member {Object} documentStatusMap */ - documentStatusMap : { + documentStatusMap: { SCANNING : 'scanning', // The server doing its own secondary upload to the final storage location may return this. // We enter the same state as scanning. A spinner shows for the duration of this state - UPLOADING : 'scanning', + UPLOADING : 'scanning', - MALWARE_DETECTED : 'scan-failed', - UN_DOWNLOADABLE : 'not-downloadable', - AVAILABLE : 'not-downloadable', - DOWNLOADABLE : 'downloadable', - DELETED : 'deleted' + MALWARE_DETECTED: 'scan-failed', + UN_DOWNLOADABLE : 'not-downloadable', + AVAILABLE : 'not-downloadable', + DOWNLOADABLE : 'downloadable', + DELETED : 'deleted' }, - document_ : null, + /** + * @member {String|null} document_=null + */ + document_: null, /** * If this widget should reference an existing document, configure the widget with a documentId @@ -169,7 +172,7 @@ class FileUpload extends Base { * the id returned from the {@link #property-uploadUrl}. * @member {String|Number} documentId */ - documentId : null, + documentId: null, /** * The URL of the file upload service to which the selected file is sent. @@ -188,9 +191,9 @@ class FileUpload extends Base { * scan operation to see if the file was accepted, and whether it is to be subsequently downloadable. * * The document status request URL must be configured in {@link #member-documentStatusUrl} - * @member {String} uploadUrl + * @member {String|null} uploadUrl=null */ - uploadUrl : null, + uploadUrl: null, /** * The name of the JSON property in which the document id is returned in the upload response @@ -199,18 +202,18 @@ class FileUpload extends Base { * * Defaults fro `documentId` * - * @member {String} downloadUrl + * @member {String} documentIdParameter='documentId' */ - documentIdParameter : 'documentId', + documentIdParameter: 'documentId', /** * The URL from which the file may be downloaded after it has finished its scan. * * This must contain a substitution token named the same as the {@link #property-documentIdParameter} * which is used when creating a URL - * + * * for example: - * + * * ```json * { * downloadUrl : '/getDocument/${documentId}' @@ -220,18 +223,18 @@ class FileUpload extends Base { * The document id returned from the {@link #member-uploadUrl upload} is passed in the parameter named * by the {@link #member-documentIdParameter}. It defaults to `'documentId'`. * - * @member {String} downloadUrl + * @member {String|null} downloadUrl_=null */ - downloadUrl_ : null, + downloadUrl_: null, /** * The URL of the file status reporting service. * * This must contain a substitution token named the same as the {@link #property-documentIdParameter} * which is used when creating a URL - * + * * for example: - * + * * ```json * { * documentStatusUrl : '/getDocumentStatus/${documentId}' @@ -249,9 +252,9 @@ class FileUpload extends Base { * } * ``` * - * @member {String} documentStatusUrl + * @member {String|null} documentStatusUrl_=null */ - documentStatusUrl_ : null, + documentStatusUrl_: null, /** * The polling interval *in milliseconds* to wait between asking the server how the document scan @@ -259,18 +262,18 @@ class FileUpload extends Base { * * Defaults to 2000ms * - * @member {String} documentDeleteUrl + * @member {Number} statusScanInterval=2000 */ - statusScanInterval : 2000, + statusScanInterval: 2000, /** * The URL of the file deletion service. * * This must contain a substitution token named the same as the {@link #property-documentIdParameter} * which is used when creating a URL - * + * * for example: - * + * * ```json * { * documentDeleteUrl : '/deleteDocument/${documentId}' @@ -282,17 +285,17 @@ class FileUpload extends Base { * * If this service yields an HTTP 200 status, the deletion is taken to have been successful. * - * @member {String} documentDeleteUrl + * @member {String|null} documentDeleteUrl_=null */ - documentDeleteUrl_ : null, + documentDeleteUrl_: null, /** * The HTTP method to use when requesting a document deletion using the {@link #member-documentDeleteUrl}. - * + * * Defaults to `DELETE`. - * @member {String} documentDeleteMethod + * @member {String} documentDeleteMethod='DELETE' */ - documentDeleteMethod : 'DELETE', + documentDeleteMethod: 'DELETE', /** * @member {String} state_=null @@ -302,16 +305,16 @@ class FileUpload extends Base { /** * @member {Object} types=null */ - types_ : null, + types_: null, /** - * @member {String|Number} maxSize + * @member {String|Number|null} maxSize=null */ maxSize_: null, /** * The error text to show below the widget - * @member {String} error + * @member {String|null} error_=null */ error_ : null, @@ -362,7 +365,7 @@ class FileUpload extends Base { this.vdom.cn[4].html = this.chooseFile; } - + /** * @returns {Object} */ @@ -410,7 +413,7 @@ class FileUpload extends Base { if (files.length) { NeoArray.remove(cls, 'neo-field-empty'); me.cls = cls; - + const file = files.item(0), pointPos = file.name.lastIndexOf('.'), @@ -643,7 +646,7 @@ class FileUpload extends Base { // Success if (String(statusResponse.status).slice(0, 1) === '2') { - const + const serverJson = await statusResponse.json(), serverStatus = serverJson.status, // Map the server's states codes to our own status codes @@ -746,8 +749,8 @@ class FileUpload extends Base { /** * Creates a URL substituting the passed parameter names in at the places where the name * occurs within `{}` in the pattern. - * @param {String} urlPattern - * @param {Object} params + * @param {String} urlPattern + * @param {Object} params */ createUrl(urlPattern, params) { for (const paramName in params) { From 18af4a6ba49f624c3299b524c520e11ce284126e Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 21 Sep 2023 20:10:19 +0200 Subject: [PATCH 105/162] form.field.FileUpload: _vdom formatting (matching html indentation) --- src/form/field/FileUpload.mjs | 50 +++++++++++------------------------ 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/src/form/field/FileUpload.mjs b/src/form/field/FileUpload.mjs index 1c7074379..70db1a9a7 100644 --- a/src/form/field/FileUpload.mjs +++ b/src/form/field/FileUpload.mjs @@ -97,42 +97,24 @@ class FileUpload extends Base { */ baseCls: ['neo-file-upload-field'], /** - * @member {Object} _vdom + * @member {String[]} cls=['neo-field-empty'] */ - _vdom: { - cn : [ - { - tag : 'i', - cls : 'neo-file-upload-state-icon' - }, - { - cls : 'neo-file-upload-body', - cn : [{ - cls : 'neo-file-upload-filename' - }, { - cls : 'neo-file-upload-state' - }] - }, - { - cls : 'neo-file-upload-action-button', - tag : 'button' - }, - { - cls : 'neo-file-upload-input', - tag : 'input', - type : 'file' - }, - { - cls : 'neo-file-upload-label', - tag : 'label' - }, - { - cls : 'neo-file-upload-error-message' - } - ] - }, - cls: ['neo-field-empty'], + /** + * @member {Object} _vdom + */ + _vdom: + {cn: [ + {tag: 'i', cls: 'neo-file-upload-state-icon'}, + {cls: 'neo-file-upload-body', cn: [ + {cls: 'neo-file-upload-filename'}, + {cls: 'neo-file-upload-state'} + ]}, + {tag: 'button', cls: 'neo-file-upload-action-button'}, + {tag: 'input', cls: 'neo-file-upload-input', type: 'file'}, + {tag: 'label', cls: 'neo-file-upload-label'}, + {cls: 'neo-file-upload-error-message'} + ]}, /** * An Object containing a default set of headers to be passed to the server on every HTTP request. From 097a5dda734785c474aca0a1a847afd4bc9fbe8d Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 21 Sep 2023 20:11:01 +0200 Subject: [PATCH 106/162] component.Base: update() => removed the aync --- src/component/Base.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/component/Base.mjs b/src/component/Base.mjs index afe414c60..a4500cde3 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -2166,8 +2166,8 @@ class Base extends CoreBase { /** * */ - async update() { - await this.afterSetVdom(this.vdom, null) + update() { + this.afterSetVdom(this.vdom, null) } /** From eb1161ade3450cde457084ae6b0e3eff4ec5e230 Mon Sep 17 00:00:00 2001 From: Nige White Date: Fri, 22 Sep 2023 11:24:47 +0200 Subject: [PATCH 107/162] Make sure file name is truncated --- resources/scss/src/form/field/FileUpload.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/scss/src/form/field/FileUpload.scss b/resources/scss/src/form/field/FileUpload.scss index a04bf3bdc..bd24035b1 100644 --- a/resources/scss/src/form/field/FileUpload.scss +++ b/resources/scss/src/form/field/FileUpload.scss @@ -88,6 +88,7 @@ .neo-file-upload-filename { font-weight : bold; + align-self : stretch; } .neo-file-upload-filename, .neo-file-upload-state { From 3b62070350385a4c5d488af35915c3e529a6e38f Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 22 Sep 2023 11:30:39 +0200 Subject: [PATCH 108/162] vdom.Helper: createDeltas() => removeNode => edge case where the delta does not contain a parentId #4933 --- src/vdom/Helper.mjs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vdom/Helper.mjs b/src/vdom/Helper.mjs index 7764b73b2..266a22ce8 100644 --- a/src/vdom/Helper.mjs +++ b/src/vdom/Helper.mjs @@ -189,10 +189,13 @@ class Helper extends Base { } else { // console.log('top level removed node', oldVnode.id, oldVnode); + let removedNodeDetails = me.findVnode(oldVnodeRoot, oldVnode.id); + deltas.push({ - action: 'removeNode', - id : oldVnode.id - }); + action : 'removeNode', + id : oldVnode.id, + parentId: removedNodeDetails?.parentNode.id + }) } } else { if (newVnode && oldVnode && newVnode.id !== oldVnode.id) { From 1073ddac1c033851a2e1bff2e0555501c34304af Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 22 Sep 2023 11:37:20 +0200 Subject: [PATCH 109/162] v6.6.2 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index ecca94c27..432638511 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.6.1' + * @member {String} version='6.6.2' */ - version: '6.6.1' + version: '6.6.2' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index ecca94c27..432638511 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.6.1' + * @member {String} version='6.6.2' */ - version: '6.6.1' + version: '6.6.2' } /** diff --git a/package.json b/package.json index ac31cb693..7bd605ec1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.6.1", + "version": "6.6.2", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index 4c3c37935..a28cb05ad 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.6.1' + * @default '6.6.2' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.6.1' + version: '6.6.2' }; Object.assign(DefaultConfig, { From 7fac69d8572f264293823cc1cdfe6e7f8b151f12 Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 22 Sep 2023 12:08:26 +0200 Subject: [PATCH 110/162] vdom.Helper: createDeltas() => removeNode edge case => only search the parentId for vtype text #4935 --- src/vdom/Helper.mjs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/vdom/Helper.mjs b/src/vdom/Helper.mjs index 266a22ce8..ec5791433 100644 --- a/src/vdom/Helper.mjs +++ b/src/vdom/Helper.mjs @@ -189,13 +189,19 @@ class Helper extends Base { } else { // console.log('top level removed node', oldVnode.id, oldVnode); - let removedNodeDetails = me.findVnode(oldVnodeRoot, oldVnode.id); + delta = { + action: 'removeNode', + id : oldVnode.id + }; - deltas.push({ - action : 'removeNode', - id : oldVnode.id, - parentId: removedNodeDetails?.parentNode.id - }) + // We only need a parentId for vtype text + if (oldVnode.vtype === 'text') { + let removedNodeDetails = me.findVnode(oldVnodeRoot, oldVnode.id); + + delta.parentId = removedNodeDetails?.parentNode.id + } + + deltas.push(delta) } } else { if (newVnode && oldVnode && newVnode.id !== oldVnode.id) { From 6263ef9318c578d66c39e88596e6683d6de1255d Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 22 Sep 2023 13:14:40 +0200 Subject: [PATCH 111/162] list.Base / menu.List: add support for hidden list items #4936 --- src/list/Base.mjs | 4 ++++ src/menu/Model.mjs | 3 +++ 2 files changed, 7 insertions(+) diff --git a/src/list/Base.mjs b/src/list/Base.mjs index 3bd45e296..dd70a72a4 100644 --- a/src/list/Base.mjs +++ b/src/list/Base.mjs @@ -398,6 +398,10 @@ class Base extends Component { tabIndex: -1 }; + if (record.hidden) { + item.removeDom = true + } + if (me.itemRole) { item.role = me.itemRole } diff --git a/src/menu/Model.mjs b/src/menu/Model.mjs index 8787362fd..0a612e501 100644 --- a/src/menu/Model.mjs +++ b/src/menu/Model.mjs @@ -24,6 +24,9 @@ class Model extends BaseModel { }, { name: 'handler', type: 'Function' + }, { + name: 'hidden', + type: 'Boolean' }, { name: 'iconCls', type: 'String' From 50426b3ed5dac1612372987b136bc93a40442218 Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 22 Sep 2023 13:15:20 +0200 Subject: [PATCH 112/162] v6.6.3 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index 432638511..e1fbd97bc 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.6.2' + * @member {String} version='6.6.3' */ - version: '6.6.2' + version: '6.6.3' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index 432638511..e1fbd97bc 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.6.2' + * @member {String} version='6.6.3' */ - version: '6.6.2' + version: '6.6.3' } /** diff --git a/package.json b/package.json index 7bd605ec1..5be973420 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.6.2", + "version": "6.6.3", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index a28cb05ad..da6e335e9 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.6.2' + * @default '6.6.3' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.6.2' + version: '6.6.3' }; Object.assign(DefaultConfig, { From d35c58b0134ff02c6902d7a91c3310851ef921af Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 22 Sep 2023 13:23:25 +0200 Subject: [PATCH 113/162] DefaultConfig: resolved the merge conflict fail --- src/DefaultConfig.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index a48356968..da6e335e9 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -242,6 +242,7 @@ const DefaultConfig = { * @type String */ version: '6.6.3' +}; Object.assign(DefaultConfig, { /** From dd0cdf9a6d370df530e84f08b74165c2e8ab0314 Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 22 Sep 2023 13:24:13 +0200 Subject: [PATCH 114/162] v6.6.4 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index e1fbd97bc..819643856 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.6.3' + * @member {String} version='6.6.4' */ - version: '6.6.3' + version: '6.6.4' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index e1fbd97bc..819643856 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.6.3' + * @member {String} version='6.6.4' */ - version: '6.6.3' + version: '6.6.4' } /** diff --git a/package.json b/package.json index 5be973420..d675d8b94 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.6.3", + "version": "6.6.4", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index da6e335e9..f9d0a890a 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.6.3' + * @default '6.6.4' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.6.3' + version: '6.6.4' }; Object.assign(DefaultConfig, { From 30be5f24b30a3cdf2a4a83741a9171341226d21c Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 22 Sep 2023 16:50:55 +0200 Subject: [PATCH 115/162] util.VDom: syncVdomIds() => only update vdom ids in case there is no own id present #4939 --- src/util/VDom.mjs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/util/VDom.mjs b/src/util/VDom.mjs index 35635a680..6677aa7fd 100644 --- a/src/util/VDom.mjs +++ b/src/util/VDom.mjs @@ -341,7 +341,8 @@ class VDom extends Base { } /** - * Neo.vdom.Helper will create ids for each vnode, so we need to sync them into the vdom + * Neo.vdom.Helper will create ids for each vnode which does not already have one, + * so we need to sync them into the vdom. * @param {Neo.vdom.VNode} vnode * @param {Object} vdom */ @@ -350,7 +351,9 @@ class VDom extends Base { let childNodes = vdom.cn, cn, i, len; - if (vnode.id && vnode.id !== vdom.id) { + // we only want to change vdom ids in case there is not already an own id + // (think of adding & removing nodes in parallel) + if (!vdom.id && vnode.id) { vdom.id = vnode.id; } From 9480fd3fd789bfa06a67665ebe56ea42c9c52245 Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 22 Sep 2023 16:53:16 +0200 Subject: [PATCH 116/162] v6.6.5 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index 819643856..fcd975176 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.6.4' + * @member {String} version='6.6.5' */ - version: '6.6.4' + version: '6.6.5' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index 819643856..fcd975176 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.6.4' + * @member {String} version='6.6.5' */ - version: '6.6.4' + version: '6.6.5' } /** diff --git a/package.json b/package.json index d675d8b94..1daa3c6fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.6.4", + "version": "6.6.5", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index f9d0a890a..37a22e67a 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.6.4' + * @default '6.6.5' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.6.4' + version: '6.6.5' }; Object.assign(DefaultConfig, { From 27b293a51212a5890ffceff1b09fc40ffb4fadc3 Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 22 Sep 2023 17:30:06 +0200 Subject: [PATCH 117/162] util.VDom: forceSyncVdomIds() #4941 --- src/component/Base.mjs | 5 +- src/component/DateSelector.mjs | 9 ++- src/util/VDom.mjs | 101 ++++++++++++++++++--------------- 3 files changed, 61 insertions(+), 54 deletions(-) diff --git a/src/component/Base.mjs b/src/component/Base.mjs index a4500cde3..2e2da069d 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -2050,9 +2050,10 @@ class Base extends CoreBase { * Placeholder method for util.VDom.syncVdomIds to allow overriding (disabling) it * @param {Neo.vdom.VNode} [vnode=this.vnode] * @param {Object} [vdom=this.vdom] + * @param {Boolean} force=false */ - syncVdomIds(vnode=this.vnode, vdom=this.vdom) { - VDomUtil.syncVdomIds(vnode, vdom) + syncVdomIds(vnode=this.vnode, vdom=this.vdom, force=false) { + VDomUtil.syncVdomIds(vnode, vdom, force) } /** diff --git a/src/component/DateSelector.mjs b/src/component/DateSelector.mjs index 4e05648ab..c13b8c60d 100644 --- a/src/component/DateSelector.mjs +++ b/src/component/DateSelector.mjs @@ -828,9 +828,8 @@ class DateSelector extends Component { me.getCenterContentEl().cn = []; me.createDayViewContent(true); - if (syncIds) { - me.syncVdomIds() - } + // using force => we do want to keep the same ids + syncIds && me.syncVdomIds(me.vnode, me.vdom, true); me.triggerVdomUpdate(silent) } @@ -846,9 +845,9 @@ class DateSelector extends Component { me.isUpdating = true; - me.promiseUpdate(me.vdom).then(() => { + me.promiseUpdate().then(() => { me.isUpdating = false; - }); + }) } } diff --git a/src/util/VDom.mjs b/src/util/VDom.mjs index 6677aa7fd..44e74b442 100644 --- a/src/util/VDom.mjs +++ b/src/util/VDom.mjs @@ -15,23 +15,23 @@ class VDom extends Base { /** * @param {Object} vdom - * @param {Boolean} [removeIds=true] + * @param {Boolean} removeIds=true * @returns {Object} cloned vdom */ static clone(vdom, removeIds=true) { const clone = Neo.clone(vdom, true); if (removeIds) { - delete clone.id; + delete clone.id } if (clone.cn) { clone.cn.forEach((item, index) => { - clone.cn[index] = VDom.clone(item, removeIds); - }); + clone.cn[index] = VDom.clone(item, removeIds) + }) } - return clone; + return clone } /** @@ -64,39 +64,39 @@ class VDom extends Base { case 'cls': if (typeof value === 'string' && Neo.isArray(vdom[key])) { if (vdom[key].includes(value)) { - matchArray.push(true); + matchArray.push(true) } } else if (typeof value === 'string' && typeof vdom[key] === 'string') { if (vdom[key] === value) { - matchArray.push(true); + matchArray.push(true) } } else if (Neo.isArray(value) && Neo.isArray(vdom[key])) { // todo: either search the vdom array for all keys or compare if the arrays are equal. - throw new Error('findVdomChild: cls matching not supported for target & source types of Arrays'); + throw new Error('findVdomChild: cls matching not supported for target & source types of Arrays') } break; case 'style': if (typeof value === 'string' && typeof vdom[key] === 'string') { if (vdom[key] === value) { - matchArray.push(true); + matchArray.push(true) } } else if (Neo.isObject(value) && Neo.isObject(vdom[key])) { Object.entries(value).forEach(([styleKey, styleValue]) => { if (!(vdom[key].hasOwnProperty(styleKey) && vdom[key][styleKey] === styleValue)) { - styleMatch = false; + styleMatch = false } }); if (styleMatch) { - matchArray.push(true); + matchArray.push(true) } } else { - throw new Error('findVdomChild: style matching not supported for mixed target & source types (Object VS String)'); + throw new Error('findVdomChild: style matching not supported for mixed target & source types (Object VS String)') } break; default: if (vdom[key] === value) { - matchArray.push(true); + matchArray.push(true) } break; } @@ -118,13 +118,13 @@ class VDom extends Base { parentNode: subChild.parentNode, vdom : subChild.vdom }; - break; + break } } } } - return child; + return child } /** @@ -135,7 +135,7 @@ class VDom extends Base { */ static getByFlag(vdom, flag) { let node = VDom.findVdomChild(vdom, {flag: flag}); - return node?.vdom; + return node?.vdom } /** @@ -149,13 +149,13 @@ class VDom extends Base { childNodes.forEach(childNode => { if (childNode.id) { - childIds.push(childNode.id); + childIds.push(childNode.id) } - childIds = VDom.getChildIds(childNode, childIds); + childIds = VDom.getChildIds(childNode, childIds) }); - return childIds; + return childIds } /** @@ -169,12 +169,12 @@ class VDom extends Base { if (vdom.cn) { vdom.cn.forEach(row => { if (row.cn?.[index]) { - columnNodes.push(row.cn[index]); + columnNodes.push(row.cn[index]) } - }); + }) } - return columnNodes; + return columnNodes } /** @@ -183,7 +183,7 @@ class VDom extends Base { * @returns {Array} */ static getColumnNodesIds(vdom, index) { - return VDom.getColumnNodes(vdom, index).map(e => e.id); + return VDom.getColumnNodes(vdom, index).map(e => e.id) } /** @@ -197,19 +197,19 @@ class VDom extends Base { matchArray = []; if (vdom.flag === flag) { - matchArray.push(vdom); + matchArray.push(vdom) } } (vdom?.cn || []).forEach(childNode => { if (childNode.flag === flag) { - matchArray.push(childNode); + matchArray.push(childNode) } - matchArray = VDom.getFlags(childNode, flag, matchArray); + matchArray = VDom.getFlags(childNode, flag, matchArray) }); - return matchArray; + return matchArray } /** @@ -224,7 +224,7 @@ class VDom extends Base { len = vdom.cn?.length || 0; if (vdom.id === id) { - return []; + return [] } for (; i < len; i++) { @@ -232,15 +232,15 @@ class VDom extends Base { if (parents) { parents.push(vdom.cn[i]); - break; + break } } if (topLevel && parents) { - parents.push(vdom); + parents.push(vdom) } - return parents; + return parents } /** @@ -251,7 +251,7 @@ class VDom extends Base { * @returns {Boolean} */ static insertAfterNode(vdom, nodeToInsert, targetNodeId) { - return VDom.insertNode(vdom, nodeToInsert, targetNodeId, false); + return VDom.insertNode(vdom, nodeToInsert, targetNodeId, false) } /** @@ -262,7 +262,7 @@ class VDom extends Base { * @returns {Boolean} */ static insertBeforeNode(vdom, nodeToInsert, targetNodeId) { - return VDom.insertNode(vdom, nodeToInsert, targetNodeId, true); + return VDom.insertNode(vdom, nodeToInsert, targetNodeId, true) } /** @@ -275,7 +275,7 @@ class VDom extends Base { */ static insertNode(vdom, nodeToInsert, targetNodeId, insertBefore) { if (Neo.isObject(targetNodeId)) { - targetNodeId = targetNodeId.id; + targetNodeId = targetNodeId.id } let targetNode = VDom.findVdomChild(vdom, {id: targetNodeId}), @@ -284,10 +284,10 @@ class VDom extends Base { if (targetNode) { index = insertBefore ? targetNode.index : targetNode.index + 1; targetNode.parentNode.cn.splice(index, 0, nodeToInsert); - return true; + return true } - return false; + return false } /** @@ -301,10 +301,10 @@ class VDom extends Base { if (child) { child.parentNode.cn.splice(child.index, 1); - return true; + return true } - return false; + return false } /** @@ -321,7 +321,7 @@ class VDom extends Base { childNode; if (vdom.id === id) { - throw new Error('replaceVdomChild: target id matches the root vnode id: ' + id); + throw new Error('replaceVdomChild: target id matches the root vnode id: ' + id) } for (; i < len; i++) { @@ -329,11 +329,11 @@ class VDom extends Base { if (childNode.id === id) { cn[i] = newChildNode; - return true; + return true } if (VDom.replaceVdomChild(childNode, id, newChildNode)) { - return true; + return true } } @@ -345,16 +345,23 @@ class VDom extends Base { * so we need to sync them into the vdom. * @param {Neo.vdom.VNode} vnode * @param {Object} vdom + * @param {Boolean} force=false The force param will enforce overwriting different ids */ - static syncVdomIds(vnode, vdom) { + static syncVdomIds(vnode, vdom, force=false) { if (vnode && vdom) { let childNodes = vdom.cn, cn, i, len; - // we only want to change vdom ids in case there is not already an own id - // (think of adding & removing nodes in parallel) - if (!vdom.id && vnode.id) { - vdom.id = vnode.id; + if (force) { + if (vnode.id && vdom.id !== vnode.id) { + vdom.id = vnode.id + } + } else { + // we only want to change vdom ids in case there is not already an own id + // (think of adding & removing nodes in parallel) + if (!vdom.id && vnode.id) { + vdom.id = vnode.id + } } if (childNodes) { @@ -364,7 +371,7 @@ class VDom extends Base { for (; i < len; i++) { if (vnode.childNodes) { - VDom.syncVdomIds(vnode.childNodes[i], cn[i]) + VDom.syncVdomIds(vnode.childNodes[i], cn[i], force) } } } From f4c470d0e08f378e02ce40f327dd5606438a0162 Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 22 Sep 2023 17:36:12 +0200 Subject: [PATCH 118/162] component.Base: syncVnodeTree() => doc comment update --- src/component/Base.mjs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/component/Base.mjs b/src/component/Base.mjs index 2e2da069d..752191a7e 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -2057,7 +2057,10 @@ class Base extends CoreBase { } /** - * Placeholder method for util.VDom.syncVdomIds to allow overriding (disabling) it + * In case a component receives a new vnode, we want to do: + * - sync the vdom ids + * - setting rendered to true for child components + * - updating the parent component to ensure that the vnode tree stays persistent * @param {Neo.vdom.VNode} [vnode=this.vnode] */ syncVnodeTree(vnode=this.vnode) { From 19b06275e9f78b113cf78615b0f43e4d8cbc8287 Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 22 Sep 2023 17:51:22 +0200 Subject: [PATCH 119/162] component.DateSelector: cleanup --- src/component/DateSelector.mjs | 133 ++++++++++++++++----------------- 1 file changed, 65 insertions(+), 68 deletions(-) diff --git a/src/component/DateSelector.mjs b/src/component/DateSelector.mjs index c13b8c60d..c43cdd87f 100644 --- a/src/component/DateSelector.mjs +++ b/src/component/DateSelector.mjs @@ -156,7 +156,7 @@ class DateSelector extends Component { me.addDomListeners([ {click: me.onComponentClick, scope: me}, {wheel: me.onComponentWheel, scope: me} - ]); + ]) } /** @@ -176,12 +176,12 @@ class DateSelector extends Component { if (monthIncrement !== 0) { // gets used when month & year changed as well method = 'changeMonth'; - methodParams = [monthIncrement, yearIncrement]; + methodParams = [monthIncrement, yearIncrement] } else if (yearIncrement !== 0) { method = 'changeYear'; - methodParams = [yearIncrement]; + methodParams = [yearIncrement] } else if (dayIncrement !== 0) { - me.selectionModel.select(me.id + '__' + DateUtil.convertToyyyymmdd(value)); + me.selectionModel.select(me.id + '__' + DateUtil.convertToyyyymmdd(value)) } if (method) { @@ -189,10 +189,10 @@ class DateSelector extends Component { Neo.main.DomAccess.focus({ id: me.id }).then(data => { - me[method](...methodParams); - }); + me[method](...methodParams) + }) } else { - me[method](...methodParams); + me[method](...methodParams) } } } else if (value) { @@ -209,7 +209,7 @@ class DateSelector extends Component { * @protected */ afterSetDayNameFormat(value, oldValue) { - this.updateHeaderDays(value, oldValue); + this.updateHeaderDays(value, oldValue) } /** @@ -223,10 +223,10 @@ class DateSelector extends Component { let me = this; if (me.cachedUpdate && me.cachedUpdate !== new Date(`${me.value}T00:00:00.000Z`)) { - me.afterSetValue(me.value, DateUtil.convertToyyyymmdd(me.cachedUpdate)); + me.afterSetValue(me.value, DateUtil.convertToyyyymmdd(me.cachedUpdate)) } - me.cachedUpdate = null; + me.cachedUpdate = null } } @@ -238,14 +238,14 @@ class DateSelector extends Component { */ afterSetLocale(value, oldValue) { if (oldValue !== undefined) { - let me = this, - dt = new Intl.DateTimeFormat(me.locale, {month: 'short'}); + let me = this, + dt = new Intl.DateTimeFormat(me.locale, {month: 'short'}); me.updateHeaderDays(me.dayNameFormat, '', true); me.getHeaderMonthEl().html = dt.format(me.currentDate); - me.update(); + me.update() } } @@ -279,8 +279,8 @@ class DateSelector extends Component { let me = this, cls = me.cls; - NeoArray[value ? 'remove' : 'add'](cls, 'neo-hide-inner-borders'); - me.cls = cls; + NeoArray.toggle(cls, 'neo-hide-inner-borders', !value); + me.cls = cls } /** @@ -313,9 +313,9 @@ class DateSelector extends Component { if (item.cls.includes('neo-weekend')) { if (value) { - delete item.removeDom; + delete item.removeDom } else { - item.removeDom = true; + item.removeDom = true } } } @@ -366,7 +366,7 @@ class DateSelector extends Component { * @protected */ afterSetWeekStartDay(value, oldValue) { - oldValue !== undefined && this.recreateDayViewContent(false, false); + oldValue !== undefined && this.recreateDayViewContent(false, false) } /** @@ -376,7 +376,7 @@ class DateSelector extends Component { * @protected */ beforeSetDayNameFormat(value, oldValue) { - return this.beforeSetEnumValue(value, oldValue, 'dayNameFormat', DateUtil.prototype.dayNameFormats); + return this.beforeSetEnumValue(value, oldValue, 'dayNameFormat', DateUtil.prototype.dayNameFormats) } /** @@ -388,7 +388,7 @@ class DateSelector extends Component { beforeSetSelectionModel(value, oldValue) { oldValue && oldValue.destroy(); - return ClassSystemUtil.beforeSetInstance(value, DateSelectorModel); + return ClassSystemUtil.beforeSetInstance(value, DateSelectorModel) } /** @@ -398,7 +398,7 @@ class DateSelector extends Component { * @protected */ beforeSetWeekStartDay(value, oldValue) { - return this.beforeSetEnumValue(value, oldValue, 'weekStartDay', DateUtil.prototype.weekStartDays); + return this.beforeSetEnumValue(value, oldValue, 'weekStartDay', DateUtil.prototype.weekStartDays) } /** @@ -407,7 +407,7 @@ class DateSelector extends Component { * @protected */ cacheUpdate(date=this.currentDate) { - this.cachedUpdate = date; + this.cachedUpdate = date } /** @@ -420,7 +420,7 @@ class DateSelector extends Component { headerMonthOpts, vdom, x; if (!me.useAnimations) { - me.recreateContent(increment, yearIncrement); + me.recreateContent(increment, yearIncrement) } else { if (!me.isUpdating) { me.isUpdating = true; @@ -440,7 +440,7 @@ class DateSelector extends Component { headerMonthOpts = me.updateHeaderMonth(increment, yearIncrement, true, data[1]); if (yearIncrement !== 0) { - me.updateHeaderYear(increment, true); + me.updateHeaderYear(increment, true) } me.createDayViewContent(true, vdom.cn[2].cn[0].cn[0]); @@ -455,12 +455,12 @@ class DateSelector extends Component { setTimeout(() => { me.changeMonthWrapperCallback(slideDirection); me.updateHeaderMonthWrapperCallback(headerMonthOpts); - me.triggerVdomUpdate(); + me.triggerVdomUpdate() }, 300); }); }); } else { - me.cacheUpdate(); + me.cacheUpdate() } } } @@ -474,12 +474,11 @@ class DateSelector extends Component { */ changeMonthTransitionCallback(opts) { let me = this, - vdom = me.vdom, {data, slideDirection} = opts, x; x = slideDirection === 'right' ? -data.width : 0; - vdom.cn[1].cn[0].style.transform = `translateX(${x}px)`; + me.vdom.cn[1].cn[0].style.transform = `translateX(${x}px)` } /** @@ -488,10 +487,9 @@ class DateSelector extends Component { * @protected */ changeMonthWrapperCallback(slideDirection) { - let me = this, - vdom = me.vdom; + let vdom = this.vdom; - vdom.cn[1] = vdom.cn[1].cn[0].cn[slideDirection === 'right' ? 1 : 0]; + vdom.cn[1] = vdom.cn[1].cn[0].cn[slideDirection === 'right' ? 1 : 0] } /** @@ -502,7 +500,7 @@ class DateSelector extends Component { scrollFromTop, style, vdom, y; if (!me.useAnimations) { - me.recreateContent(0, increment); + me.recreateContent(0, increment) } else { if (!me.isUpdating) { me.isUpdating = true; @@ -540,12 +538,12 @@ class DateSelector extends Component { setTimeout(() => { vdom.cn[1] = vdom.cn[1].cn[0].cn[scrollFromTop ? 1 : 0]; - me.triggerVdomUpdate(); - }, 300); + me.triggerVdomUpdate() + }, 300) }); }); } else { - me.cacheUpdate(); + me.cacheUpdate() } } } @@ -572,15 +570,15 @@ class DateSelector extends Component { day = date.getDay(); if (!me.showWeekends && (day === 0 || day === 6)) { - config.removeDom = true; + config.removeDom = true } row.cn.push(config); - date.setDate(date.getDate() + 1); + date.setDate(date.getDate() + 1) } - return row; + return row } /** @@ -636,7 +634,7 @@ class DateSelector extends Component { if (dateDay === 0 || dateDay === 6) { if (!me.showWeekends) { - config.removeDom = true; + config.removeDom = true } config.cls.push('neo-weekend'); @@ -685,37 +683,37 @@ class DateSelector extends Component { day = day.toString(); if (day.length < 2) { - day = '0' + day; + day = '0' + day } month = month.toString(); if (month.length < 2) { - month = '0' + month; + month = '0' + month } - return this.id + '__' + year + '-' + month + '-' + day; + return this.id + '__' + year + '-' + month + '-' + day } /** * @returns {Object} */ getCenterContentEl() { - return this.vdom.cn[1]; + return this.vdom.cn[1] } /** * @returns {Object} */ getHeaderMonthEl() { - return this.vdom.cn[0].cn[1].cn[0]; + return this.vdom.cn[0].cn[1].cn[0] } /** * @returns {Object} */ getHeaderYearEl() { - return this.vdom.cn[0].cn[1].cn[1]; + return this.vdom.cn[0].cn[1].cn[1] } /** @@ -732,7 +730,7 @@ class DateSelector extends Component { // We want to always trigger a change event. // Reason: A form.field.Date can have a null value, and we want to select the current date. me._value = date; - me.afterSetValue(date, null); + me.afterSetValue(date, null) } /** @@ -743,14 +741,14 @@ class DateSelector extends Component { cls = data.path[0].cls, date, monthIncrement; - if (cls.includes('neo-cell')) {me.onCellClick(data);} - else if (cls.includes('neo-next-button')) {monthIncrement = 1;} - else if (cls.includes('neo-prev-button')) {monthIncrement = -1;} + if (cls.includes('neo-cell')) {me.onCellClick(data)} + else if (cls.includes('neo-next-button')) {monthIncrement = 1} + else if (cls.includes('neo-prev-button')) {monthIncrement = -1} if (monthIncrement) { date = me.currentDate; // cloned date.setMonth(date.getMonth() + monthIncrement); - me.value = DateUtil.convertToyyyymmdd(date); + me.value = DateUtil.convertToyyyymmdd(date) } } @@ -765,21 +763,21 @@ class DateSelector extends Component { date, monthIncrement, yearIncrement; if (Math.abs(deltaY) >= Math.abs(deltaX)) { - if (deltaY >= wheelDelta) {yearIncrement = 1;} - else if (deltaY <= -wheelDelta) {yearIncrement = -1;} + if (deltaY >= wheelDelta) {yearIncrement = 1} + else if (deltaY <= -wheelDelta) {yearIncrement = -1} } else { - if (deltaX >= wheelDelta) {monthIncrement = 1;} - else if (deltaX <= -wheelDelta) {monthIncrement = -1;} + if (deltaX >= wheelDelta) {monthIncrement = 1} + else if (deltaX <= -wheelDelta) {monthIncrement = -1} } if (monthIncrement) { date = me.currentDate; // cloned date.setMonth(date.getMonth() + monthIncrement); - me.value = DateUtil.convertToyyyymmdd(date); + me.value = DateUtil.convertToyyyymmdd(date) } else if (yearIncrement) { date = me.currentDate; // cloned date.setFullYear(date.getFullYear() + yearIncrement); - me.value = DateUtil.convertToyyyymmdd(date); + me.value = DateUtil.convertToyyyymmdd(date) } } @@ -846,7 +844,7 @@ class DateSelector extends Component { me.isUpdating = true; me.promiseUpdate().then(() => { - me.isUpdating = false; + me.isUpdating = false }) } } @@ -864,7 +862,6 @@ class DateSelector extends Component { if (oldValue !== undefined) { let centerEl = me.getCenterContentEl().cn[0], date = me.currentDate, // cloned - vdom = me.vdom, i = 0, day, node; @@ -878,15 +875,15 @@ class DateSelector extends Component { day = date.getDay(); if (!me.showWeekends && (day === 0 || day === 6)) { - node.removeDom = true; + node.removeDom = true } else { - delete node.removeDom; + delete node.removeDom } - date.setDate(date.getDate() + 1); + date.setDate(date.getDate() + 1) } - me[silent ? '_vdom' : 'vdom'] = vdom; + !silent && me.update() } } @@ -908,8 +905,8 @@ class DateSelector extends Component { if (!me.mounted || !me.useAnimations) { monthEl.html = currentMonth; - !silent && me.update() - return null; + !silent && me.update(); + return null } else { y = slideDirection === 'top' ? 0 : -monthElDomRect.height; @@ -940,14 +937,14 @@ class DateSelector extends Component { headerCenterEl.cn[0].cn[0].cn[slideDirection === 'top' ? 'unshift' : 'push'](headerCenterEl.cn[1]); headerCenterEl.cn.splice(1, 1); - me[silent ? '_vdom' : 'vdom'] = vdom; + !silent && me.update(); return { data: monthElDomRect, headerCenterEl, increment, yearIncrement - }; + } } } @@ -966,7 +963,7 @@ class DateSelector extends Component { y; y = slideDirection === 'top' ? -data.height : 0; - headerCenterEl.cn[0].cn[0].style.transform = `translateY(${y}px)`; + headerCenterEl.cn[0].cn[0].style.transform = `translateY(${y}px)` } /** @@ -981,7 +978,7 @@ class DateSelector extends Component { let {headerCenterEl, increment, yearIncrement} = opts, slideDirection = yearIncrement > 0 ? 'bottom' : yearIncrement < 0 ? 'top' : increment < 0 ? 'top' : 'bottom'; - headerCenterEl.cn[0] = headerCenterEl.cn[0].cn[0].cn[slideDirection === 'top' ? 1 : 0]; + headerCenterEl.cn[0] = headerCenterEl.cn[0].cn[0].cn[slideDirection === 'top' ? 1 : 0] } /** From ac4b92ed51106c929f74087ae7f433deb7607db2 Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 22 Sep 2023 17:52:50 +0200 Subject: [PATCH 120/162] v6.7.0 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index fcd975176..19bbabb30 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.6.5' + * @member {String} version='6.7.0' */ - version: '6.6.5' + version: '6.7.0' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index fcd975176..19bbabb30 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.6.5' + * @member {String} version='6.7.0' */ - version: '6.6.5' + version: '6.7.0' } /** diff --git a/package.json b/package.json index 1daa3c6fd..ae1662440 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.6.5", + "version": "6.7.0", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index 37a22e67a..e0e3d9a5d 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.6.5' + * @default '6.7.0' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.6.5' + version: '6.7.0' }; Object.assign(DefaultConfig, { From 7a95798033d3a6f917c643a4fe7e14eaeb6cdf5c Mon Sep 17 00:00:00 2001 From: tobiu Date: Fri, 22 Sep 2023 18:04:40 +0200 Subject: [PATCH 121/162] dependencies update --- package-lock.json | 63 +++++++++++++++++++++++++---------------------- package.json | 4 +-- 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/package-lock.json b/package-lock.json index 419993f91..9aac04044 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "neo.mjs", - "version": "6.6.0", + "version": "6.7.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "neo.mjs", - "version": "6.6.0", + "version": "6.7.0", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-free": "^6.4.2", "@material/mwc-button": "^0.27.0", "@material/mwc-textfield": "^0.27.0", - "autoprefixer": "^10.4.15", + "autoprefixer": "^10.4.16", "chalk": "^5.3.0", "clean-webpack-plugin": "^4.0.0", "commander": "^11.0.0", @@ -24,7 +24,7 @@ "neo-jsdoc": "1.0.1", "neo-jsdoc-x": "1.0.5", "postcss": "^8.4.30", - "sass": "^1.67.0", + "sass": "^1.68.0", "showdown": "^2.1.0", "webpack": "^5.88.2", "webpack-cli": "^5.1.4", @@ -1460,9 +1460,9 @@ "license": "ISC" }, "node_modules/autoprefixer": { - "version": "10.4.15", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.15.tgz", - "integrity": "sha512-KCuPB8ZCIqFdA4HwKXsvz7j6gvSDNhDP7WnUjBleRkKjPdvCmHFuQ77ocavI8FT6NdvlBnE2UFr2H4Mycn8Vew==", + "version": "10.4.16", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", + "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", "funding": [ { "type": "opencollective", @@ -1479,8 +1479,8 @@ ], "dependencies": { "browserslist": "^4.21.10", - "caniuse-lite": "^1.0.30001520", - "fraction.js": "^4.2.0", + "caniuse-lite": "^1.0.30001538", + "fraction.js": "^4.3.6", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -1848,9 +1848,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001520", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001520.tgz", - "integrity": "sha512-tahF5O9EiiTzwTUqAeFjIZbn4Dnqxzz7ktrgGlMYNLH43Ul26IgTMH/zvL3DG0lZxBYnlT04axvInszUsZULdA==", + "version": "1.0.30001538", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz", + "integrity": "sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==", "funding": [ { "type": "opencollective", @@ -3169,14 +3169,15 @@ } }, "node_modules/fraction.js": { - "version": "4.2.0", - "license": "MIT", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz", + "integrity": "sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==", "engines": { "node": "*" }, "funding": { "type": "patreon", - "url": "https://www.patreon.com/infusion" + "url": "https://github.com/sponsors/rawify" } }, "node_modules/fresh": { @@ -6139,9 +6140,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.67.0.tgz", - "integrity": "sha512-SVrO9ZeX/QQyEGtuZYCVxoeAL5vGlYjJ9p4i4HFuekWl8y/LtJ7tJc10Z+ck1c8xOuoBm2MYzcLfTAffD0pl/A==", + "version": "1.68.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.68.0.tgz", + "integrity": "sha512-Lmj9lM/fef0nQswm1J2HJcEsBUba4wgNx2fea6yJHODREoMFnwRpZydBnX/RjyXw2REIwdkbqE4hrTo4qfDBUA==", "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -8744,13 +8745,13 @@ "dev": true }, "autoprefixer": { - "version": "10.4.15", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.15.tgz", - "integrity": "sha512-KCuPB8ZCIqFdA4HwKXsvz7j6gvSDNhDP7WnUjBleRkKjPdvCmHFuQ77ocavI8FT6NdvlBnE2UFr2H4Mycn8Vew==", + "version": "10.4.16", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", + "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", "requires": { "browserslist": "^4.21.10", - "caniuse-lite": "^1.0.30001520", - "fraction.js": "^4.2.0", + "caniuse-lite": "^1.0.30001538", + "fraction.js": "^4.3.6", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -8979,9 +8980,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001520", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001520.tgz", - "integrity": "sha512-tahF5O9EiiTzwTUqAeFjIZbn4Dnqxzz7ktrgGlMYNLH43Ul26IgTMH/zvL3DG0lZxBYnlT04axvInszUsZULdA==" + "version": "1.0.30001538", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz", + "integrity": "sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==" }, "capture-stack-trace": { "version": "1.0.1", @@ -9819,7 +9820,9 @@ "version": "0.2.0" }, "fraction.js": { - "version": "4.2.0" + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz", + "integrity": "sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==" }, "fresh": { "version": "0.5.2" @@ -11658,9 +11661,9 @@ "version": "2.1.2" }, "sass": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.67.0.tgz", - "integrity": "sha512-SVrO9ZeX/QQyEGtuZYCVxoeAL5vGlYjJ9p4i4HFuekWl8y/LtJ7tJc10Z+ck1c8xOuoBm2MYzcLfTAffD0pl/A==", + "version": "1.68.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.68.0.tgz", + "integrity": "sha512-Lmj9lM/fef0nQswm1J2HJcEsBUba4wgNx2fea6yJHODREoMFnwRpZydBnX/RjyXw2REIwdkbqE4hrTo4qfDBUA==", "requires": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", diff --git a/package.json b/package.json index ae1662440..07b5a8dbe 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@fortawesome/fontawesome-free": "^6.4.2", "@material/mwc-button": "^0.27.0", "@material/mwc-textfield": "^0.27.0", - "autoprefixer": "^10.4.15", + "autoprefixer": "^10.4.16", "chalk": "^5.3.0", "clean-webpack-plugin": "^4.0.0", "commander": "^11.0.0", @@ -57,7 +57,7 @@ "neo-jsdoc": "1.0.1", "neo-jsdoc-x": "1.0.5", "postcss": "^8.4.30", - "sass": "^1.67.0", + "sass": "^1.68.0", "showdown": "^2.1.0", "webpack": "^5.88.2", "webpack-cli": "^5.1.4", From c8087c168fb124aaccd2af2812b79ac178bd49df Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 25 Sep 2023 10:24:10 +0200 Subject: [PATCH 122/162] examples.dialog.DemoDialog: different title for the 2nd dialog --- examples/dialog/DemoDialog.mjs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/examples/dialog/DemoDialog.mjs b/examples/dialog/DemoDialog.mjs index 8e52776ce..b4679844a 100644 --- a/examples/dialog/DemoDialog.mjs +++ b/examples/dialog/DemoDialog.mjs @@ -44,11 +44,12 @@ class DemoDialog extends Dialog { })() } }, { - module : Button, - handler: me.createDialog.bind(me), - iconCls: 'fa fa-window-maximize', - text : 'Create new modal Dialog', - }]; + module : Button, + handler : me.createDialog.bind(me), + iconCls : 'fa fa-window-maximize', + reference: 'create-second-dialog-button', + text : 'Create new modal Dialog', + }] } /** @@ -63,16 +64,16 @@ class DemoDialog extends Dialog { appName : me.appName, boundaryContainerId: me.boundaryContainerId, listeners : {close: me.onWindowClose, scope: me}, - modal : true + modal : true, + title : 'Second Dialog' }); } + /** + * + */ onWindowClose() { - let button = this.down({ - text: 'Create new modal Dialog' - }); - - button.disabled = false; + this.getReference('create-second-dialog-button').disabled = false; } } From 835fd664f6a5bdd6c600f8c30fb17bc054324cf5 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 25 Sep 2023 17:30:18 +0200 Subject: [PATCH 123/162] examples.ConfigurationViewport: switch theme => smarter button check (without id) --- examples/ConfigurationViewport.mjs | 18 +++++++++--------- src/util/Function.mjs | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/ConfigurationViewport.mjs b/examples/ConfigurationViewport.mjs index 1a3391a2f..aabca6c9a 100644 --- a/examples/ConfigurationViewport.mjs +++ b/examples/ConfigurationViewport.mjs @@ -1,8 +1,8 @@ -import Button from '../src/button/Base.mjs'; -import Container from '../src/container/Base.mjs'; -import NeoArray from '../src/util/Array.mjs'; -import Panel from '../src/container/Panel.mjs'; -import Viewport from '../src/container/Viewport.mjs'; +import Button from '../src/button/Base.mjs'; +import Container from '../src/container/Base.mjs'; +import {bindAppend} from '../src/util/Function.mjs'; +import Panel from '../src/container/Panel.mjs'; +import Viewport from '../src/container/Viewport.mjs'; /** * Base class for example Apps which should be configurable @@ -112,8 +112,7 @@ class ConfigurationViewport extends Viewport { items: [...me.configurationComponents, { module : Button, - handler: me.onSwitchTheme.bind(me, 'cmp'), - id : me.id + '_cmp_' + 'switchThemeButton', + handler: bindAppend(me.onSwitchTheme, me, 'cmp'), style : {marginTop: '20px'}, text : theme === 'neo-theme-dark' ? 'Theme Light' : 'Theme Dark', width : 100 @@ -172,11 +171,12 @@ class ConfigurationViewport extends Viewport { } /** + * @param {Object} data * @param {String} target */ - onSwitchTheme(target) { + onSwitchTheme(data, target) { let me = this, - button = Neo.getComponent(me.id + (target !== 'cmp' ? '__' : '_cmp_') + 'switchThemeButton'), + button = data.component, newTheme, oldTheme; if (button.text === 'Theme Light') { diff --git a/src/util/Function.mjs b/src/util/Function.mjs index 6fed20118..f75d7be91 100644 --- a/src/util/Function.mjs +++ b/src/util/Function.mjs @@ -1,11 +1,11 @@ /** * Append args instead of prepending them + * @param {Function} fn * @param {Object} scope * @returns {Function} */ -export function bindAppend(scope) { - const fn = this, - args = [].slice.call(arguments).slice(1); +export function bindAppend(fn, scope) { + const args = [].slice.call(arguments).slice(2); return function() { return fn.apply(scope, [].slice.call(arguments).concat(args)) From 203bd87c90648464e4acd7699df78444ed567166 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 25 Sep 2023 18:27:28 +0200 Subject: [PATCH 124/162] toolbar.Breadcrumb: default button ui => tertiary --- src/toolbar/Breadcrumb.mjs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/toolbar/Breadcrumb.mjs b/src/toolbar/Breadcrumb.mjs index 449ba3101..12478f6d7 100644 --- a/src/toolbar/Breadcrumb.mjs +++ b/src/toolbar/Breadcrumb.mjs @@ -27,6 +27,13 @@ class Breadcrumb extends Toolbar { * @member {String[]} baseCls=['neo-breadcrumb-toolbar','neo-toolbar'] */ baseCls: ['neo-breadcrumb-toolbar', 'neo-toolbar'], + /** + * @member {Object} itemDefaults={ntype:'button', ui: 'tertiary'} + */ + itemDefaults: { + ntype: 'button', + ui : 'tertiary' + }, /** * @member {Neo.data.Store|Object} store_=null */ From e2360e3215575bc4e379d68721dda83ca8256eac Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 25 Sep 2023 19:01:05 +0200 Subject: [PATCH 125/162] examples.ConfigurationViewport: top level themes config (making it easier to add custom themes) --- examples/ConfigurationViewport.mjs | 37 ++++++++++++++++++------------ 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/examples/ConfigurationViewport.mjs b/examples/ConfigurationViewport.mjs index aabca6c9a..6c41c0bc3 100644 --- a/examples/ConfigurationViewport.mjs +++ b/examples/ConfigurationViewport.mjs @@ -4,6 +4,12 @@ import {bindAppend} from '../src/util/Function.mjs'; import Panel from '../src/container/Panel.mjs'; import Viewport from '../src/container/Viewport.mjs'; +// add custom themes here +const themes = [ + {name: 'neo-theme-dark', label: 'Theme Dark'}, + {name: 'neo-theme-light', label: 'Theme Light'} +] + /** * Base class for example Apps which should be configurable * @class Neo.examples.ConfigurationViewport @@ -175,21 +181,22 @@ class ConfigurationViewport extends Viewport { * @param {String} target */ onSwitchTheme(data, target) { - let me = this, - button = data.component, - newTheme, oldTheme; - - if (button.text === 'Theme Light') { - newTheme = 'neo-theme-light'; - oldTheme = 'neo-theme-dark'; - - button.text = 'Theme Dark'; - } else { - newTheme = 'neo-theme-dark'; - oldTheme = 'neo-theme-light'; - - button.text = 'Theme Light'; - } + let me = this, + button = data.component, + countThemes = themes.length, + newTheme, oldIndex, oldTheme, themeIndex; + + themes.forEach((theme, index) => { + if (button.text === theme.label) { + newTheme = theme.name; + themeIndex = index; + } + }); + + oldIndex = (themeIndex + 1) % countThemes; + oldTheme = themes[oldIndex].name; + + button.text = themes[oldIndex].label; if (target === 'cmp') { me.exampleComponent.theme = newTheme; From fb2144439b58037920d9a4189832fb551bbc91cb Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 26 Sep 2023 11:54:54 +0200 Subject: [PATCH 126/162] form.field.Base: increase the debounce timers to 1000ms #4944 --- src/form/field/Base.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/form/field/Base.mjs b/src/form/field/Base.mjs index c6612748b..9b5d3fa4d 100644 --- a/src/form/field/Base.mjs +++ b/src/form/field/Base.mjs @@ -13,8 +13,8 @@ class Base extends Component { * @static */ static delayable = { - fireChangeEvent : {type: 'debounce', timer: 300}, - fireUserChangeEvent: {type: 'debounce', timer: 300} + fireChangeEvent : {type: 'debounce', timer: 1000}, + fireUserChangeEvent: {type: 'debounce', timer: 1000} } static config = { From 41868ac240df512343f66ec4bfc0d3a58249510f Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 26 Sep 2023 11:57:48 +0200 Subject: [PATCH 127/162] v6.7.1 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index 19bbabb30..c996be8af 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.7.0' + * @member {String} version='6.7.1' */ - version: '6.7.0' + version: '6.7.1' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index 19bbabb30..c996be8af 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.7.0' + * @member {String} version='6.7.1' */ - version: '6.7.0' + version: '6.7.1' } /** diff --git a/package.json b/package.json index 07b5a8dbe..e3452cc63 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.7.0", + "version": "6.7.1", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index e0e3d9a5d..56f1fdd0e 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.7.0' + * @default '6.7.1' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.7.0' + version: '6.7.1' }; Object.assign(DefaultConfig, { From e218a615d00d7027c17fc808796ce3e8bbb698e2 Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 26 Sep 2023 14:22:29 +0200 Subject: [PATCH 128/162] component.Base: up() => docs comment improvement --- src/component/Base.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/component/Base.mjs b/src/component/Base.mjs index 752191a7e..abc73cf0c 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -2161,7 +2161,7 @@ class Base extends CoreBase { /** * Convenience shortcut for Neo.manager.Component.up * @param {Object|String} config - * @returns {Neo.core.Base} The matching instance or null + * @returns {Neo.component.Base|null} The matching instance or null */ up(config) { return ComponentManager.up(this.id, config) From 171c1f9e46ed6bfe4cbcdfe129d5328d9ea430ef Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 27 Sep 2023 10:35:26 +0200 Subject: [PATCH 129/162] Neo.getComponent(this.parentId) => this.parent --- src/component/Base.mjs | 6 +++--- src/table/View.mjs | 4 ++-- src/table/header/Button.mjs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/component/Base.mjs b/src/component/Base.mjs index abc73cf0c..0efb25513 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -1336,7 +1336,7 @@ class Base extends CoreBase { } if (me.parentId) { - parentComponent = Neo.getComponent(me.parentId) || Neo.get(me.parentId); + parentComponent = me.parent || Neo.get(me.parentId); if (parentComponent) { return parentComponent.getConfigInstanceByNtype(configName, ntype) @@ -1559,7 +1559,7 @@ class Base extends CoreBase { let removeFn = function() { if(me.parentId !== 'document.body') { me.vdom.removeDom = true; - Neo.getComponent(me.parentId).update() + me.parent.update() } else { me.unmount() } @@ -2033,7 +2033,7 @@ class Base extends CoreBase { if (me.silentVdomUpdate) { me.needsVdomUpdate = true } else if(me.parentId !== 'document.body') { - Neo.getComponent(me.parentId).update() + me.parent.update() } else { !me.mounted && me.render(true) } diff --git a/src/table/View.mjs b/src/table/View.mjs index 09ec5acd9..6aff3e1b5 100644 --- a/src/table/View.mjs +++ b/src/table/View.mjs @@ -134,7 +134,7 @@ class View extends Component { createViewData(inputData) { let me = this, amountRows = inputData.length, - container = Neo.getComponent(me.parentId), + container = me.parent, columns = container.items[0].items, colCount = columns.length, data = [], @@ -337,7 +337,7 @@ class View extends Component { */ onStoreRecordChange(opts) { let me = this, - container = Neo.getComponent(me.parentId), + container = me.parent, needsUpdate = false, vdom = me.vdom, cellId, cellNode, column, index, scope; diff --git a/src/table/header/Button.mjs b/src/table/header/Button.mjs index 535d5aceb..f8997499e 100644 --- a/src/table/header/Button.mjs +++ b/src/table/header/Button.mjs @@ -369,9 +369,9 @@ class Button extends BaseButton { */ onDrop(data) { let me = this, - headerToolbar = Neo.getComponent(me.parentId), + headerToolbar = me.parent, style = me.style, - tableContainer = Neo.getComponent(headerToolbar.parentId); + tableContainer = headerToolbar.parent; me.onDragLeave(); headerToolbar.switchItems(me.id, data.srcId); From d7532438ad9c32dd70866b48e27effbf7b419551 Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 27 Sep 2023 10:58:24 +0200 Subject: [PATCH 130/162] table.View: getColumn() => not accessing header buttons #4946 --- src/component/Base.mjs | 2 +- src/table/Container.mjs | 80 ++++++++++++++++++++--------------------- src/table/View.mjs | 4 +-- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/component/Base.mjs b/src/component/Base.mjs index 0efb25513..2b2c9f905 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -1419,7 +1419,7 @@ class Base extends CoreBase { * @returns {Number|undefined} */ getMountedParentIndex() { - let parent = Neo.getComponent(this.parentId), + let parent = this.parent, items = parent?.items || [], i = 0, index = 0, diff --git a/src/table/Container.mjs b/src/table/Container.mjs index 956d1ee91..0affde71f 100644 --- a/src/table/Container.mjs +++ b/src/table/Container.mjs @@ -142,7 +142,7 @@ class Container extends BaseContainer { me.vdom.id = me.getWrapperId(); - me.createColumns(me.columns); + me.createColumns(me.columns) } /** @@ -152,7 +152,7 @@ class Container extends BaseContainer { * @protected */ afterSetSelectionModel(value, oldValue) { - this.rendered && value.register(this); + this.rendered && value.register(this) } /** @@ -163,7 +163,7 @@ class Container extends BaseContainer { */ afterSetShowHeaderFilters(value, oldValue) { if (oldValue !== undefined) { - Neo.getComponent(this.headerToolbarId).showHeaderFilters = value; + Neo.getComponent(this.headerToolbarId).showHeaderFilters = value } } @@ -175,7 +175,7 @@ class Container extends BaseContainer { */ afterSetSortable(value, oldValue) { if (oldValue !== undefined) { - Neo.getComponent(this.headerToolbarId).sortable = value; + Neo.getComponent(this.headerToolbarId).sortable = value } } @@ -187,7 +187,7 @@ class Container extends BaseContainer { */ afterSetUseCustomScrollbars(value, oldValue) { if (value === true) { - this.vdom.cls = NeoArray.union(this.vdom.cls, ['neo-use-custom-scrollbar']); + this.vdom.cls = NeoArray.union(this.vdom.cls, ['neo-use-custom-scrollbar']) } } @@ -200,17 +200,17 @@ class Container extends BaseContainer { cssRules = []; if (me.dockLeftMargin) { - cssRules.push('#' + id + '::-webkit-scrollbar-track:horizontal {margin-left: ' + me.dockLeftMargin + 'px;}'); + cssRules.push('#' + id + '::-webkit-scrollbar-track:horizontal {margin-left: ' + me.dockLeftMargin + 'px;}') } if (me.dockRightMargin) { - cssRules.push('#' + id + '::-webkit-scrollbar-track:horizontal {margin-right: ' + me.dockRightMargin + 'px;}'); + cssRules.push('#' + id + '::-webkit-scrollbar-track:horizontal {margin-right: ' + me.dockRightMargin + 'px;}') } if (cssRules.length > 0) { - CssUtil.insertRules(me.appName, cssRules); + CssUtil.insertRules(me.appName, cssRules) } - me.scrollbarsCssApplied = true; + me.scrollbarsCssApplied = true } /** @@ -221,10 +221,10 @@ class Container extends BaseContainer { */ beforeSetColumns(value, oldValue) { if (this.configsApplied) { - return this.createColumns(value); + return this.createColumns(value) } - return value; + return value } /** @@ -234,7 +234,7 @@ class Container extends BaseContainer { * @protected */ beforeSetHeaderToolbarId(value, oldValue) { - return value ? value : oldValue; + return value || oldValue } /** @@ -246,7 +246,7 @@ class Container extends BaseContainer { beforeSetSelectionModel(value, oldValue) { oldValue?.destroy(); - return ClassSystemUtil.beforeSetInstance(value, RowModel); + return ClassSystemUtil.beforeSetInstance(value, RowModel) } /** @@ -270,20 +270,20 @@ class Container extends BaseContainer { if (value instanceof Store) { value.on(listeners); - value.getCount() > 0 && me.onStoreLoad(value.items); + value.getCount() > 0 && me.onStoreLoad(value.items) } else { value = ClassSystemUtil.beforeSetInstance(value, Store, { listeners - }); + }) } // in case we dynamically change the store, the view needs to get the new reference if (me.view) { - me.view.store = value; + me.view.store = value } } - return value; + return value } /** @@ -293,7 +293,7 @@ class Container extends BaseContainer { * @protected */ beforeSetViewId(value, oldValue) { - return value ? value : oldValue; + return value || oldValue } /** @@ -325,26 +325,26 @@ class Container extends BaseContainer { if (sorters?.[0]) { if (column.dataField === sorters[0].property) { - column.isSorted = sorters[0].direction; + column.isSorted = sorters[0].direction } } column.listeners = { sort : me.onSortColumn, scope: me - }; + } }); me.items[0].items = columns; - return columns; + return columns } /** * @param {Number} countRows */ createRandomViewData(countRows) { - this.loadData(countRows); + this.loadData(countRows) } /** @@ -360,7 +360,7 @@ class Container extends BaseContainer { me.applyCustomScrollbarsCss(); } - me.items = items; + me.items = items } /** @@ -368,21 +368,21 @@ class Container extends BaseContainer { * @returns {*} */ getVdomRoot() { - return this.vdom.cn[0]; + return this.vdom.cn[0] } /** * @returns {Object[]} The new vdom items root */ getVdomItemsRoot() { - return this.vdom.cn[0]; + return this.vdom.cn[0] } /** * @returns {Neo.table.View} */ getView() { - return Neo.getComponent(this.viewId) || Neo.get(this.viewId); + return Neo.getComponent(this.viewId) || Neo.get(this.viewId) } /** @@ -390,14 +390,14 @@ class Container extends BaseContainer { * @returns {Neo.vdom.VNode} */ getVnodeRoot() { - return this.vnode.childNodes[0]; + return this.vnode.childNodes[0] } /** * @returns {String} */ getWrapperId() { - return `${this.id}__wrapper`; + return `${this.id}__wrapper` } /** @@ -409,8 +409,8 @@ class Container extends BaseContainer { countColumns = columns.length; Neo.manager.Store.createRandomData([countColumns, countRows]).then(data => { - me.createViewData(data); - }); + me.createViewData(data) + }) } /** @@ -426,8 +426,8 @@ class Container extends BaseContainer { if (me.createRandomData) { // todo: if mounting apply after mount setTimeout(() => { - me.createRandomViewData(me.amountRows); - }, 50); + me.createRandomViewData(me.amountRows) + }, 50) } } @@ -442,14 +442,14 @@ class Container extends BaseContainer { me.store.sort(opts); me.removeSortingCss(opts.property); - me.onStoreLoad(me.store.items); + me.onStoreLoad(me.store.items) } /** * */ onStoreFilter() { - this.onStoreLoad(this.store.items); + this.onStoreLoad(this.store.items) } /** @@ -464,14 +464,14 @@ class Container extends BaseContainer { me.createViewData(data); if (me.store.sorters.length < 1) { - me.removeSortingCss(); + me.removeSortingCss() } } else { listenerId = me.on('rendered', () => { me.un('rendered', listenerId); setTimeout(() => { - me.createViewData(data); - }, 50); + me.createViewData(data) + }, 50) }); } } @@ -487,7 +487,7 @@ class Container extends BaseContainer { * @param {*} opts.value */ onStoreRecordChange(opts) { - Neo.getComponent(this.viewId).onStoreRecordChange(opts); + Neo.getComponent(this.viewId).onStoreRecordChange(opts) } /** @@ -497,9 +497,9 @@ class Container extends BaseContainer { removeSortingCss(dataField) { this.items[0].items.forEach(column => { if (column.dataField !== dataField) { - column.removeSortingCss(); + column.removeSortingCss() } - }); + }) } } diff --git a/src/table/View.mjs b/src/table/View.mjs index 6aff3e1b5..a5621ce3d 100644 --- a/src/table/View.mjs +++ b/src/table/View.mjs @@ -248,8 +248,8 @@ class View extends Component { * @returns {Object|null} */ getColumn(field) { - let container = Neo.getComponent(this.parentId), - columns = container.columns, + let container = this.parent, + columns = container.items[0].items, // todo: we need a shortcut for accessing the header toolbar i = 0, len = columns.length, column; From e49961bace5605161daaf956d5a5bee5acbee68e Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 27 Sep 2023 17:52:28 +0200 Subject: [PATCH 131/162] v6.7.2 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index c996be8af..49b117d88 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.7.1' + * @member {String} version='6.7.2' */ - version: '6.7.1' + version: '6.7.2' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index c996be8af..49b117d88 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.7.1' + * @member {String} version='6.7.2' */ - version: '6.7.1' + version: '6.7.2' } /** diff --git a/package.json b/package.json index e3452cc63..53335ca30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.7.1", + "version": "6.7.2", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index 56f1fdd0e..7b5cc0642 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.7.1' + * @default '6.7.2' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.7.1' + version: '6.7.2' }; Object.assign(DefaultConfig, { From 846533c99bcf2c8ab034d2e0c31e33797e55d4fb Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 27 Sep 2023 20:20:26 +0200 Subject: [PATCH 132/162] component.Base: getDomRect() => inconsistent return values #4950 --- src/component/Base.mjs | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/src/component/Base.mjs b/src/component/Base.mjs index 2b2c9f905..0eee88a05 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -1362,31 +1362,13 @@ class Base extends CoreBase { * @returns {Promise} */ async getDomRect(id=this.id, appName=this.appName) { - if (Array.isArray(id)) { - return await Neo.main.DomAccess.getBoundingClientRect({appName, id}); - } - else { - const - { - x, - y, - width, - height, - minWidth, - minHeight - } = await Neo.main.DomAccess.getBoundingClientRect({appName, id}), - result = new Rectangle(x, y, width, height); - - if (minWidth) { - result.minWidth = minWidth; - } + const result = await Neo.main.DomAccess.getBoundingClientRect({appName, id}); - if (minHeight) { - result.minHeight = minHeight; - } - - return result; + if (Array.isArray(result)) { + return result.map(rect => Rectangle.clone(rect)) } + + return Rectangle.clone(result) } /** From c22ec679fc984d9f3c7768f6b24694b2de74935d Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 27 Sep 2023 21:23:27 +0200 Subject: [PATCH 133/162] main.DomAccess: getBoundingClientRect() does not pass minHeight & minWidth to the app worker #4951 --- src/util/Rectangle.mjs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/util/Rectangle.mjs b/src/util/Rectangle.mjs index be359ac19..268fe5ed7 100644 --- a/src/util/Rectangle.mjs +++ b/src/util/Rectangle.mjs @@ -87,6 +87,15 @@ export default class Rectangle extends DOMRect { className: 'Neo.util.Rectangle' } + /** + * @member {Number|null} minHeight=null + */ + minHeight = null + /** + * @member {Number|null} minWidth=null + */ + minWidth = null + /** * Checks if rect1 does not have an intersection with rect2 * !includes() is true for intersections as well @@ -587,4 +596,13 @@ export default class Rectangle extends DOMRect { setTimeout(() => div.remove(), 30000); return div; } + + /** + * When using JSON.stringify(this), we want to add minHeight & minWidth to the output. + * @returns {Object} + */ + toJSON() { + const {bottom, height, left, minHeight, minWidth, right, top, width, x, y} = this; + return {bottom, height, left, minHeight, minWidth, right, top, width, x, y} + } } From 27e1ccdebf4c607373c9bc53f38e1aa5e789c0a9 Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 27 Sep 2023 21:34:07 +0200 Subject: [PATCH 134/162] main.DomAccess: getBoundingClientRect() minor cleanup --- src/main/DomAccess.mjs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main/DomAccess.mjs b/src/main/DomAccess.mjs index 25f66abd2..3dd34f085 100644 --- a/src/main/DomAccess.mjs +++ b/src/main/DomAccess.mjs @@ -351,16 +351,19 @@ class DomAccess extends Base { * Returns node.getBoundingClientRect() for a given dom node id * @param {Object} data * @param {Array|String} data.id either an id or an array of ids - * @returns {Array|Object} In case id is an array, an array of DomRects is returned, otherwise an DomRect object + * @returns {DOMRect|DOMRect[]} In case id is an array, an array of DomRects is returned, otherwise an DomRect object */ getBoundingClientRect(data) { - let returnData; + let me = this, + returnData; if (Array.isArray(data.id)) { - return data.id.map(id => this.getBoundingClientRect({ id })); + console.log(data.id.map(id => me.getBoundingClientRect({ id }))); + return data.id.map(id => me.getBoundingClientRect({ id })); } else { - let node = this.getElementOrBody(data.nodeType ? data : data.id), - rect = {}, style, minWidth, minHeight; + let node = me.getElementOrBody(data.nodeType ? data : data.id), + rect = {}, + minWidth, minHeight, style; returnData = {}; @@ -377,15 +380,15 @@ class DomAccess extends Base { // Note that 0px is what the DOM reports if no minWidth is specified // so we do not report a minimum in these cases. if (lengthRE.test(minWidth) && minWidth !== '0px') { - returnData.minWidth = this.measure({ value : minWidth, id : node}); + returnData.minWidth = me.measure({ value : minWidth, id : node}) } if (lengthRE.test(minHeight) && minHeight !== '0px') { - returnData.minHeight = this.measure({ value : minHeight, id : node }); + returnData.minHeight = me.measure({ value : minHeight, id : node }) } } } - return returnData; + return returnData } getClippedRect(data) { @@ -395,11 +398,11 @@ class DomAccess extends Base { for (let parentElement = node.offsetParent; rect && parentElement !== document.documentElement; parentElement = parentElement.parentElement) { if (defaultView.getComputedStyle(parentElement).getPropertyValue('overflow') !== 'visible') { - rect = rect.intersects(this.getBoundingClientRect(parentElement)); + rect = rect.intersects(this.getBoundingClientRect(parentElement)) } } - return rect; + return rect } onDocumentMutation(mutations) { From aae132f325b649adb217af17da4144db9094da58 Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 27 Sep 2023 21:35:31 +0200 Subject: [PATCH 135/162] draggable.toolbar.SortZone: switchItems() => regression issue #4948 --- src/draggable/toolbar/SortZone.mjs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/draggable/toolbar/SortZone.mjs b/src/draggable/toolbar/SortZone.mjs index fc0f0d48f..5f79f7e18 100644 --- a/src/draggable/toolbar/SortZone.mjs +++ b/src/draggable/toolbar/SortZone.mjs @@ -263,17 +263,17 @@ class SortZone extends DragZone { map = me.indexMap, rect1 = itemRects[index1], rect2 = itemRects[index2], - rect1Copy = {...rect1}, - rect2Copy = {...rect2}; + rect1Copy = rect1.clone(), + rect2Copy = rect2.clone(); if (me.sortDirection === 'horizontal') { rect1.width = rect2Copy.width; - rect2.left = rect1Copy.left + rect2Copy.width; + rect2.x = rect1Copy.x + rect2Copy.width; rect2.width = rect1Copy.width; } else { rect1.height = rect2Copy.height; rect2.height = rect1Copy.height; - rect2.top = rect1Copy.top + rect2Copy.height; + rect2.y = rect1Copy.y + rect2Copy.height; } tmp = map[index1]; From 71cb06bfcb3839e7b28d97e9814bc13f490adba4 Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 27 Sep 2023 22:01:15 +0200 Subject: [PATCH 136/162] main.DomAccess: -testing log --- src/main/DomAccess.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/DomAccess.mjs b/src/main/DomAccess.mjs index 3dd34f085..15d664e15 100644 --- a/src/main/DomAccess.mjs +++ b/src/main/DomAccess.mjs @@ -358,7 +358,6 @@ class DomAccess extends Base { returnData; if (Array.isArray(data.id)) { - console.log(data.id.map(id => me.getBoundingClientRect({ id }))); return data.id.map(id => me.getBoundingClientRect({ id })); } else { let node = me.getElementOrBody(data.nodeType ? data : data.id), From defedcb292660ee8c9fa1560dec08a17f17d79c8 Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 28 Sep 2023 12:27:50 +0200 Subject: [PATCH 137/162] table.Container: get headerToolbar(), get view() convenience shortcuts #4952 --- src/table/Container.mjs | 29 +++++++++++------ src/table/header/Button.mjs | 62 +++++++++++++++++++----------------- src/table/header/Toolbar.mjs | 24 +++++++------- 3 files changed, 63 insertions(+), 52 deletions(-) diff --git a/src/table/Container.mjs b/src/table/Container.mjs index 0affde71f..ca451b9f7 100644 --- a/src/table/Container.mjs +++ b/src/table/Container.mjs @@ -114,6 +114,22 @@ class Container extends BaseContainer { ]} } + /** + * Convenience method to access the Neo.table.header.Toolbar + * @returns {Neo.table.header.Toolbar|null} + */ + get headerToolbar() { + return Neo.getComponent(this.headerToolbarId) || Neo.get(this.headerToolbarId) + } + + /** + * Convenience method to access the Neo.table.View + * @returns {Neo.table.View|null} + */ + get view() { + return Neo.getComponent(this.viewId) || Neo.get(this.viewId) + } + /** * @param {Object} config */ @@ -163,7 +179,7 @@ class Container extends BaseContainer { */ afterSetShowHeaderFilters(value, oldValue) { if (oldValue !== undefined) { - Neo.getComponent(this.headerToolbarId).showHeaderFilters = value + this.headerToolbar.showHeaderFilters = value } } @@ -175,7 +191,7 @@ class Container extends BaseContainer { */ afterSetSortable(value, oldValue) { if (oldValue !== undefined) { - Neo.getComponent(this.headerToolbarId).sortable = value + this.headerToolbar.sortable = value } } @@ -378,13 +394,6 @@ class Container extends BaseContainer { return this.vdom.cn[0] } - /** - * @returns {Neo.table.View} - */ - getView() { - return Neo.getComponent(this.viewId) || Neo.get(this.viewId) - } - /** * @override * @returns {Neo.vdom.VNode} @@ -487,7 +496,7 @@ class Container extends BaseContainer { * @param {*} opts.value */ onStoreRecordChange(opts) { - Neo.getComponent(this.viewId).onStoreRecordChange(opts) + this.view.onStoreRecordChange(opts) } /** diff --git a/src/table/header/Button.mjs b/src/table/header/Button.mjs index f8997499e..6fad70d96 100644 --- a/src/table/header/Button.mjs +++ b/src/table/header/Button.mjs @@ -134,12 +134,12 @@ class Button extends BaseButton { let me = this; if (value === true) { - me.getVdomRoot().draggable = true; + me.getVdomRoot().draggable = true } else { - delete me.getVdomRoot().draggable; + delete me.getVdomRoot().draggable } - me.update(); + me.update() } /** @@ -173,13 +173,13 @@ class Button extends BaseButton { // testing check until all example tables have a store if (!container || !container.store) { - return; + return } me.mounted && me.fire('sort', { direction: value, property : me.dataField - }); + }) } /** @@ -213,15 +213,15 @@ class Button extends BaseButton { ...me.editorConfig }); - me.vdom.cn.push(me.filterField.vdom); + me.vdom.cn.push(me.filterField.vdom) } else { - delete me.filterField.vdom.removeDom; + delete me.filterField.vdom.removeDom } } else if (me.filterField) { - me.filterField.vdom.removeDom = true; + me.filterField.vdom.removeDom = true } - me.update(); + me.update() } /** @@ -236,20 +236,22 @@ class Button extends BaseButton { if (value === true) { NeoArray.remove(cls, 'neo-sort-hidden'); + me.addDomListeners({ click: me.onButtonClick, scope: me - }); + }) } else { NeoArray.add(cls, 'neo-sort-hidden'); + me.removeDomListeners({ click: me.onButtonClick, scope: me - }); + }) } me.cls = cls; - me.update(); + me.update() } /** @@ -268,7 +270,7 @@ class Button extends BaseButton { destroy(...args) { this.filterField?.destroy(); - super.destroy(...args); + super.destroy(...args) } /** @@ -277,7 +279,7 @@ class Button extends BaseButton { * @returns {Object} The new vdom root */ getVdomRoot() { - return this.vdom.cn[0]; + return this.vdom.cn[0] } /** @@ -286,7 +288,7 @@ class Button extends BaseButton { * @returns {Object} The new vnode root */ getVnodeRoot() { - return this.vnode.childNodes[0]; + return this.vnode.childNodes[0] } /** @@ -301,16 +303,16 @@ class Button extends BaseButton { ASC : null, DESC: 'ASC', null: 'DESC' - }; + } } else { map = { ASC : 'DESC', DESC: null, null: 'ASC' - }; + } } - me.isSorted = map[me.isSorted + '']; + me.isSorted = map[me.isSorted + ''] } /** @@ -321,7 +323,7 @@ class Button extends BaseButton { style = me.style; delete style.opacity; - me.style = style; + me.style = style } /** @@ -332,7 +334,7 @@ class Button extends BaseButton { cls = me.cls; NeoArray.add(cls, 'neo-drag-over'); - me.cls = cls; + me.cls = cls } /** @@ -343,7 +345,7 @@ class Button extends BaseButton { cls = me.cls; NeoArray.remove(cls, 'neo-drag-over'); - me.cls = cls; + me.cls = cls } /** @@ -361,7 +363,7 @@ class Button extends BaseButton { style = me.style; style.opacity = 0.4; - me.style = style; + me.style = style } /** @@ -378,7 +380,7 @@ class Button extends BaseButton { tableContainer.createViewData(tableContainer.store.data); style.opacity = 1; - me.style = style; + me.style = style } /** @@ -404,9 +406,9 @@ class Button extends BaseButton { ...me.filterConfig }); - store.filters = filters; + store.filters = filters } else { - filter.operator = operator; + filter.operator = operator } } } @@ -427,7 +429,7 @@ class Button extends BaseButton { field = model && model.getField(me.dataField); if (value && field.type.toLowerCase() === 'date') { - value = new Date(value); + value = new Date(value) } if (!filter) { @@ -440,9 +442,9 @@ class Button extends BaseButton { ...me.filterConfig }); - store.filters = filters; + store.filters = filters } else { - filter.value = value; + filter.value = value } } } @@ -457,7 +459,7 @@ class Button extends BaseButton { NeoArray.add(cls, 'neo-sort-hidden'); me.cls = cls; - me._isSorted = null; + me._isSorted = null } /** @@ -469,7 +471,7 @@ class Button extends BaseButton { * @returns {*} */ renderer(data) { - return data.value; + return data.value } } diff --git a/src/table/header/Toolbar.mjs b/src/table/header/Toolbar.mjs index c73f303c7..ab8eea5cb 100644 --- a/src/table/header/Toolbar.mjs +++ b/src/table/header/Toolbar.mjs @@ -60,10 +60,10 @@ class Toolbar extends BaseToolbar { me.items.forEach(item => { item.setSilent({ showHeaderFilter: value - }); + }) }); - me.update(); + me.update() } } @@ -80,10 +80,10 @@ class Toolbar extends BaseToolbar { me.items.forEach(item => { item.setSilent({ sortable: value - }); + }) }); - me.update(); + me.update() } } @@ -118,11 +118,11 @@ class Toolbar extends BaseToolbar { style.left = dockLeftWidth + 'px'; } - dockLeftWidth += (item.width + 1); // todo: borders fix + dockLeftWidth += (item.width + 1) // todo: borders fix } else { - item.vdom.cls = []; // remove the button cls from the th tag + item.vdom.cls = [] // remove the button cls from the th tag } - + item.sortable = me.sortable; item.wrapperStyle = style; @@ -135,11 +135,11 @@ class Toolbar extends BaseToolbar { item.wrapperStyle = style; - dockRightWidth += (item.width + 1); // todo: borders fix + dockRightWidth += (item.width + 1) // todo: borders fix } }); - me.update(); + me.update() } /** @@ -148,7 +148,7 @@ class Toolbar extends BaseToolbar { * @override */ getLayoutConfig(dock) { - return 'base'; + return 'base' } /** @@ -157,7 +157,7 @@ class Toolbar extends BaseToolbar { * @returns {Object} The new vdom root */ getVdomRoot() { - return this.vdom.cn[0]; + return this.vdom.cn[0] } /** @@ -166,7 +166,7 @@ class Toolbar extends BaseToolbar { * @returns {Object} The new vnode root */ getVnodeRoot() { - return this.vnode.childNodes[0]; + return this.vnode.childNodes[0] } } From 54459ff347b1a72175adf8ca2f66a184c15794d9 Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 28 Sep 2023 12:47:27 +0200 Subject: [PATCH 138/162] form.field.Text: emptyValue config #4953 --- src/form/field/Text.mjs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/form/field/Text.mjs b/src/form/field/Text.mjs index 61c1121a8..67297fc99 100644 --- a/src/form/field/Text.mjs +++ b/src/form/field/Text.mjs @@ -75,6 +75,11 @@ class Text extends Base { * @member {String[]|null} disabledChars_=null */ disabledChars_: null, + /** + * Configure the value of empty fields. null or an empty string is recommended. + * @member {String|null} emptyValue=null + */ + emptyValue: null, /** * @member {String|null} error_=null */ @@ -950,6 +955,21 @@ class Text extends Base { return value } + /** + * Triggered before the value config gets changed + * @param {String|null} value + * @param {String|null} oldValue + * @returns {String|null} + * @protected + */ + beforeSetValue(value, oldValue) { + if (value === null || value === '') { + return this.emptyValue + } + + return value + } + /** * Resets the field to its original value or null depending on the clearToOriginalValue config */ From 31e1b45d9f5cdd64bc66961c4482a878a7dfd00e Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 28 Sep 2023 12:51:22 +0200 Subject: [PATCH 139/162] v6.7.3 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index 49b117d88..30fc0a917 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.7.2' + * @member {String} version='6.7.3' */ - version: '6.7.2' + version: '6.7.3' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index 49b117d88..30fc0a917 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.7.2' + * @member {String} version='6.7.3' */ - version: '6.7.2' + version: '6.7.3' } /** diff --git a/package.json b/package.json index 53335ca30..139a9e029 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.7.2", + "version": "6.7.3", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index 7b5cc0642..79b6f2473 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.7.2' + * @default '6.7.3' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.7.2' + version: '6.7.3' }; Object.assign(DefaultConfig, { From 8ec05c12045c7472f30c9fe319383b0bb1317955 Mon Sep 17 00:00:00 2001 From: Thorsten Raab Date: Thu, 28 Sep 2023 19:36:14 +0200 Subject: [PATCH 140/162] Extend error message handling --- src/form/field/FileUpload.mjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/form/field/FileUpload.mjs b/src/form/field/FileUpload.mjs index 70db1a9a7..007a45d53 100644 --- a/src/form/field/FileUpload.mjs +++ b/src/form/field/FileUpload.mjs @@ -535,8 +535,13 @@ class FileUpload extends Base { } // Failed network request else { + if (xhr.response){ + const response = JSON.parse(xhr.response); + me.error = response.message; + } else { + me.error = `HTTP status : ${xhr.statusText}`; + } me.progress = NaN; - me.error = `HTTP status : ${xhr.statusText}`; me.state = 'upload-failed'; } } From e49844dac7febf9c2ef57a15e0d33af40cf0d988 Mon Sep 17 00:00:00 2001 From: tobiu Date: Thu, 28 Sep 2023 20:16:23 +0200 Subject: [PATCH 141/162] #4956 shortening the logic --- src/form/field/FileUpload.mjs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/form/field/FileUpload.mjs b/src/form/field/FileUpload.mjs index 007a45d53..e09cfaaa8 100644 --- a/src/form/field/FileUpload.mjs +++ b/src/form/field/FileUpload.mjs @@ -535,14 +535,9 @@ class FileUpload extends Base { } // Failed network request else { - if (xhr.response){ - const response = JSON.parse(xhr.response); - me.error = response.message; - } else { - me.error = `HTTP status : ${xhr.statusText}`; - } + me.error = xhr.response ? JSON.parse(xhr.response).message : `HTTP status : ${xhr.statusText}`; me.progress = NaN; - me.state = 'upload-failed'; + me.state = 'upload-failed'; } } From 94217c5e2414b402d7f0e3ddcab7e93179f9a28c Mon Sep 17 00:00:00 2001 From: Thorsten Raab Date: Fri, 29 Sep 2023 10:32:22 +0200 Subject: [PATCH 142/162] v6.7.4 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index 30fc0a917..eba329da9 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.7.3' + * @member {String} version='6.7.4' */ - version: '6.7.3' + version: '6.7.4' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index 30fc0a917..eba329da9 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.7.3' + * @member {String} version='6.7.4' */ - version: '6.7.3' + version: '6.7.4' } /** diff --git a/package-lock.json b/package-lock.json index 9aac04044..66753d427 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "neo.mjs", - "version": "6.7.0", + "version": "6.7.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "neo.mjs", - "version": "6.7.0", + "version": "6.7.3", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-free": "^6.4.2", diff --git a/package.json b/package.json index 139a9e029..0f487ffaa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.7.3", + "version": "6.7.4", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index 79b6f2473..437cf2939 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.7.3' + * @default '6.7.4' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.7.3' + version: '6.7.4' }; Object.assign(DefaultConfig, { From 499bcb61118792376887701fe55251c4195de3b0 Mon Sep 17 00:00:00 2001 From: tobiu Date: Sat, 30 Sep 2023 13:15:04 +0200 Subject: [PATCH 143/162] calendar.view.MainContainer: regression bug => the DateSelector jumps from null to today to july 2023 #4915 --- src/calendar/view/MainContainer.mjs | 1 + src/component/DateSelector.mjs | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/calendar/view/MainContainer.mjs b/src/calendar/view/MainContainer.mjs index 840a297cb..652526833 100644 --- a/src/calendar/view/MainContainer.mjs +++ b/src/calendar/view/MainContainer.mjs @@ -445,6 +445,7 @@ class MainContainer extends Container { height : me.sideBarWidth, listeners: {change: me.onDateSelectorChange, scope: me}, parentId : me.id, // we need the parentId to access the model inside the ctor + value : null, bind: { locale : data => data.locale, diff --git a/src/component/DateSelector.mjs b/src/component/DateSelector.mjs index c43cdd87f..3fec2144d 100644 --- a/src/component/DateSelector.mjs +++ b/src/component/DateSelector.mjs @@ -346,16 +346,18 @@ class DateSelector extends Component { afterSetValue(value, oldValue) { let me = this; - if (!me.isUpdating) { - me.currentDate = new Date(`${value}T00:00:00.000Z`); + if (value) { + if (!me.isUpdating) { + me.currentDate = new Date(`${value}T00:00:00.000Z`); - me.fire('change', { - component: me, - oldValue, - value - }) - } else { - me.cacheUpdate() + me.fire('change', { + component: me, + oldValue, + value + }) + } else { + me.cacheUpdate() + } } } From b1c0ab1794ae47a5ddf11d711e076d987dabb452 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 2 Oct 2023 10:21:44 +0200 Subject: [PATCH 144/162] dependencies update --- package-lock.json | 18 +++++++++--------- package.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 66753d427..c39d7ac2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "neo.mjs", - "version": "6.7.3", + "version": "6.7.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "neo.mjs", - "version": "6.7.3", + "version": "6.7.4", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-free": "^6.4.2", @@ -23,7 +23,7 @@ "inquirer": "^9.2.11", "neo-jsdoc": "1.0.1", "neo-jsdoc-x": "1.0.5", - "postcss": "^8.4.30", + "postcss": "^8.4.31", "sass": "^1.68.0", "showdown": "^2.1.0", "webpack": "^5.88.2", @@ -5388,9 +5388,9 @@ } }, "node_modules/postcss": { - "version": "8.4.30", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.30.tgz", - "integrity": "sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { "type": "opencollective", @@ -11198,9 +11198,9 @@ "dev": true }, "postcss": { - "version": "8.4.30", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.30.tgz", - "integrity": "sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "requires": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", diff --git a/package.json b/package.json index 0f487ffaa..fb5b937f5 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "inquirer": "^9.2.11", "neo-jsdoc": "1.0.1", "neo-jsdoc-x": "1.0.5", - "postcss": "^8.4.30", + "postcss": "^8.4.31", "sass": "^1.68.0", "showdown": "^2.1.0", "webpack": "^5.88.2", From 5118134975c3e7c89f8d39e7c8c4a3873d694471 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 2 Oct 2023 10:34:57 +0200 Subject: [PATCH 145/162] component.Base: cleanup (doc comments) --- src/component/Base.mjs | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/component/Base.mjs b/src/component/Base.mjs index 0eee88a05..2614c6ab0 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -48,9 +48,10 @@ class Base extends CoreBase { ntype: 'component', /** * The default alignment specification to position this Component relative to some other - * Component, or Element or Rectangle. + * Component, or Element or Rectangle. Only applies in case floating = true. + * @member {Object|String} align_={edgeAlign:'t-b',constrainTo:'document.body'} */ - align_ : { + align_: { edgeAlign : 't-b', constrainTo : 'document.body' }, @@ -970,26 +971,30 @@ class Base extends CoreBase { } /** - * @param {Object|String} align + * Triggered before the align config gets changed. + * @param {Object|String} value + * @param {Object} oldValue * @returns {Object} + * @protected */ - beforeSetAlign(align) { + beforeSetAlign(value, oldValue) { let me = this; // Just a simple 't-b' - if (typeof align === 'string') { - align = { - edgeAlign: align - }; + if (typeof value === 'string') { + value = { + edgeAlign: value + } } // merge the incoming alignment specification into the configured default - return me.merge(me.merge({}, me.constructor.config.align), align); + return me.merge(me.merge({}, me.constructor.config.align), value) } /** * Triggered before the cls config gets changed. * @param {String[]} value * @param {String[]} oldValue + * @returns {String[]} * @protected */ beforeSetCls(value, oldValue) { @@ -1018,8 +1023,9 @@ class Base extends CoreBase { /** * Triggered before the domListeners config gets changed. - * @param {Object} value - * @param {Object} oldValue + * @param {Object|Object[]} value + * @param {Object[]} oldValue + * @returns {Object[]} * @protected */ beforeSetDomListeners(value, oldValue) { @@ -1034,6 +1040,7 @@ class Base extends CoreBase { * Triggered before the hideMode config gets changed * @param {String} value * @param {String} oldValue + * @returns {String} * @protected */ beforeSetHideMode(value, oldValue) { @@ -1045,6 +1052,7 @@ class Base extends CoreBase { * Creates a KeyNavigation instance if needed. * @param {Object} value * @param {Object} oldValue + * @returns {Neo.util.KeyNavigation} * @protected */ beforeSetKeys(value, oldValue) { @@ -1088,6 +1096,7 @@ class Base extends CoreBase { * Triggered before the plugins config gets changed. * @param {Object[]} value * @param {Object[]} oldValue + * @returns {Neo.plugin.Base[]} * @protected */ beforeSetPlugins(value, oldValue) { @@ -2006,7 +2015,7 @@ class Base extends CoreBase { * hideMode: 'removeDom' uses vdom removeDom. * hideMode: 'visibility' uses css visibility. */ - show(align) { + show() { const me = this; if (me.hideMode !== 'visibility') { From a6c5d2f5fb6c1e9d9c80f012afc85a3fe45000e3 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 2 Oct 2023 11:35:27 +0200 Subject: [PATCH 146/162] tab.header.Toolbar: sortable #4767 --- resources/scss/src/tab/header/Toolbar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/scss/src/tab/header/Toolbar.scss b/resources/scss/src/tab/header/Toolbar.scss index e95581b29..f09c7b888 100644 --- a/resources/scss/src/tab/header/Toolbar.scss +++ b/resources/scss/src/tab/header/Toolbar.scss @@ -1,6 +1,6 @@ .neo-tab-header-toolbar { background-color: transparent; - height : var(--tab-button-height-pressed); + flex : 0 0 auto; padding : 0; &.neo-dock-bottom { From 2af4af5dbc6cf71de1119938af2130a043b26eeb Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 2 Oct 2023 11:52:55 +0200 Subject: [PATCH 147/162] core.Base: merge() => must not call itself recursively for null values #4960 --- src/core/Base.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/Base.mjs b/src/core/Base.mjs index 9cd115215..d211f6f6a 100644 --- a/src/core/Base.mjs +++ b/src/core/Base.mjs @@ -374,7 +374,7 @@ class Base { for (const key in src) { const value = src[key]; - if (typeof value === 'object') { + if (Neo.isObject(value)) { dest[key] = this.merge(dest[key], value); } else { From bf021532952ba6f0f169d5c174b3f6ccd9bfa65c Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 2 Oct 2023 12:22:57 +0200 Subject: [PATCH 148/162] combine Neo.merge() & core.Base: merge() #4961 --- src/Neo.mjs | 18 +++++++----------- src/component/Base.mjs | 4 ++-- src/core/Base.mjs | 20 -------------------- 3 files changed, 9 insertions(+), 33 deletions(-) diff --git a/src/Neo.mjs b/src/Neo.mjs index b47085bea..362ac941a 100644 --- a/src/Neo.mjs +++ b/src/Neo.mjs @@ -371,17 +371,13 @@ Neo = globalThis.Neo = Object.assign({ * @returns {Object} target */ merge(target, source) { - if (Neo.typeOf(target) === 'Object') { - for (let key in source) { - if (Neo.typeOf(source[key]) === 'Object') { - if (!target[key]) { - target[key] = source[key]; - } else { - Neo.merge(target[key], source[key]); - } - } else { - target[key] = source[key]; - } + for (const key in source) { + const value = source[key]; + + if (Neo.typeOf(value) === 'Object') { + target[key] = this.merge(target[key], value); + } else { + target[key] = value; } } diff --git a/src/component/Base.mjs b/src/component/Base.mjs index 2614c6ab0..bd53dee4a 100644 --- a/src/component/Base.mjs +++ b/src/component/Base.mjs @@ -987,7 +987,7 @@ class Base extends CoreBase { } } // merge the incoming alignment specification into the configured default - return me.merge(me.merge({}, me.constructor.config.align), value) + return Neo.merge(Neo.merge({}, me.constructor.config.align), value) } /** @@ -1138,7 +1138,7 @@ class Base extends CoreBase { if (typeof value === 'object') { // merge the incoming style specification into the configured default - value = me.merge(me.merge({}, me.constructor.config.style), value) + value = Neo.merge(Neo.merge({}, me.constructor.config.style), value) } return value diff --git a/src/core/Base.mjs b/src/core/Base.mjs index d211f6f6a..7e0b01df0 100644 --- a/src/core/Base.mjs +++ b/src/core/Base.mjs @@ -364,26 +364,6 @@ class Base { } } - /** - * Merges nested objects - * @param {Object} dest={} - * @param {Object} src - * @returns {Object} - */ - merge(dest={}, src) { - for (const key in src) { - const value = src[key]; - - if (Neo.isObject(value)) { - dest[key] = this.merge(dest[key], value); - } - else { - dest[key] = value; - } - } - return dest; - } - /** * Override this method to change the order configs are applied to this instance. * @param {Object} config From bc466e6ce02f1ad956dc5526222d15eb48711aee Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 2 Oct 2023 12:25:08 +0200 Subject: [PATCH 149/162] #4961 cleanup --- src/Neo.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo.mjs b/src/Neo.mjs index 362ac941a..837dd2307 100644 --- a/src/Neo.mjs +++ b/src/Neo.mjs @@ -375,7 +375,7 @@ Neo = globalThis.Neo = Object.assign({ const value = source[key]; if (Neo.typeOf(value) === 'Object') { - target[key] = this.merge(target[key], value); + target[key] = Neo.merge(target[key], value); } else { target[key] = value; } From 38cff2c0a92c48da1c0f1e95f1a91484a610efec Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 2 Oct 2023 12:27:42 +0200 Subject: [PATCH 150/162] v6.7.5 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index eba329da9..3ae1cbb90 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.7.4' + * @member {String} version='6.7.5' */ - version: '6.7.4' + version: '6.7.5' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index eba329da9..3ae1cbb90 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.7.4' + * @member {String} version='6.7.5' */ - version: '6.7.4' + version: '6.7.5' } /** diff --git a/package.json b/package.json index fb5b937f5..0dabdab33 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.7.4", + "version": "6.7.5", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index 437cf2939..a23298d48 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.7.4' + * @default '6.7.5' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.7.4' + version: '6.7.5' }; Object.assign(DefaultConfig, { From 065f8aafab9025c3e550edd91d519413bcd87d81 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 2 Oct 2023 13:50:47 +0200 Subject: [PATCH 151/162] main.DomAccess: doc comments, method order --- src/main/DomAccess.mjs | 50 ++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/src/main/DomAccess.mjs b/src/main/DomAccess.mjs index 15d664e15..55fc91b8a 100644 --- a/src/main/DomAccess.mjs +++ b/src/main/DomAccess.mjs @@ -390,6 +390,10 @@ class DomAccess extends Base { return returnData } + /** + * @param {Object|String} data + * @returns {Neo.util.Rectangle} + */ getClippedRect(data) { let node = this.getElement(typeof data === 'object' ? data.id : data), { defaultView } = node.ownerDocument, @@ -404,21 +408,6 @@ class DomAccess extends Base { return rect } - onDocumentMutation(mutations) { - const me = this; - - // If the mutations are purely align subjects being added or removed, take no action. - if (!mutations.every(({ type, addedNodes, removedNodes }) => { - if (type === 'childList') { - const nodes = [...Array.from(addedNodes), ...Array.from(removedNodes)]; - - return nodes.every(a => me.isAlignSubject(a)) - } - })) { - me.syncAligns(); - } - } - /** * @param {String|HTMLElement} nodeId * @returns {HTMLElement} @@ -537,6 +526,24 @@ class DomAccess extends Base { return value; } + /** + * @param {Array} mutations + */ + onDocumentMutation(mutations) { + const me = this; + + // If the mutations are purely align subjects being added or removed, take no action. + if (!mutations.every(({ type, addedNodes, removedNodes }) => { + if (type === 'childList') { + const nodes = [...Array.from(addedNodes), ...Array.from(removedNodes)]; + + return nodes.every(a => me.isAlignSubject(a)) + } + })) { + me.syncAligns(); + } + } + /** * */ @@ -548,6 +555,7 @@ class DomAccess extends Base { /** * @param {Object} data + * @param {String} data.id * @param {String} data.nodeId */ onGetOffscreenCanvas(data) { @@ -627,7 +635,7 @@ class DomAccess extends Base { data, replyId: data.id, success: true - }); + }) } /** @@ -636,7 +644,7 @@ class DomAccess extends Base { */ read(data) { if (typeof data === 'function') { - data(); + data() } } @@ -836,6 +844,11 @@ class DomAccess extends Base { }) } + /** + * @param {Object} data + * @param {String} data.id + * @param {Boolean} data.modal + */ syncModalMask({ id, modal }) { const el = id && this.getElement(id); @@ -852,8 +865,7 @@ class DomAccess extends Base { // Move the mask under the next topmost modal now modal "id" is gone. if (topmostModal) { this.syncModalMask({ id : topmostModal.id, modal : true }) - } - else { + } else { this._modalMask?.remove() } } From 03c435f97a9ab1770e876b6bf9c87df5a1cd5201 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 2 Oct 2023 14:48:52 +0200 Subject: [PATCH 152/162] dialog.Base: showing a dialog with out an animateTargetId breaks #4963 --- examples/dialog/DemoDialog.mjs | 5 ++--- src/dialog/Base.mjs | 28 ++++++++++++++++++++++++++-- src/main/DomAccess.mjs | 4 ++-- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/examples/dialog/DemoDialog.mjs b/examples/dialog/DemoDialog.mjs index b4679844a..74c04d2c3 100644 --- a/examples/dialog/DemoDialog.mjs +++ b/examples/dialog/DemoDialog.mjs @@ -64,16 +64,15 @@ class DemoDialog extends Dialog { appName : me.appName, boundaryContainerId: me.boundaryContainerId, listeners : {close: me.onWindowClose, scope: me}, - modal : true, title : 'Second Dialog' - }); + }) } /** * */ onWindowClose() { - this.getReference('create-second-dialog-button').disabled = false; + this.getReference('create-second-dialog-button').disabled = false } } diff --git a/src/dialog/Base.mjs b/src/dialog/Base.mjs index a8ce07ccb..1210eda01 100644 --- a/src/dialog/Base.mjs +++ b/src/dialog/Base.mjs @@ -136,11 +136,35 @@ class Base extends Panel { construct(config) { super.construct(config); - let me = this; + let me = this, + style = me.style; me.createHeader(); - me.autoShow && me.show() + if (!me.animateTargetId) { + Neo.assignDefaults(style, { + left : '50%', + top : '50%', + transform: 'translate(-50%, -50%)', + width : '50%' + }); + + me.style = style + } + } + + init() { + super.init(); + + let me = this; + + if (me.animateTargetId) { + me.autoShow && me.show() + } else { + me.timeout(100).then(() => { + me.syncModalMask() + }) + } } /** diff --git a/src/main/DomAccess.mjs b/src/main/DomAccess.mjs index 55fc91b8a..84cf96cf4 100644 --- a/src/main/DomAccess.mjs +++ b/src/main/DomAccess.mjs @@ -856,7 +856,7 @@ class DomAccess extends Base { if (el && modal && el.ownerDocument.contains(el) && el.ownerDocument.defaultView.getComputedStyle(el).getPropertyValue('display') !== 'none') { document.body.insertBefore(this.modalMask, el); } - // Otherwise, the mask needs to be blow the next topmost modal dialog if possible, or hidden + // Otherwise, the mask needs to be below the next topmost modal dialog if possible, or hidden else { const modals = document.querySelectorAll('.neo-modal'), @@ -864,7 +864,7 @@ class DomAccess extends Base { // Move the mask under the next topmost modal now modal "id" is gone. if (topmostModal) { - this.syncModalMask({ id : topmostModal.id, modal : true }) + this.syncModalMask({ id: topmostModal.id, modal: true }) } else { this._modalMask?.remove() } From 1efed7ec7ddcac5969b2a5febf8670bc7d5ab391 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 2 Oct 2023 16:24:15 +0200 Subject: [PATCH 153/162] examples.dialog.DemoDialog: modal state for child dialogs #4964 --- examples/dialog/DemoDialog.mjs | 19 +++++++++++++++++-- src/dialog/Base.mjs | 18 ++++++------------ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/examples/dialog/DemoDialog.mjs b/examples/dialog/DemoDialog.mjs index 74c04d2c3..38a57800a 100644 --- a/examples/dialog/DemoDialog.mjs +++ b/examples/dialog/DemoDialog.mjs @@ -37,10 +37,10 @@ class DemoDialog extends Dialog { result.push({ id : i, name : `Option ${i + 1}` - }); + }) } - return result; + return result })() } }, { @@ -52,6 +52,20 @@ class DemoDialog extends Dialog { }] } + /** + * Triggered after the modal config got changed + * @param {Boolean} value + * @param {Boolean} oldValue + * @protected + */ + afterSetModal(value, oldValue) { + super.afterSetModal(value, oldValue); + + if (this.dialog) { + this.dialog.modal = value + } + } + /** * @param {Object} data */ @@ -64,6 +78,7 @@ class DemoDialog extends Dialog { appName : me.appName, boundaryContainerId: me.boundaryContainerId, listeners : {close: me.onWindowClose, scope: me}, + modal : me.app.mainView.down({ valueLabelText : 'Modal' }).checked, title : 'Second Dialog' }) } diff --git a/src/dialog/Base.mjs b/src/dialog/Base.mjs index 1210eda01..27c342800 100644 --- a/src/dialog/Base.mjs +++ b/src/dialog/Base.mjs @@ -258,16 +258,12 @@ class Base extends Panel { * @protected */ afterSetModal(value, oldValue) { - const - me = this, - { cls } = me.vdom; + let me = this; - NeoArray.toggle(cls, 'neo-modal', value); + NeoArray.toggle(me.vdom.cls, 'neo-modal', value); me.update(); - if (me.rendered) { - me.syncModalMask() - } + me.rendered && me.syncModalMask() } /** @@ -428,13 +424,11 @@ class Base extends Panel { * @param {Boolean} animate=!!this.animateTargetId */ async closeOrHide(animate=!!this.animateTargetId) { - const - me = this, - { id } = me; + let me = this; me[me.closeAction](animate); await me.timeout(30); - me.syncModalMask(id) + me.syncModalMask(me.id) } /** @@ -666,7 +660,7 @@ class Base extends Panel { */ syncModalMask(id=this.id) { // This should sync the visibility and position of the modal mask element. - Neo.main.DomAccess.syncModalMask({ id, modal: this.modal }); + Neo.main.DomAccess.syncModalMask({ id, modal: this.modal }) } } From 42c9c37b518e0d2790c29e0bef1c9e07a74ba7b7 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 2 Oct 2023 16:30:45 +0200 Subject: [PATCH 154/162] #4964 examples.dialog.MainContainer: pass changes of the modal checkbox to the dialog instance --- examples/dialog/MainContainer.mjs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/examples/dialog/MainContainer.mjs b/examples/dialog/MainContainer.mjs index 1baf91e31..6df933be6 100644 --- a/examples/dialog/MainContainer.mjs +++ b/examples/dialog/MainContainer.mjs @@ -47,7 +47,7 @@ class MainContainer extends Viewport { checked : true, hideLabel : true, hideValueLabel: false, - listeners : {change: me.onDragLimitChange, scope: me}, + listeners : {change: me.onConfigChange.bind(me, 'boundaryContainerId')}, style : {marginLeft: '3em'}, valueLabelText: 'Limit Drag&Drop to the document.body' }, { @@ -55,6 +55,7 @@ class MainContainer extends Viewport { checked : true, hideLabel : true, hideValueLabel: false, + listeners : {change: me.onConfigChange.bind(me, 'modal')}, style : {marginLeft: '3em'}, valueLabelText: 'Modal' }, '->', { @@ -83,6 +84,16 @@ class MainContainer extends Viewport { }); } + /** + * @param {String} config + * @param {Object} opts + */ + onConfigChange(config, opts) { + if (this.dialog) { + this.dialog[config] = opts.value; + } + } + /** * @param {Object} data */ From 952f5d5a8331da9c1d5267832a45d4ec4eeafa3b Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 2 Oct 2023 16:59:30 +0200 Subject: [PATCH 155/162] examples.dialog.MainContainer: animated checkbox #4965 --- examples/dialog/DemoDialog.mjs | 44 ++++++++++++++++++++++++++----- examples/dialog/MainContainer.mjs | 23 +++++++++++----- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/examples/dialog/DemoDialog.mjs b/examples/dialog/DemoDialog.mjs index 38a57800a..7c6fd9f23 100644 --- a/examples/dialog/DemoDialog.mjs +++ b/examples/dialog/DemoDialog.mjs @@ -12,6 +12,17 @@ class DemoDialog extends Dialog { modal : true, title : 'My Dialog', + /** + * Custom config to dynamically enable / disable the animateTargetId + * @member {Boolean} animated_=true + */ + animated_: true, + /** + * Custom config used by animated_ + * @member {String|null} optionalAnimateTargetId=null + */ + optionalAnimateTargetId: null, + wrapperStyle: { width : '40%' } @@ -52,6 +63,22 @@ class DemoDialog extends Dialog { }] } + /** + * Triggered after the animated config got changed + * @param {Boolean} value + * @param {Boolean} oldValue + * @protected + */ + afterSetAnimated(value, oldValue) { + let me = this; + + me.animateTargetId = value ? me.optionalAnimateTargetId : null; + + if (me.dialog) { + me.dialog.animated = value + } + } + /** * Triggered after the modal config got changed * @param {Boolean} value @@ -70,16 +97,19 @@ class DemoDialog extends Dialog { * @param {Object} data */ createDialog(data) { - let me = this; + let me = this, + button = data.component; - data.component.disabled = true; + button.disabled = true; me.dialog = Neo.create(DemoDialog, { - appName : me.appName, - boundaryContainerId: me.boundaryContainerId, - listeners : {close: me.onWindowClose, scope: me}, - modal : me.app.mainView.down({ valueLabelText : 'Modal' }).checked, - title : 'Second Dialog' + animated : me.animated, + appName : me.appName, + boundaryContainerId : me.boundaryContainerId, + listeners : {close: me.onWindowClose, scope: me}, + modal : me.app.mainView.down({valueLabelText: 'Modal'}).checked, + optionalAnimateTargetId: button.id, + title : 'Second Dialog' }) } diff --git a/examples/dialog/MainContainer.mjs b/examples/dialog/MainContainer.mjs index 6df933be6..86b3fb308 100644 --- a/examples/dialog/MainContainer.mjs +++ b/examples/dialog/MainContainer.mjs @@ -55,8 +55,16 @@ class MainContainer extends Viewport { checked : true, hideLabel : true, hideValueLabel: false, - listeners : {change: me.onConfigChange.bind(me, 'modal')}, + listeners : {change: me.onConfigChange.bind(me, 'animated')}, style : {marginLeft: '3em'}, + valueLabelText: 'Animated' + }, { + module : CheckBox, + checked : true, + hideLabel : true, + hideValueLabel: false, + listeners : {change: me.onConfigChange.bind(me, 'modal')}, + style : {marginLeft: '1em'}, valueLabelText: 'Modal' }, '->', { module : Button, @@ -76,12 +84,13 @@ class MainContainer extends Viewport { data.component.disabled = true; me.dialog = Neo.create(DemoDialog, { - animateTargetId : data.component.id, - appName : me.appName, - boundaryContainerId: me.boundaryContainerId, - listeners : {close: me.onWindowClose, scope: me}, - modal : me.down({ valueLabelText : 'Modal' }).checked - }); + animated : me.down({valueLabelText: 'Animated'}).checked, + appName : me.appName, + boundaryContainerId : me.boundaryContainerId, + listeners : {close: me.onWindowClose, scope: me}, + modal : me.down({valueLabelText: 'Modal'}).checked, + optionalAnimateTargetId: data.component.id + }) } /** From 65d98e3041a6f4935b813d0d8890e6e272131409 Mon Sep 17 00:00:00 2001 From: tobiu Date: Mon, 2 Oct 2023 17:45:45 +0200 Subject: [PATCH 156/162] dialog.Base: close button throws an error, in case there is no animateTargetId #4966 --- src/dialog/Base.mjs | 56 ++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/src/dialog/Base.mjs b/src/dialog/Base.mjs index 27c342800..ce4c0a081 100644 --- a/src/dialog/Base.mjs +++ b/src/dialog/Base.mjs @@ -153,20 +153,6 @@ class Base extends Panel { } } - init() { - super.init(); - - let me = this; - - if (me.animateTargetId) { - me.autoShow && me.show() - } else { - me.timeout(100).then(() => { - me.syncModalMask() - }) - } - } - /** * Triggered after the animateTargetId config got changed * @param {String|null} value @@ -248,7 +234,7 @@ class Base extends Panel { cls = me.vdom.cls; // todo: using wrapperCls NeoArray.toggle(cls, 'neo-maximized', value); - me.update(); + me.update() } /** @@ -342,10 +328,12 @@ class Base extends Panel { me.closeOrHide(false); - await Neo.applyDeltas(appName, [ - {id, cls: {remove: ['animated-hiding-showing']}}, - {id, action: 'removeNode'} - ]) + if (me.closeAction === 'hide') { + await Neo.applyDeltas(appName, [ + {id, cls: {remove: ['animated-hiding-showing']}}, + {id, action: 'removeNode'} + ]) + } } /** @@ -380,7 +368,7 @@ class Base extends Panel { add: ['animated-hiding-showing'] }, style: { - height : style?.height || '', + height : style?.height || null, // height will point to the animation origin, so we need a reset left : style?.left || '50%', top : style?.top || '50%', transform: style?.transform || 'translate(-50%, -50%)', @@ -431,6 +419,15 @@ class Base extends Panel { me.syncModalMask(me.id) } + /** + * Action when clicking the X button inside the header toolbar. + * @param {Object} data + * @protected + */ + closeOrHideAction(data) { + this.closeOrHide() + } + /** * */ @@ -465,7 +462,7 @@ class Base extends Panel { let me = this, map = { - close : me.closeOrHide, + close : me.closeOrHideAction, maximize: me.maximize }; @@ -518,6 +515,23 @@ class Base extends Panel { me.syncModalMask() } + /** + * + */ + init() { + super.init(); + + let me = this; + + if (me.animateTargetId) { + me.autoShow && me.show() + } else { + me.timeout(100).then(() => { + me.syncModalMask() + }) + } + } + /** * @param {Object} [data] */ From 4b53ba0968ab4a0810f8c9dabdcaa41e751bd1aa Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 3 Oct 2023 09:46:26 +0200 Subject: [PATCH 157/162] examples.dialog.DemoDialog: polishing --- examples/dialog/DemoDialog.mjs | 50 +++++++++++++++++++++++-------- examples/dialog/MainContainer.mjs | 27 ++++++----------- 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/examples/dialog/DemoDialog.mjs b/examples/dialog/DemoDialog.mjs index 7c6fd9f23..3bfdace05 100644 --- a/examples/dialog/DemoDialog.mjs +++ b/examples/dialog/DemoDialog.mjs @@ -8,21 +8,41 @@ import SelectField from '../../src/form/field/Select.mjs'; */ class DemoDialog extends Dialog { static config = { - className: 'Neo.examples.dialog.DemoWindow', - modal : true, - title : 'My Dialog', - + /** + * @member {String} className='Neo.examples.dialog.DemoDialog' + * @protected + */ + className: 'Neo.examples.dialog.DemoDialog', /** * Custom config to dynamically enable / disable the animateTargetId * @member {Boolean} animated_=true */ animated_: true, + /** + * @member {Object} containerConfig + */ + containerConfig: { + style: { + padding: '1em' + } + }, + /** + * Custom config to show the current dialog number + * @member {Number} index=1 + */ + index: 1, + /** + * @member {Boolean} modal=true + */ + modal: true, /** * Custom config used by animated_ * @member {String|null} optionalAnimateTargetId=null */ optionalAnimateTargetId: null, - + /** + * @member {Object} wrapperStyle + */ wrapperStyle: { width : '40%' } @@ -37,8 +57,9 @@ class DemoDialog extends Dialog { const me = this; me.items = [{ - module : SelectField, - labelText: 'Select', + module : SelectField, + labelText : 'Select', + labelWidth: 80, store: { data: (() => { @@ -58,8 +79,9 @@ class DemoDialog extends Dialog { module : Button, handler : me.createDialog.bind(me), iconCls : 'fa fa-window-maximize', - reference: 'create-second-dialog-button', - text : 'Create new modal Dialog', + reference: 'create-dialog-button', + style : {marginTop: '3em'}, + text : 'Create Dialog ' + (me.index + 1), }] } @@ -97,8 +119,9 @@ class DemoDialog extends Dialog { * @param {Object} data */ createDialog(data) { - let me = this, - button = data.component; + let me = this, + button = data.component, + nextIndex = me.index + 1; button.disabled = true; @@ -106,10 +129,11 @@ class DemoDialog extends Dialog { animated : me.animated, appName : me.appName, boundaryContainerId : me.boundaryContainerId, + index : nextIndex, listeners : {close: me.onWindowClose, scope: me}, modal : me.app.mainView.down({valueLabelText: 'Modal'}).checked, optionalAnimateTargetId: button.id, - title : 'Second Dialog' + title : 'Dialog' + nextIndex }) } @@ -117,7 +141,7 @@ class DemoDialog extends Dialog { * */ onWindowClose() { - this.getReference('create-second-dialog-button').disabled = false + this.getReference('create-dialog-button').disabled = false } } diff --git a/examples/dialog/MainContainer.mjs b/examples/dialog/MainContainer.mjs index 86b3fb308..2aab843bc 100644 --- a/examples/dialog/MainContainer.mjs +++ b/examples/dialog/MainContainer.mjs @@ -38,10 +38,11 @@ class MainContainer extends Viewport { me.items = [{ module: Toolbar, items :[{ - module : Button, - handler: me.createDialog.bind(me), - iconCls: 'fa fa-window-maximize', - text : 'Create Dialog', + module : Button, + handler : me.createDialog.bind(me), + iconCls : 'fa fa-window-maximize', + reference: 'create-dialog-button', + text : 'Create Dialog', }, { module : CheckBox, checked : true, @@ -89,7 +90,8 @@ class MainContainer extends Viewport { boundaryContainerId : me.boundaryContainerId, listeners : {close: me.onWindowClose, scope: me}, modal : me.down({valueLabelText: 'Modal'}).checked, - optionalAnimateTargetId: data.component.id + optionalAnimateTargetId: data.component.id, + title : 'Dialog 1' }) } @@ -99,26 +101,15 @@ class MainContainer extends Viewport { */ onConfigChange(config, opts) { if (this.dialog) { - this.dialog[config] = opts.value; + this.dialog[config] = opts.value ? 'document.body' : null } } - /** - * @param {Object} data - */ - onDragLimitChange(data) { - this.dialog.boundaryContainerId = data.value ? 'document.body' : null - } - /** * */ onWindowClose() { - let button = this.down({ - text: 'Create Dialog' - }); - - button.disabled = false; + this.getReference('create-dialog-button').disabled = false } /** From 51a7129572375d5b42e57f39371fdf30ea5c1b9e Mon Sep 17 00:00:00 2001 From: tobiu Date: Tue, 3 Oct 2023 09:51:52 +0200 Subject: [PATCH 158/162] covid tables: replacing the deprecated getView() with view --- apps/covid/view/country/Table.mjs | 4 ++-- apps/sharedcovid/view/country/Table.mjs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/covid/view/country/Table.mjs b/apps/covid/view/country/Table.mjs index 493e66fa4..d24a1ca32 100644 --- a/apps/covid/view/country/Table.mjs +++ b/apps/covid/view/country/Table.mjs @@ -121,7 +121,7 @@ class Table extends Container { if (oldValue !== undefined) { let me = this, selectionModel = me.selectionModel, - view = me.getView(), + view = me.view, id; if (view) { @@ -158,7 +158,7 @@ class Table extends Container { if (me.store.getCount() > 0) { if (item) { - item = me.getView().getRecordByRowId(item)?.country; + item = me.view.getRecordByRowId(item)?.country; } // in case getRecordByRowId() has no match, the initial row creation will include the selection diff --git a/apps/sharedcovid/view/country/Table.mjs b/apps/sharedcovid/view/country/Table.mjs index 149d40405..1d2408093 100644 --- a/apps/sharedcovid/view/country/Table.mjs +++ b/apps/sharedcovid/view/country/Table.mjs @@ -124,7 +124,7 @@ class Table extends Container { id; if (value) { - id = `${me.getView().id}__tr__${value}`; // the store can not be loaded on the first selection + id = `${me.view.id}__tr__${value}`; // the store can not be loaded on the first selection if (!selectionModel.isSelected(id)) { selectionModel.select(id); @@ -155,7 +155,7 @@ class Table extends Container { if (me.store.getCount() > 0) { if (item) { - item = me.getView().getRecordByRowId(item)?.country; + item = me.view.getRecordByRowId(item)?.country; } // in case getRecordByRowId() has no match, the initial row creation will include the selection From 110a92434f1e03338de215c7effa943f022534ff Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 4 Oct 2023 11:38:14 +0200 Subject: [PATCH 159/162] dialog.Base: dragproxy regression issue #4967 --- examples/dialog/DemoDialog.mjs | 2 +- resources/scss/src/dialog/Base.scss | 6 ++++++ .../scss/src/draggable/DragProxyComponent.scss | 16 ++++++++-------- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/examples/dialog/DemoDialog.mjs b/examples/dialog/DemoDialog.mjs index 3bfdace05..8cbb28cdd 100644 --- a/examples/dialog/DemoDialog.mjs +++ b/examples/dialog/DemoDialog.mjs @@ -133,7 +133,7 @@ class DemoDialog extends Dialog { listeners : {close: me.onWindowClose, scope: me}, modal : me.app.mainView.down({valueLabelText: 'Modal'}).checked, optionalAnimateTargetId: button.id, - title : 'Dialog' + nextIndex + title : 'Dialog ' + nextIndex }) } diff --git a/resources/scss/src/dialog/Base.scss b/resources/scss/src/dialog/Base.scss index 0329ab9c0..1b02edf8b 100644 --- a/resources/scss/src/dialog/Base.scss +++ b/resources/scss/src/dialog/Base.scss @@ -39,6 +39,12 @@ transition-timing-function: ease-out; } + &.neo-dragproxy { + > * { + height: fit-content; + } + } + &.neo-maximized { height : 98% !important; left : 1% !important; diff --git a/resources/scss/src/draggable/DragProxyComponent.scss b/resources/scss/src/draggable/DragProxyComponent.scss index 84c133c0d..48ba54935 100644 --- a/resources/scss/src/draggable/DragProxyComponent.scss +++ b/resources/scss/src/draggable/DragProxyComponent.scss @@ -14,12 +14,12 @@ } > * { - height : 100% !important; - left : 0 !important; - position : relative !important; - opacity : 1 !important; - top : 0 !important; - transform: none !important; - width : 100% !important; + height : 100%; + left : 0; + position : relative; + opacity : 1; + top : 0; + transform: none; + width : 100%; } -} \ No newline at end of file +} From a9d46761b412ba89a56bc363552ba34813de8280 Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 4 Oct 2023 11:51:29 +0200 Subject: [PATCH 160/162] examples.dialog.DemoDialog: display additional dialogs with a random offset #4968 --- examples/dialog/DemoDialog.mjs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/examples/dialog/DemoDialog.mjs b/examples/dialog/DemoDialog.mjs index 8cbb28cdd..72fe6a369 100644 --- a/examples/dialog/DemoDialog.mjs +++ b/examples/dialog/DemoDialog.mjs @@ -133,10 +133,21 @@ class DemoDialog extends Dialog { listeners : {close: me.onWindowClose, scope: me}, modal : me.app.mainView.down({valueLabelText: 'Modal'}).checked, optionalAnimateTargetId: button.id, + style : {left: me.getOffset(), top: me.getOffset()}, title : 'Dialog ' + nextIndex }) } + /** + * We want new dialogs to have a random left & top offset between -100px & 100px, + * to ensure they are not at the exact same position. + * @returns {String} + */ + getOffset() { + let offset = Math.floor(Math.random() * 200 - 100); + return `calc(50% + ${offset}px)` + } + /** * */ From 0b4b7a320207d18db0d4d2912e9ff508052ffe77 Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 4 Oct 2023 13:10:35 +0200 Subject: [PATCH 161/162] worker.App: webpack magic comments #4969 --- src/worker/App.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/worker/App.mjs b/src/worker/App.mjs index 35c5f502b..34d991086 100644 --- a/src/worker/App.mjs +++ b/src/worker/App.mjs @@ -200,8 +200,8 @@ class App extends Base { } return import( - /* webpackInclude: /[\\\/]app.mjs$/ */ - /* webpackExclude: /[\\\/]node_modules/ */ + /* webpackInclude: /(?:\/|\\)app.mjs$/ */ + /* webpackExclude: /(?:\/|\\)node_modules/ */ /* webpackMode: "lazy" */ `../../${path}.mjs` ) From 351c5e465b31d4caaafda5f6fc18408ba6a11bb4 Mon Sep 17 00:00:00 2001 From: tobiu Date: Wed, 4 Oct 2023 13:14:11 +0200 Subject: [PATCH 162/162] v6.7.6 --- apps/ServiceWorker.mjs | 4 ++-- examples/ServiceWorker.mjs | 4 ++-- package.json | 2 +- src/DefaultConfig.mjs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/ServiceWorker.mjs b/apps/ServiceWorker.mjs index 3ae1cbb90..43bf4fa38 100644 --- a/apps/ServiceWorker.mjs +++ b/apps/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.7.5' + * @member {String} version='6.7.6' */ - version: '6.7.5' + version: '6.7.6' } /** diff --git a/examples/ServiceWorker.mjs b/examples/ServiceWorker.mjs index 3ae1cbb90..43bf4fa38 100644 --- a/examples/ServiceWorker.mjs +++ b/examples/ServiceWorker.mjs @@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase { */ singleton: true, /** - * @member {String} version='6.7.5' + * @member {String} version='6.7.6' */ - version: '6.7.5' + version: '6.7.6' } /** diff --git a/package.json b/package.json index 0dabdab33..c13473bf9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neo.mjs", - "version": "6.7.5", + "version": "6.7.6", "description": "The webworkers driven UI framework", "type": "module", "repository": { diff --git a/src/DefaultConfig.mjs b/src/DefaultConfig.mjs index a23298d48..48d0cafb9 100644 --- a/src/DefaultConfig.mjs +++ b/src/DefaultConfig.mjs @@ -236,12 +236,12 @@ const DefaultConfig = { useVdomWorker: true, /** * buildScripts/injectPackageVersion.mjs will update this value - * @default '6.7.5' + * @default '6.7.6' * @memberOf! module:Neo * @name config.version * @type String */ - version: '6.7.5' + version: '6.7.6' }; Object.assign(DefaultConfig, {