neo.mjs 6.7.8 → 6.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -45,7 +45,7 @@ class Picker extends Text {
45
45
  Escape: 'onKeyDownEscape'
46
46
  },
47
47
  /**
48
- * @member {Object|null} picker=null
48
+ * @member {Neo.container.Base|null} picker=null
49
49
  * @protected
50
50
  */
51
51
  picker: null,
@@ -107,7 +107,7 @@ class Picker extends Text {
107
107
  me.addDomListeners({
108
108
  click: me.onInputClick,
109
109
  scope: me
110
- });
110
+ })
111
111
  }
112
112
 
113
113
  /**
@@ -120,7 +120,7 @@ class Picker extends Text {
120
120
  let cls = this.cls;
121
121
 
122
122
  NeoArray.toggle(cls, 'neo-not-editable', !value);
123
- this.cls = cls;
123
+ this.cls = cls
124
124
  }
125
125
 
126
126
  /**
@@ -131,10 +131,10 @@ class Picker extends Text {
131
131
  */
132
132
  afterSetMounted(value, oldValue) {
133
133
  if (value === false && oldValue && this.pickerIsMounted) {
134
- this.picker.hide();
134
+ this.picker.hide()
135
135
  }
136
136
 
137
- super.afterSetMounted(value, oldValue);
137
+ super.afterSetMounted(value, oldValue)
138
138
  }
139
139
 
140
140
  /**
@@ -160,18 +160,19 @@ class Picker extends Text {
160
160
  { pickerWidth } = me,
161
161
  pickerComponent = me.createPickerComponent();
162
162
 
163
- return Neo.create(Container, {
163
+ me.picker = Neo.create(Container, {
164
164
  parentId : 'document.body',
165
165
  floating : true,
166
166
  align : {
167
167
  edgeAlign : pickerWidth ? 't0-b0' : 't-b',
168
- matchSize : pickerWidth ? false : true,
168
+ matchSize : !pickerWidth,
169
169
  axisLock : true,
170
170
  target : me.getInputWrapperId()
171
171
  },
172
172
  appName : me.appName,
173
173
  cls : ['neo-picker-container', 'neo-container'],
174
174
  height : me.pickerHeight,
175
+ hidden : true,
175
176
  id : me.getPickerId(),
176
177
  items : pickerComponent ? [pickerComponent] : [],
177
178
  maxHeight: me.pickerMaxHeight,
@@ -188,16 +189,18 @@ class Picker extends Text {
188
189
  for (item of data.oldPath) {
189
190
  if (item.id === me.id) {
190
191
  insideField = true;
191
- break;
192
+ break
192
193
  }
193
194
  }
194
195
 
195
196
  if (!insideField) {
196
197
  me.hidePicker();
197
- super.onFocusLeave(data);
198
+ super.onFocusLeave(data)
198
199
  }
199
200
  }
200
201
  });
202
+
203
+ return me.picker
201
204
  }
202
205
 
203
206
  /**
@@ -205,7 +208,7 @@ class Picker extends Text {
205
208
  * @returns {Neo.component.Base|null}
206
209
  */
207
210
  createPickerComponent() {
208
- return null;
211
+ return null
209
212
  }
210
213
 
211
214
  /**
@@ -214,12 +217,12 @@ class Picker extends Text {
214
217
  destroy(...args) {
215
218
  let picker = this.picker;
216
219
 
217
- if (this.pickerIsMounted) {
218
- picker?.unmount();
220
+ if (picker?.hidden === false) {
221
+ picker.unmount()
219
222
  }
220
223
 
221
224
  picker?.destroy();
222
- super.destroy(...args);
225
+ super.destroy(...args)
223
226
  }
224
227
 
225
228
  /**
@@ -227,36 +230,22 @@ class Picker extends Text {
227
230
  * @returns {Neo.container.Base}
228
231
  */
229
232
  getPicker() {
230
- let me = this;
231
-
232
- if (!me.picker) {
233
- me.picker = me.createPicker();
234
- }
235
-
236
- return me.picker;
233
+ return this.picker || this.createPicker()
237
234
  }
