neo.mjs 6.3.10 → 6.4.0

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.
Files changed (39) hide show
  1. package/apps/ServiceWorker.mjs +2 -2
  2. package/examples/ConfigurationViewport.mjs +21 -9
  3. package/examples/ServiceWorker.mjs +2 -2
  4. package/examples/container/dialog/MainContainerController.mjs +20 -16
  5. package/examples/dialog/DemoDialog.mjs +24 -3
  6. package/examples/form/field/select/MainContainer.mjs +28 -6
  7. package/package.json +1 -1
  8. package/resources/scss/src/component/Base.scss +26 -0
  9. package/resources/scss/src/form/field/Picker.scss +0 -3
  10. package/resources/scss/src/menu/List.scss +4 -8
  11. package/resources/scss/theme-dark/menu/List.scss +2 -1
  12. package/resources/scss/theme-light/menu/List.scss +1 -0
  13. package/src/DefaultConfig.mjs +2 -2
  14. package/src/button/Base.mjs +41 -6
  15. package/src/component/Base.mjs +236 -44
  16. package/src/container/Dialog.mjs +3 -3
  17. package/src/core/Base.mjs +20 -0
  18. package/src/form/Container.mjs +2 -0
  19. package/src/form/field/Picker.mjs +30 -47
  20. package/src/form/field/Time.mjs +8 -8
  21. package/src/form/field/trigger/Base.mjs +1 -1
  22. package/src/form/field/trigger/CopyToClipboard.mjs +5 -1
  23. package/src/grid/View.mjs +5 -2
  24. package/src/grid/header/Button.mjs +10 -10
  25. package/src/list/Base.mjs +17 -14
  26. package/src/list/plugin/Animate.mjs +3 -3
  27. package/src/main/DomAccess.mjs +272 -28
  28. package/src/menu/List.mjs +35 -102
  29. package/src/table/Container.mjs +2 -2
  30. package/src/table/View.mjs +1 -0
  31. package/src/table/header/Button.mjs +21 -23
  32. package/src/tree/Accordion.mjs +1 -1
  33. package/src/util/Array.mjs +4 -18
  34. package/src/util/Css.mjs +6 -8
  35. package/src/util/HashHistory.mjs +10 -3
  36. package/src/util/Rectangle.mjs +444 -7
  37. package/test/siesta/siesta-node.js +2 -1
  38. package/test/siesta/siesta.js +1 -0
  39. package/test/siesta/tests/Rectangle.mjs +409 -0
@@ -6,11 +6,16 @@ import KeyNavigation from '../util/KeyNavigation.mjs';
6
6
  import Logger from '../util/Logger.mjs';
7
7
  import NeoArray from '../util/Array.mjs';
8
8
  import Observable from '../core/Observable.mjs';
9
+ import Rectangle from '../util/Rectangle.mjs';
9
10
  import Style from '../util/Style.mjs';
10
11
  import Util from '../core/Util.mjs';
11
12
  import VDomUtil from '../util/VDom.mjs';
12
13
  import VNodeUtil from '../util/VNode.mjs';
13
14
 
15
+ const
16
+ lengthRE = /^\d+\w+$/,
17
+ addUnits = value => value == null ? value : isNaN(value) ? value : `${value}px`;
18
+
14
19
  /**
15
20
  * @class Neo.component.Base
16
21
  * @extends Neo.core.Base
@@ -41,6 +46,14 @@ class Base extends CoreBase {
41
46
  * @protected
42
47
  */
43
48
  ntype: 'component',
49
+ /**
50
+ * The default alignment specification to position this Component relative to some other
51
+ * Component, or Element or Rectangle.
52
+ */
53
+ align_ : {
54
+ edgeAlign : 't-b',
55
+ constrainTo : 'document.body'
56
+ },
44
57
  /**
45
58
  * The name of the App this component belongs to
46
59
  * @member {String|null} appName_=null
@@ -139,6 +152,11 @@ class Base extends CoreBase {
139
152
  * @member {Object} dropZoneConfig=null
140
153
  */
141
154
  dropZoneConfig: null,