238
235
 
239
236
  /**
240
237
  * @returns {String}
241
238
  */
242
239
  getPickerId() {
243
- return `${this.id}__picker`;
240
+ return `${this.id}__picker`
244
241
  }
245
242
 
246
243
  /**
247
244
  *
248
245
  */
249
246
  async hidePicker() {
250
- let me = this,
251
- picker = me.getPicker();
252
-
253
- // avoid breaking selection model cls updates
254
- await me.timeout(30);
255
-
256
- if (me.pickerIsMounted) {
257
- picker.unmount();
258
-
259
- me.pickerIsMounted = false;
247
+ if (this.picker) {
248
+ this.picker.hidden = true
260
249
  }
261
250
  }
262
251
 
@@ -269,7 +258,7 @@ class Picker extends Text {
269
258
 
270
259
  let me = this;
271
260
 
272
- me.showPickerOnFocus && !me.pickerIsMounted && me.showPicker();
261
+ me.showPickerOnFocus && me.showPicker()
273
262
  }
274
263
 
275
264
  /**
@@ -284,13 +273,13 @@ class Picker extends Text {
284
273
  for (item of data.oldPath) {
285
274
  if (item.id === me.getPickerId()) {
286
275
  insidePicker = true;
287
- break;
276
+ break
288
277
  }
289
278
  }
290
279
 
291
280
  if (!insidePicker) {
292
281
  me.hidePicker();
293
- super.onFocusLeave(data);
282
+ super.onFocusLeave(data)
294
283
  }
295
284
  }
296
285
 
@@ -308,7 +297,7 @@ class Picker extends Text {
308
297
  * @protected
309
298
  */
310
299
  onKeyDownEnter(data, callback, callbackScope) {
311
- !this.pickerIsMounted && this.showPicker(callback, callbackScope);
300
+ !this.pickerIsMounted && this.showPicker(callback, callbackScope)
312
301
  }
313
302
 
314
303
  /**
@@ -316,7 +305,7 @@ class Picker extends Text {
316
305
  * @protected
317
306
  */
318
307
  onKeyDownEscape(data) {
319
- this.pickerIsMounted && this.hidePicker();
308
+ this.pickerIsMounted && this.hidePicker()
320
309
  }
321
310
 
322
311
  /**
@@ -324,44 +313,23 @@ class Picker extends Text {
324
313
  * @protected
325
314
  */
326
315
  onPickerTriggerClick() {
327
- this.editable && this.togglePicker();
316
+ this.editable && this.togglePicker()
328
317
  }
329
318
 
330
319
  /**
331
- * @param {Function} [callback]
332
- * @param {Object} [callbackScope]
320
+ *
333
321
  */
334
- showPicker(callback, callbackScope) {
335
- let me = this,
336
- picker = me.getPicker(),
337
- listenerId;
338
-
339
- if (!me.pickerIsMounting) {
340
- me.pickerIsMounting = true;
341
-
342
- listenerId = picker.on('mounted', () => {
343
- picker.un('mounted', listenerId);
344
-
345
- me.pickerIsMounting = false;
346
- me.pickerIsMounted = true;
347
- callback?.apply(callbackScope || me);
348
- });
349
-
350
- picker.render(true);
351
- }
322
+ showPicker() {
323
+ let picker = this.getPicker();
324
+ picker.hidden = false
352
325
  }
353
326
 
354
327
  /**
355
328
  *
356
329
  */
357
330
  togglePicker() {
358
- let me = this;
359
-
360
- if (me.pickerIsMounted) {
361
- me.hidePicker();
362
- } else {
363
- me.showPicker();
364
- }
331
+ let picker = this.getPicker();
332
+ picker.hidden = !picker.hidden
365
333
  }
366
334
  }
367
335
 
@@ -4,7 +4,21 @@ import Observable from '../core/Observable.mjs';
4
4
  import Rectangle from '../util/Rectangle.mjs';
5
5
 
6
6
  const
7
- lengthRE = /^\d+\w+$/,
7
+ doPreventDefault = e => e.preventDefault(),
8
+ filterTabbable = e => !e.classList.contains('neo-focus-trap') && isTabbable(e) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP,
9
+ lengthRE = /^\d+\w+$/,
10
+
11
+ focusableTags = {
12
+ BODY : 1,
13
+ BUTTON : 1,
14
+ EMBED : 1,
15
+ IFRAME : 1,
16
+ INPUT : 1,
17
+ OBJECT : 1,
18
+ SELECT : 1,
19
+ TEXTAREA : 1
20
+ },
21
+
8
22
  fontSizeProps = [
9
23
  'font-family',
10
24
  'font-kerning',
@@ -18,7 +32,22 @@ const
18
32
  'text-decoration',
19
33
  'text-transform',
20
34
  'word-break'
21
- ];
35
+ ],
36
+
37
+ isTabbable = e => {
38
+ const
39
+ { nodeName } = e,
40
+ style = getComputedStyle(e);
41
+
42
+ if (style.getPropertyValue('display') === 'none' || style.getPropertyValue('visibility') === 'hidden') {
43
+ return false;
44
+ }
45
+
46
+ return focusableTags[nodeName] ||
47
+ ((nodeName === 'A' || nodeName === 'LINK') && !!e.href) ||
48
+ e.getAttribute('tabIndex') != null ||
49
+ e.contentEditable === 'true';
50
+ };
22
51
 