155
+ /**
156
+ * True to render this component into the viewport outside of the document flow
157
+ * @member {Boolean} floating
158
+ */
159
+ floating: false,
142
160
  /**
143
161
  * Internal flag which will get set to true on mount
144
162
  * @member {Boolean} hasBeenMounted=false
@@ -269,6 +287,12 @@ class Base extends CoreBase {
269
287
  * @member {Object} style_=null
270
288
  */
271
289
  style_: null,
290
+ /**
291
+ * You can pass an used theme directly to any component,
292
+ * to style specific component trees differently from your main view.
293
+ * @member {String|null} theme_=null
294
+ */
295
+ theme_: null,
272
296
  /**
273
297
  * Add tooltip config objects
274
298
  * See tooltip/Base.mjs
@@ -276,8 +300,7 @@ class Base extends CoreBase {
276
300
  */
277
301
  tooltips_: null,
278
302
  /**
279
- * Add 'primary' and other attributes to make it
280
- * an outstanding design
303
+ * Add 'primary' and other attributes to make it an outstanding design
281
304
  * @member {String|null} ui_=null
282
305
  */
283
306
  ui_: null,
@@ -318,6 +341,31 @@ class Base extends CoreBase {
318
341
  */
319
342
  resolveUpdateCache = []
320
343
 
344
+ /**
345
+ * Convenience method to access the App this component belongs to
346
+ * @returns {Neo.controller.Application|null}
347
+ */
348
+ get app() {
349
+ return Neo.apps[this.appName] || null
350
+ }
351
+
352
+ /**
353
+ * Convenience method
354
+ * @returns {Boolean}
355
+ */
356
+ get isVdomUpdating() {
357
+ // The VDOM is being updated if we have the promise that executeVdomUpdate uses
358
+ return Boolean(this.vdomUpdate);
359
+ }
360
+ // Allow the Component to be set to the isVdomUpdating state
361
+ set isVdomUpdating(isVdomUpdating) {
362
+ isVdomUpdating = Boolean(isVdomUpdating);
363
+
364
+ if (isVdomUpdating !== this.isVdomUpdating) {
365
+ this.vdomUpdate = isVdomUpdating;
366
+ }
367
+ }
368
+
321
369
  /**
322
370
  * Apply component based listeners
323
371
  * @member {Object} listeners={}
@@ -507,6 +555,21 @@ class Base extends CoreBase {
507
555
  }
508
556
  }
509
557
 
558
+ /**
559
+ * Triggered after the flex config got changed
560
+ * @param {Number|String|null} value
561
+ * @param {Number|String|null} oldValue
562
+ * @protected
563
+ */
564
+ afterSetFlex(value, oldValue) {
565
+ if (!isNaN(value)) {
566
+ value = `${value} ${value} 0%`
567
+ }
568
+
569
+ this.configuredFlex = value;
570
+ this.changeVdomRootKey('flex', value)
571
+ }
572
+
510
573
  /**
511
574
  * Triggered after the hasUnmountedVdomChanges config got changed
512
575
  * @param {Boolean} value
@@ -537,6 +600,7 @@ class Base extends CoreBase {
537
600
  * @protected
538
601
  */
539
602
  afterSetHeight(value, oldValue) {
603
+ this.configuredHeight = addUnits(value);
540
604
  this.changeVdomRootKey('height', value)
541
605
  }
542
606
 
@@ -587,6 +651,7 @@ class Base extends CoreBase {
587
651
  * @protected
588
652
  */
589
653
  afterSetMaxHeight(value, oldValue) {
654
+ this.configuredMaxHeight = addUnits(value);
590
655
  this.changeVdomRootKey('maxHeight', value)
591
656
  }
592
657
 
@@ -597,6 +662,7 @@ class Base extends CoreBase {
597
662
  * @protected
598
663
  */
599
664
  afterSetMaxWidth(value, oldValue) {
665
+ this.configuredMaxWidth = addUnits(value);
600
666
  this.changeVdomRootKey('maxWidth', value)
601
667
  }
602
668
 
@@ -607,6 +673,7 @@ class Base extends CoreBase {
607
673
  * @protected
608
674
  */
609
675
  afterSetMinHeight(value, oldValue) {
676
+ this.configuredMinHeight = addUnits(value);
610
677
  this.changeVdomRootKey('minHeight', value)
611
678
  }
612
679
 
@@ -617,6 +684,7 @@ class Base extends CoreBase {
617
684
  * @protected
618
685
  */
619
686
  afterSetMinWidth(value, oldValue) {
687
+ this.configuredMinWidth = addUnits(value);
620
688
  this.changeVdomRootKey('minWidth', value)
621
689
  }
622
690
 
@@ -642,6 +710,10 @@ class Base extends CoreBase {
642
710
 
643
711
  me.doResolveUpdateCache();
644
712
 
713
+ if (me.floating) {
714
+ me.alignTo();
715
+ }
716
+
645
717
  me.fire('mounted', me.id)
646
718
  }
647
719
  }
@@ -669,6 +741,23 @@ class Base extends CoreBase {
669
741
  }
670
742
  }
671
743
 
744
+ /**
745
+ * Triggered after the theme config got changed
746
+ * @param {String|null} value
747
+ * @param {String|null} oldValue
748
+ * @protected
749
+ */
750
+ afterSetTheme(value, oldValue) {
751
+ if (value || oldValue !== undefined) {
752
+ let cls = this.cls;
753
+
754
+ NeoArray.remove(cls, oldValue);
755
+ value && NeoArray.add(cls, value);
756
+
757
+ this.cls = cls
758
+ }
759
+ }
760
+
672
761
  /**
673
762
  * Triggered after the tooltips config got changed
674
763
  * @param {Boolean} value
@@ -710,7 +799,7 @@ class Base extends CoreBase {
710
799
  }
711
800
 
712
801
  /**
713
- * Triggered after the vdom config got changed
802
+ * Triggered after the vdom pseudo-config got changed
714
803
  * @param {Object} value
715
804
  * @param {Object|null} oldValue
716
805
  * @protected
@@ -736,6 +825,7 @@ class Base extends CoreBase {
736
825
  * @protected
737
826
  */
738
827
  afterSetWidth(value, oldValue) {
828
+ this.configuredWidth = addUnits(value);
739
829
  this.changeVdomRootKey('width', value)
740
830
  }
741
831
 
@@ -798,6 +888,28 @@ class Base extends CoreBase {
798
888
  }
799
889
  }
800
890
 
891
+ /**
892
+ * Aligns the top level node inside the main thread
893
+ * @param {Object} spec={}
894
+ * @returns {Promise<void>}
895
+ */
896
+ async alignTo(spec={}) {
897
+ const me = this;
898
+
899
+ await Neo.main.DomAccess.align({
900
+ ...me.align,
901
+ ...spec,
902
+ id : me.id,
903
+ configuredFlex : me.configuredFlex,
904
+ configuredWidth : me.configuredWidth,
905
+ configuredHeight : me.configuredHeight,
906
+ configuredMinWidth : me.configuredMinWidth,
907
+ configuredMinHeight : me.configuredMinHeight,
908
+ configuredMaxWidth : me.configuredMaxWidth,
909
+ configuredMaxHeight : me.configuredMaxHeight
910
+ });
911
+ }
912
+
801
913
  /**
802
914
  * Triggered when accessing the cls config
803
915
  * @param {String[]|null} value
@@ -845,6 +957,23 @@ class Base extends CoreBase {
845
957
  return {...Object.assign(this.vdom.style || {}, value)}
846
958
  }
847
959
 
960
+ /**
961
+ * @param {Object|String} align
962
+ * @returns {Object}
963
+ */
964
+ beforeSetAlign(align) {
965
+ let me = this;
966
+
967
+ // Just a simple 't-b'
968
+ if (typeof align === 'string') {
969
+ align = {
970
+ edgeAlign : align
971
+ };
972
+ }
973
+ // merge the incoming alignment specification into the configured default
974
+ return me.merge(me.merge({}, me.constructor.config.align), align);
975
+ }
976
+
848
977
  /**
849
978
  * Triggered before the cls config gets changed.
850
979
  * @param {String[]} value
@@ -852,7 +981,7 @@ class Base extends CoreBase {
852
981
  * @protected
853
982
  */
854
983
  beforeSetCls(value, oldValue) {
855
- return NeoArray.union(value || [], this.baseCls)
984
+ return NeoArray.union(value || [], this.baseCls, this.getBaseClass());
856
985
  }
857
986
 
858
987
  /**
@@ -976,6 +1105,17 @@ class Base extends CoreBase {
976
1105
  return (Neo.isNumber(oldValue) && oldValue > 0) ? (oldValue - 1) : 0
977
1106
  }
978
1107
 
1108
+ beforeSetStyle(value) {
1109
+ let me = this;
1110
+
1111
+ if (typeof value === 'object') {
1112
+ // merge the incoming style specification into the configured default
1113
+ value = me.merge(me.merge({}, me.constructor.config.style), value)
1114
+ }
1115
+
1116
+ return value
1117
+ }
1118
+
979
1119
  /**
980
1120
  * Changes the value of a vdom object attribute or removes it in case it has no value
981
1121
  * @param {String} key
@@ -1024,7 +1164,7 @@ class Base extends CoreBase {
1024
1164
  }
1025
1165
 
1026
1166
  /**
1027
- * Unregisters this instance from the ComponentManager
1167
+ * Unregister this instance from the ComponentManager
1028
1168
  * @param {Boolean} updateParentVdom=false true to remove the component from the parent vdom => real dom
1029
1169
  * @param {Boolean} silent=false true to update the vdom silently (useful for destroying multiple child items in a row)
1030
1170
  * todo: unregister events
@@ -1097,28 +1237,34 @@ class Base extends CoreBase {
1097
1237
  opts = {vdom, vnode},
1098
1238
  deltas;
1099
1239
 
1100
- me.isVdomUpdating = true;
1240
+ if (Neo.currentWorker.isSharedWorker) {
1241
+ opts.appName = me.appName
1242
+ }
1243
+
1244
+ /**
1245
+ * If a VDOM update is in flight, this is the Promise that will resolve when
1246
+ * the update is completed.
1247
+ * @member {Promise|null} vdomUpdate
1248
+ * @protected
1249
+ */
1250
+ me.vdomUpdate = Neo.vdom.Helper.update(opts);
1101
1251
 
1102
1252
  // we can not set the config directly => it could already be false,
1103
1253
  // and we still want to pass it further into subtrees
1104
1254
  me._needsVdomUpdate = false;
1105
1255
  me.afterSetNeedsVdomUpdate?.(false, true)
1106
1256
 
1107
- if (Neo.currentWorker.isSharedWorker) {
1108
- opts.appName = me.appName
1109
- }
1110
-
1111
- Neo.vdom.Helper.update(opts).catch(err => {
1257
+ me.vdomUpdate.catch(err => {
1258
+ me.vdomUpdate = null;
1112
1259
  console.log('Error attempting to update component dom', err, me);
1113
- me.isVdomUpdating = false;
1114
1260
 
1115
1261
  reject?.()
1116
1262
  }).then(data => {
1263
+ me.vdomUpdate = null;
1117
1264
  // checking if the component got destroyed before the update cycle is done
1118
1265
  if (me.id) {
1119
1266
  // console.log('Component vnode updated', data);
1120
- me.vnode = data.vnode;
1121
- me.isVdomUpdating = false;
1267
+ me.vnode = data.vnode;
1122
1268
 
1123
1269
  deltas = data.deltas;
1124
1270
 
@@ -1130,7 +1276,9 @@ class Base extends CoreBase {
1130
1276
  me.resolveVdomUpdate(resolve)
1131
1277
  }
1132
1278
  }
1133
- })
1279
+ });
1280
+
1281
+ return me.vdomUpdate;
1134
1282
  }
1135
1283
 
1136
1284
  /**
@@ -1146,11 +1294,17 @@ class Base extends CoreBase {
1146
1294
  }
1147
1295
 
1148
1296
  /**
1149
- * Convenience method to access the App this component belongs to
1150
- * @returns {Neo.controller.Application}
1297
+ * Override this method to add dynamic values into this.cls
1298
+ * @returns {String[]}
1151
1299
  */
1152
- getApp() {
1153
- return Neo.apps[this.appName]
1300
+ getBaseClass() {
1301
+ const result = [];
1302
+
1303
+ if (this.floating) {
1304
+ result.push('neo-floating');
1305
+ }
1306
+
1307
+ return result;
1154
1308
  }
1155
1309
 
1156
1310
  /**
@@ -1194,10 +1348,29 @@ class Base extends CoreBase {
1194
1348
  * Convenience shortcut
1195
1349
  * @param {String[]|String} id=this.id
1196
1350
  * @param {String} appName=this.appName
1197
- * @returns {Promise<*>}
1198
- */
1199
- getDomRect(id=this.id, appName=this.appName) {
1200
- return Neo.main.DomAccess.getBoundingClientRect({appName, id})
1351
+ * @returns {Promise<Neo.util.Rectangle>}
1352
+ */
1353
+ async getDomRect(id=this.id, appName=this.appName) {
1354
+ const
1355
+ {
1356
+ x,
1357
+ y,
1358
+ width,
1359
+ height,
1360
+ minWidth,
1361
+ minHeight
1362
+ } = await Neo.main.DomAccess.getBoundingClientRect({appName, id}),
1363
+ result = new Rectangle(x, y, width, height);
1364
+
1365
+ if (minWidth) {
1366
+ result.minWidth = minWidth;
1367
+ }
1368
+
1369
+ if (minHeight) {
1370
+ result.minHeight = minHeight;
1371
+ }
1372
+
1373
+ return result;
1201
1374
  }
1202
1375
 
1203
1376
  /**
@@ -1302,7 +1475,7 @@ class Base extends CoreBase {
1302
1475
  getTheme() {
1303
1476
  let me = this,
1304
1477
  themeMatch = 'neo-theme-',
1305
- app, mainView, parentNodes;
1478
+ mainView, parentNodes;
1306
1479
 
1307
1480
  for (const item of me.cls || []) {
1308
1481
  if (item.startsWith(themeMatch)) {
@@ -1310,8 +1483,7 @@ class Base extends CoreBase {
1310
1483
  }
1311
1484
  }
1312
1485
 
1313
- app = Neo.apps[me.appName];
1314
- mainView = app?.mainView;
1486
+ mainView = me.app?.mainView;
1315
1487
 
1316
1488
  if (mainView) {
1317
1489
  parentNodes = VDomUtil.getParentNodes(mainView.vdom, me.id);
@@ -1445,6 +1617,26 @@ class Base extends CoreBase {
1445
1617
  return false
1446
1618
  }
1447
1619
 
1620
+ /**
1621
+ * @param {Number|String} value
1622
+ * @returns {Promise<number>}
1623
+ */
1624
+ async measure(value) {
1625
+ if (value != null) {
1626
+ if (value.endsWith('px')) {
1627
+ value = parseFloat(value);
1628
+ }
1629
+ else if (lengthRE.test(value)) {
1630
+ value = await Neo.main.DomAccess.measure({ value, id : this.id });
1631
+ }
1632
+ else if (!isNaN(value)) {
1633
+ value = parseFloat(value);
1634
+ }
1635
+ }
1636
+
1637
+ return value
1638
+ }
1639
+
1448
1640
  /**
1449
1641
  * Override this method to change the order configs are applied to this instance.
1450
1642
  * @param {Object} config
@@ -1588,7 +1780,7 @@ class Base extends CoreBase {
1588
1780
  */
1589
1781
  onRender(data, autoMount) {
1590
1782
  let me = this,
1591
- app = Neo.apps[me.appName];
1783
+ app = me.app;
1592
1784
 
1593
1785
  me.rendering = false;
1594
1786
 
@@ -1633,8 +1825,8 @@ class Base extends CoreBase {
1633
1825
 
1634
1826
  /**
1635
1827
  * Promise based vdom update
1636
- * @param {Object} [vdom=this.vdom]
1637
- * @param {Neo.vdom.VNode} [vnode= this.vnode]
1828
+ * @param {Object} vdom=this.vdom
1829
+ * @param {Neo.vdom.VNode} vnode= this.vnode
1638
1830
  * @returns {Promise<any>}
1639
1831
  */
1640
1832
  promiseUpdate(vdom=this.vdom, vnode=this.vnode) {
@@ -1714,10 +1906,10 @@ class Base extends CoreBase {
1714
1906
  * - or the autoMount config is set to true
1715
1907
  * @param {Boolean} [mount] Mount the DOM after the vnode got created
1716
1908
  */
1717
- render(mount) {
1909
+ async render(mount) {
1718
1910
  let me = this,
1719
1911
  autoMount = mount || me.autoMount,
1720
- app = Neo.apps[me.appName],
1912
+ app = me.app,
1721
1913
  useVdomWorker = Neo.config.useVdomWorker;
1722
1914
 
1723
1915
  me.rendering = true;
@@ -1732,22 +1924,22 @@ class Base extends CoreBase {
1732
1924
  delete me.vdom.removeDom;
1733
1925
 
1734
1926
  me._needsVdomUpdate = false;
1735
- me.afterSetNeedsVdomUpdate?.(false, true)
1927
+ me.afterSetNeedsVdomUpdate?.(false, true);
1736
1928
 
1737
- Neo.vdom.Helper.create({
1929
+ const data = await Neo.vdom.Helper.create({
1738
1930
  appName : me.appName,
1739
1931
  autoMount,
1740
1932
  parentId : autoMount ? me.getMountedParentId() : undefined,
1741
1933
  parentIndex: autoMount ? me.getMountedParentIndex() : undefined,
1742
1934
  ...me.vdom
1743
- }).then(data => {
1744
- me.onRender(data, useVdomWorker ? autoMount : false);
1745
- me.isVdomUpdating = false;
1935
+ });
1936
+
1937
+ me.onRender(data, useVdomWorker ? autoMount : false);
1938
+ me.isVdomUpdating = false;
1746
1939
 
1747
- autoMount && !useVdomWorker && me.mount();
1940
+ autoMount && !useVdomWorker && me.mount();
1748
1941
 
1749
- me.resolveVdomUpdate()
1750
- })
1942
+ me.resolveVdomUpdate()
1751
1943
  }
1752
1944
  }
1753
1945
 
@@ -1816,8 +2008,8 @@ class Base extends CoreBase {
1816
2008
  * hideMode: 'removeDom' uses vdom removeDom.
1817
2009
  * hideMode: 'visibility' uses css visibility.
1818
2010
  */
1819
- show() {
1820
- let me = this;
2011
+ show(align) {
2012
+ const me = this;
1821
2013
 
1822
2014
  if (me.hideMode !== 'visibility') {
1823
2015
  delete me.vdom.removeDom;
@@ -1958,8 +2150,8 @@ class Base extends CoreBase {
1958
2150
  /**
1959
2151
  *
1960
2152
  */
1961
- update() {
1962
- this.afterSetVdom(this.vdom, null)
2153
+ async update() {
2154
+ await this.afterSetVdom(this.vdom, null)
1963
2155
  }
1964
2156
 
1965
2157
  /**
@@ -2052,7 +2244,7 @@ class Base extends CoreBase {
2052
2244
  */
2053
2245
  updateVdom(vdom=this.vdom, vnode=this.vnode, resolve, reject) {
2054
2246
  let me = this,
2055
- app = Neo.apps[me.appName],
2247
+ app = me.app,
2056
2248
  mounted = me.mounted,
2057
2249
  listenerId;
2058
2250
 
@@ -114,9 +114,9 @@ class Dialog extends Base {
114
114
  let me = this;
115
115
 
116
116
  Neo.main.addon.Dialog.close({
117
- id: me.id,
118
- appName: me.appName
119
- });
117
+ appName: me.appName,
118
+ id : me.id
119
+ })
120
120
  }
121
121
 
122
122
  /**
package/src/core/Base.mjs CHANGED
@@ -331,6 +331,26 @@ class Base {
331
331
  }
332
332
  }
333
333
 
334
+ /**
335
+ * Merges nested objects
336
+ * @param {Object} dest={}
337
+ * @param {Object} src
338
+ * @returns {Object}
339
+ */
340
+ merge(dest={}, src) {
341
+ for (const key in src) {
342
+ const value = src[key];
343
+
344
+ if (typeof value === 'object') {
345
+ dest[key] = this.merge(dest[key], value);
346
+ }
347
+ else {
348
+ dest[key] = value;
349
+ }
350
+ }
351
+ return dest;
352
+ }
353
+
334
354
  /**
335
355
  * Override this method to change the order configs are applied to this instance.
336
356
  * @param {Object} config
@@ -44,6 +44,8 @@ class Container extends BaseContainer {
44
44
  type = Neo.typeOf(value);
45
45
 
46
46
  if (type === 'Array') {
47
+ assign = false;
48
+
47
49
  value.forEach(item => {
48
50
  if (Neo.typeOf(item) === 'Object') {
49
51
  this.adjustTreeLeaves(item, configName)