23
52
  /**
24
53
  * @class Neo.main.DomAccess
@@ -78,6 +107,7 @@ class DomAccess extends Base {
78
107
  'setBodyCls',
79
108
  'setStyle',
80
109
  'syncModalMask',
110
+ 'trapFocus',
81
111
  'windowScrollTo'
82
112
  ]
83
113
  },
@@ -106,6 +136,7 @@ class DomAccess extends Base {
106
136
  if (!me._modalMask) {
107
137
  me._modalMask = document.createElement('div');
108
138
  me._modalMask.className = 'neo-dialog-modal-mask';
139
+ me._modalMask.addEventListener('mousedown', doPreventDefault, { capture : true });
109
140
  }
110
141
 
111
142
  return me._modalMask;
@@ -638,6 +669,28 @@ class DomAccess extends Base {
638
669
  })
639
670
  }
640
671
 
672
+ /**
673
+ * @param data
674
+ * @param data.target
675
+ * @param data.relatedTarget
676
+ */
677
+ onTrappedFocusMovement({ target, relatedTarget }) {
678
+ const backwards = relatedTarget && (target.compareDocumentPosition(relatedTarget) & 4);
679
+
680
+ if (target.matches('.neo-focus-trap')) {
681
+ const
682
+ containingEement = target.parentElement,
683
+ treeWalker = containingEement.$treeWalker,
684
+ topFocusTrap = containingEement.$topFocusTrap,
685
+ bottomFocusTrap = containingEement.$bottomFocusTrap;
686
+
687
+ treeWalker.currentNode = backwards ? bottomFocusTrap : topFocusTrap;
688
+ treeWalker[backwards ? 'previousNode' : 'nextNode']();
689
+
690
+ requestAnimationFrame(() => treeWalker.currentNode.focus());
691
+ }
692
+ }
693
+
641
694
  /**
642
695
  * @param {Object} data
643
696
  * @protected
@@ -871,6 +924,53 @@ class DomAccess extends Base {
871
924
  }
872
925
  }
873
926
 
927
+ /**
928
+ * Traps (or stops trapping) focus within a Component
929
+ * @param {Object} data
930
+ * @param {String} data.id The Component to trap focus within.
931
+ * @param {Boolean} [data.trap=true] Pass `false` to stop trapping focus inside the Component.
932
+ */
933
+ async trapFocus(data) {
934
+ const
935
+ me = this,
936
+ onTrappedFocusMovement = me.$boundOnTrappedFocusMovement || (me.$boundOnTrappedFocusMovement = me.onTrappedFocusMovement.bind(me)),
937
+ subject = data.subject = me.getElement(data.id),
938
+ { trap = true } = data;
939
+
940
+ // Called before DOM has been created.
941
+ if (!subject) {
942
+ return;
943
+ }
944
+
945
+ let topFocusTrap = subject.$topFocusTrap,
946
+ bottomFocusTrap = subject.$bottomFocusTrap;
947
+
948
+ if (trap) {
949
+ if (!subject.$treeWalker) {
950
+ subject.$treeWalker = document.createTreeWalker(subject, NodeFilter.SHOW_ELEMENT, {
951
+ acceptNode : filterTabbable
952
+ });
953
+ topFocusTrap = subject.$topFocusTrap = document.createElement('div');
954
+ bottomFocusTrap = subject.$bottomFocusTrap = document.createElement('div');
955
+
956
+ // The two focus traping elements must be invisble but tabbable.
957
+ topFocusTrap.className = bottomFocusTrap.className = 'neo-focus-trap';
958
+ topFocusTrap.setAttribute('tabIndex', 0);
959
+ bottomFocusTrap.setAttribute('tabIndex', 0);
960
+
961
+ // Listen for when they gain focus and wrap focus within the encapsulating element
962
+ subject.addEventListener('focusin', onTrappedFocusMovement);
963
+ }
964
+
965
+ // Ensure content is encapsulated by the focus trap elements
966
+ subject.insertBefore(topFocusTrap, subject.firstChild);
967
+ subject.appendChild(bottomFocusTrap);
968
+ }
969
+ else {
970
+ subject.removeEventListener('focusin', onTrappedFocusMovement);
971
+ }
972
+ }
973
+
874
974
  /**
875
975
  * @param {Object} data
876
976
  * @param {String} [data.behavior='smooth'] // auto or smooth
@@ -1,128 +0,0 @@
1
- import chalk from 'chalk';
2
- import { spawnSync } from 'child_process';
3
- import { Command } from 'commander/esm.mjs';
4
- import envinfo from 'envinfo';
5
- import fs from 'fs-extra';
6
- import inquirer from 'inquirer';
7
- import os from 'os';
8
- import path from 'path';
9
-
10
- const __dirname = path.resolve(),
11
- cwd = process.cwd(),
12
- cpOpts = {env: process.env, cwd: cwd, stdio: 'inherit', shell: true},
13
- requireJson = path => JSON.parse(fs.readFileSync((path))),
14
- packageJson = requireJson(path.resolve(cwd, 'package.json')),
15
- program = new Command(),
16
- configPath = path.resolve(cwd, 'buildScripts/myApps.json'),
17
- neoPath = packageJson.name === 'neo.mjs' ? './' : './node_modules/neo.mjs/',
18
- webpackPath = path.resolve(neoPath, 'buildScripts/webpack'),
19
- programName = `${packageJson.name} buildMyApps`,
20
- questions = [];
21
-
22
- let webpack = './node_modules/.bin/webpack',
23
- config;
24
-
25
- if (fs.existsSync(configPath)) {
26
- config = requireJson(configPath);
27
- } else {
28
- const myAppsPath = path.resolve(neoPath, 'buildScripts/webpack/json/myApps.json');
29
-
30
- if (fs.existsSync(myAppsPath)) {
31
- config = requireJson(myAppsPath);
32
- } else {
33
- config = requireJson(path.resolve(neoPath, 'buildScripts/webpack/json/myApps.template.json'));
34
- }
35
- }
36
-
37
- let index = config.apps.indexOf('Docs');
38
-
39
- index > -1 && config.apps.splice(index, 1);
40
-
41
- program
42
- .name(programName)
43
- .version(packageJson.version)
44
- .option('-i, --info', 'print environment debug info')
45
- .option('-a, --apps <value>', ['all'].concat(config.apps).map(e => `"${e}"`).join(', '))
46
- .option('-e, --env <value>', '"all", "dev", "prod"')
47
- .option('-f, --framework')
48
- .option('-n, --noquestions')
49
- .allowUnknownOption()
50
- .on('--help', () => {
51
- console.log('\nIn case you have any issues, please create a ticket here:');
52
- console.log(chalk.cyan(packageJson.bugs.url));
53
- })
54
- .parse(process.argv);
55
-
56
- const programOpts = program.opts();
57
-
58
- if (programOpts.info) {
59
- console.log(chalk.bold('\nEnvironment Info:'));
60
- console.log(`\n current version of ${packageJson.name}: ${packageJson.version}`);
61
- console.log(` running from ${__dirname}`);
62
-
63
- envinfo
64
- .run({
65
- System : ['OS', 'CPU'],
66
- Binaries : ['Node', 'npm', 'Yarn'],
67
- Browsers : ['Chrome', 'Edge', 'Firefox', 'Safari'],
68
- npmPackages: ['neo.mjs']
69
- }, {
70
- duplicates : true,
71
- showNotFound: true
72
- })
73
- .then(console.log);
74
- } else {
75
- console.log(chalk.green(programName));
76
-
77
- if (!programOpts.noquestions) {
78
- if (!programOpts.env) {
79
- questions.push({
80
- type : 'list',
81
- name : 'env',
82
- message: 'Please choose the environment:',
83
- choices: ['all', 'dev', 'prod'],
84
- default: 'all'
85
- });
86
- }
87
-
88
- if (!programOpts.apps && config.apps.length > 1) {
89
- questions.push({
90
- type : 'checkbox',
91
- name : 'apps',
92
- message: 'Please choose which apps you want to build:',
93
- choices: config.apps
94
- });
95
- }
96
- }
97
-
98
- inquirer.prompt(questions).then(answers => {
99
- const apps = (answers.apps.length > 0 ? answers.apps : null) || programOpts.apps || ['all'],
100
- env = answers.env || programOpts.env || ['all'],
101
- insideNeo = !!programOpts.framework || false,
102
- startDate = new Date();
103
- let childProcess;
104
-
105
- if (os.platform().startsWith('win')) {
106
- webpack = path.resolve(webpack).replace(/\\/g,'/');
107
- }
108
-
109
- // dist/development
110
- if (env === 'all' || env === 'dev') {
111
- console.log(chalk.blue(`${programName} starting dist/development`));
112
- childProcess = spawnSync(webpack, ['--config', `${webpackPath}/development/webpack.config.myapps.mjs`, `--env apps=${apps}`, `--env insideNeo=${insideNeo}`], cpOpts);
113
- childProcess.status && process.exit(childProcess.status);
114
- }
115
-
116
- // dist/production
117
- if (env === 'all' || env === 'prod') {
118
- console.log(chalk.blue(`${programName} starting dist/production`));
119
- childProcess = spawnSync(webpack, ['--config', `${webpackPath}/production/webpack.config.myapps.mjs`, `--env apps=${apps}`, `--env insideNeo=${insideNeo}`], cpOpts);
120
- childProcess.status && process.exit(childProcess.status);
121
- }
122
-
123
- const processTime = (Math.round((new Date - startDate) * 100) / 100000).toFixed(2);
124
- console.log(`\nTotal time for ${programName}: ${processTime}s`);
125
-
126
- process.exit();
127
- });
128
- }
@@ -1,127 +0,0 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import webpack from 'webpack';
4
-
5
- const cwd = process.cwd(),
6
- configPath = path.resolve(cwd, 'buildScripts/myApps.json'),
7
- requireJson = path => JSON.parse(fs.readFileSync((path))),
8
- packageJson = requireJson(path.resolve(cwd, 'package.json')),
9
- neoPath = packageJson.name === 'neo.mjs' ? './' : './node_modules/neo.mjs/',
10
- buildTarget = requireJson(path.resolve(neoPath, 'buildScripts/webpack/development/buildTarget.json')),
11
- filenameConfig = requireJson(path.resolve(neoPath, 'buildScripts/webpack/json/build.json')),
12
- plugins = [],
13
- regexIndexNodeModules = /node_modules/g,
14
- regexTopLevel = /\.\.\//g;
15
-
16
- let config;
17
-
18
- if (fs.existsSync(configPath)) {
19
- config = requireJson(configPath);
20
- } else {
21
- const myAppsPath = path.resolve(neoPath, 'buildScripts/webpack/json/myApps.json');
22
-
23
- if (fs.existsSync(myAppsPath)) {
24
- config = requireJson(myAppsPath);
25
- } else {
26
- config = requireJson(path.resolve(neoPath, 'buildScripts/webpack/json/myApps.template.json'));
27
- }
28
- }
29
-
30
- let index = config.apps.indexOf('Docs');
31
-
32
- index > -1 && config.apps.splice(index, 1);
33
-
34
- if (!buildTarget.folder) {
35
- buildTarget.folder = 'dist/development';
36
- }
37
-
38
- export default env => {
39
- let apps = env.apps.split(','),
40
- insideNeo = env.insideNeo == 'true',
41
- buildAll = apps.includes('all'),
42
- choices = [],
43
- basePath, content, i, inputPath, outputPath, lAppName, treeLevel, workerBasePath;
44
-
45
- // MicroLoader.mjs
46
- inputPath = path.resolve(cwd, 'src/MicroLoader.mjs');
47
- outputPath = path.resolve(cwd, buildTarget.folder, 'src/MicroLoader.mjs');
48
-
49
- content = fs.readFileSync(inputPath).toString().replace(/\s/gm, '');
50
- fs.mkdirpSync(path.resolve(cwd, buildTarget.folder, 'src/'));
51
- fs.writeFileSync(outputPath, content);
52
-
53
- if (config.apps) {
54
- config.apps.forEach(key => {
55
- choices.push(key);
56
- });
57
-
58
- config.apps.forEach(key => {
59
- if (buildAll || choices.length < 2 || apps.includes(key)) {
60
- basePath = '';
61
- workerBasePath = '';
62
- treeLevel = key.replace('.', '/').split('/').length + 3;
63
-
64
- for (i=0; i < treeLevel; i++) {
65
- basePath += '../';
66
-
67
- if (i > 1) {
68
- workerBasePath += '../';
69
- }
70
- }
71
-
72
- lAppName = key.toLowerCase();
73
-
74
- // neo-config.json
75
- inputPath = path.resolve(cwd, 'apps', lAppName, 'neo-config.json');
76
- outputPath = path.resolve(cwd, buildTarget.folder, 'apps', lAppName, 'neo-config.json');
77
-
78
- content = requireJson(inputPath);
79
-
80
- content.appPath = content.appPath.replace(regexTopLevel, '');
81
-
82
- Object.assign(content, {
83
- basePath,
84
- environment: 'dist/development',
85
- mainPath : '../main.js',
86
- workerBasePath
87
- });
88
-
89
- fs.writeFileSync(outputPath, JSON.stringify(content, null, 4));
90
-
91
- // index.html
92
- inputPath = path.resolve(cwd, 'apps', lAppName, 'index.html');
93
- outputPath = path.resolve(cwd, buildTarget.folder, 'apps', lAppName, 'index.html');
94
-
95
- content = fs.readFileSync(inputPath).toString().replace(regexIndexNodeModules, '../../node_modules');
96
-
97
- fs.writeFileSync(outputPath, content);
98
- }
99
- });
100
- }
101
-
102
- return {
103
- mode: 'development',
104
-
105
- // see: https://webpack.js.org/configuration/devtool/
106
- devtool: 'inline-source-map',
107
- //devtool: 'cheap-module-eval-source-map',
108
-
109
- entry : {app: path.resolve(neoPath, './src/worker/App.mjs')},
110
- target: 'webworker',
111
-
112
- plugins: [
113
- new webpack.ContextReplacementPlugin(/.*/, context => {
114
- if (!insideNeo && context.context.includes('/src/worker')) {
115
- context.request = '../../' + context.request;
116
- }
117
- }),
118
- ...plugins
119
- ],
120
-
121
- output: {
122
- chunkFilename: 'chunks/app/[id].js',
123
- filename : filenameConfig.workers.app.output,
124
- path : path.resolve(cwd, buildTarget.folder)
125
- }
126
- }
127
- };