@vonage/vivid 3.0.0-next.45 → 3.0.0-next.48

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.
package/menu/index.js ADDED
@@ -0,0 +1,661 @@
1
+ import { P as Popup } from '../shared/index3.js';
2
+ import { F as FoundationElement, D as DOM, _ as __decorate, a as attr, o as observable, b as __metadata, h as html, d as designSystem } from '../shared/index.js';
3
+ import { s as styleInject } from '../shared/style-inject.es.js';
4
+ import '../shared/web.dom-collections.iterator.js';
5
+ import { S as StartEnd } from '../shared/aria-global.js';
6
+ import { a as applyMixins } from '../shared/apply-mixins.js';
7
+ import { a as keyArrowLeft, b as keyArrowRight, k as keySpace, c as keyEnter, d as keyHome, e as keyEnd, f as keyArrowUp, g as keyArrowDown } from '../shared/form-associated.js';
8
+ import { s as slotted } from '../shared/slotted.js';
9
+ import '../shared/index2.js';
10
+ import '../shared/class-names.js';
11
+ import '../button/index.js';
12
+ import '../icon/index.js';
13
+ import '../shared/icon.js';
14
+ import '../shared/export.js';
15
+ import '../shared/iterators.js';
16
+ import '../shared/to-string.js';
17
+ import '../shared/_has.js';
18
+ import '../shared/when.js';
19
+ import '../focus/index.js';
20
+ import '../shared/focus.js';
21
+ import '../shared/affix.js';
22
+ import '../shared/button.js';
23
+ import '../shared/focus2.js';
24
+ import '../shared/ref.js';
25
+ import '../shared/object-keys.js';
26
+ import '../shared/es.object.assign.js';
27
+
28
+ /**
29
+ * A test that ensures that all arguments are HTML Elements
30
+ */
31
+ function isHTMLElement(...args) {
32
+ return args.every((arg) => arg instanceof HTMLElement);
33
+ }
34
+
35
+ /**
36
+ * Expose ltr and rtl strings
37
+ */
38
+ var Direction;
39
+ (function (Direction) {
40
+ Direction["ltr"] = "ltr";
41
+ Direction["rtl"] = "rtl";
42
+ })(Direction || (Direction = {}));
43
+
44
+ /**
45
+ * a method to determine the current localization direction of the view
46
+ * @param rootNode - the HTMLElement to begin the query from, usually "this" when used in a component controller
47
+ * @public
48
+ */
49
+ const getDirection = (rootNode) => {
50
+ const dirNode = rootNode.closest("[dir]");
51
+ return dirNode !== null && dirNode.dir === "rtl" ? Direction.rtl : Direction.ltr;
52
+ };
53
+
54
+ /**
55
+ * Menu items roles.
56
+ * @public
57
+ */
58
+ const MenuItemRole = {
59
+ /**
60
+ * The menu item has a "menuitem" role
61
+ */
62
+ menuitem: "menuitem",
63
+ /**
64
+ * The menu item has a "menuitemcheckbox" role
65
+ */
66
+ menuitemcheckbox: "menuitemcheckbox",
67
+ /**
68
+ * The menu item has a "menuitemradio" role
69
+ */
70
+ menuitemradio: "menuitemradio",
71
+ };
72
+ /**
73
+ * @internal
74
+ */
75
+ const roleForMenuItem = {
76
+ [MenuItemRole.menuitem]: "menuitem",
77
+ [MenuItemRole.menuitemcheckbox]: "menuitemcheckbox",
78
+ [MenuItemRole.menuitemradio]: "menuitemradio",
79
+ };
80
+
81
+ /**
82
+ * A Switch Custom HTML Element.
83
+ * Implements {@link https://www.w3.org/TR/wai-aria-1.1/#menuitem | ARIA menuitem }, {@link https://www.w3.org/TR/wai-aria-1.1/#menuitemcheckbox | ARIA menuitemcheckbox}, or {@link https://www.w3.org/TR/wai-aria-1.1/#menuitemradio | ARIA menuitemradio }.
84
+ *
85
+ * @slot checked-indicator - The checked indicator
86
+ * @slot radio-indicator - The radio indicator
87
+ * @slot start - Content which can be provided before the menu item content
88
+ * @slot end - Content which can be provided after the menu item content
89
+ * @slot - The default slot for menu item content
90
+ * @slot expand-collapse-indicator - The expand/collapse indicator
91
+ * @slot submenu - Used to nest menu's within menu items
92
+ * @csspart input-container - The element representing the visual checked or radio indicator
93
+ * @csspart checkbox - The element wrapping the `menuitemcheckbox` indicator
94
+ * @csspart radio - The element wrapping the `menuitemradio` indicator
95
+ * @csspart content - The element wrapping the menu item content
96
+ * @csspart expand-collapse-glyph-container - The element wrapping the expand collapse element
97
+ * @csspart expand-collapse - The expand/collapse element
98
+ * @csspart submenu-region - The container for the submenu, used for positioning
99
+ * @fires expanded-change - Fires a custom 'expanded-change' event when the expanded state changes
100
+ * @fires change - Fires a custom 'change' event when a non-submenu item with a role of `menuitemcheckbox`, `menuitemradio`, or `menuitem` is invoked
101
+ *
102
+ * @public
103
+ */
104
+ class MenuItem extends FoundationElement {
105
+ constructor() {
106
+ super(...arguments);
107
+ /**
108
+ * The role of the element.
109
+ *
110
+ * @public
111
+ * @remarks
112
+ * HTML Attribute: role
113
+ */
114
+ this.role = MenuItemRole.menuitem;
115
+ /**
116
+ * @internal
117
+ */
118
+ this.hasSubmenu = false;
119
+ /**
120
+ * Track current direction to pass to the anchored region
121
+ *
122
+ * @internal
123
+ */
124
+ this.currentDirection = Direction.ltr;
125
+ this.focusSubmenuOnLoad = false;
126
+ /**
127
+ * @internal
128
+ */
129
+ this.handleMenuItemKeyDown = (e) => {
130
+ if (e.defaultPrevented) {
131
+ return false;
132
+ }
133
+ switch (e.key) {
134
+ case keyEnter:
135
+ case keySpace:
136
+ this.invoke();
137
+ return false;
138
+ case keyArrowRight:
139
+ //open/focus on submenu
140
+ this.expandAndFocus();
141
+ return false;
142
+ case keyArrowLeft:
143
+ //close submenu
144
+ if (this.expanded) {
145
+ this.expanded = false;
146
+ this.focus();
147
+ return false;
148
+ }
149
+ }
150
+ return true;
151
+ };
152
+ /**
153
+ * @internal
154
+ */
155
+ this.handleMenuItemClick = (e) => {
156
+ if (e.defaultPrevented || this.disabled) {
157
+ return false;
158
+ }
159
+ this.invoke();
160
+ return false;
161
+ };
162
+ /**
163
+ * @internal
164
+ */
165
+ this.submenuLoaded = () => {
166
+ if (!this.focusSubmenuOnLoad) {
167
+ return;
168
+ }
169
+ this.focusSubmenuOnLoad = false;
170
+ if (this.hasSubmenu) {
171
+ this.submenu.focus();
172
+ this.setAttribute("tabindex", "-1");
173
+ }
174
+ };
175
+ /**
176
+ * @internal
177
+ */
178
+ this.handleMouseOver = (e) => {
179
+ if (this.disabled || !this.hasSubmenu || this.expanded) {
180
+ return false;
181
+ }
182
+ this.expanded = true;
183
+ return false;
184
+ };
185
+ /**
186
+ * @internal
187
+ */
188
+ this.handleMouseOut = (e) => {
189
+ if (!this.expanded || this.contains(document.activeElement)) {
190
+ return false;
191
+ }
192
+ this.expanded = false;
193
+ return false;
194
+ };
195
+ /**
196
+ * @internal
197
+ */
198
+ this.expandAndFocus = () => {
199
+ if (!this.hasSubmenu) {
200
+ return;
201
+ }
202
+ this.focusSubmenuOnLoad = true;
203
+ this.expanded = true;
204
+ };
205
+ /**
206
+ * @internal
207
+ */
208
+ this.invoke = () => {
209
+ if (this.disabled) {
210
+ return;
211
+ }
212
+ switch (this.role) {
213
+ case MenuItemRole.menuitemcheckbox:
214
+ this.checked = !this.checked;
215
+ break;
216
+ case MenuItemRole.menuitem:
217
+ // update submenu
218
+ this.updateSubmenu();
219
+ if (this.hasSubmenu) {
220
+ this.expandAndFocus();
221
+ }
222
+ else {
223
+ this.$emit("change");
224
+ }
225
+ break;
226
+ case MenuItemRole.menuitemradio:
227
+ if (!this.checked) {
228
+ this.checked = true;
229
+ }
230
+ break;
231
+ }
232
+ };
233
+ /**
234
+ * Gets the submenu element if any
235
+ *
236
+ * @internal
237
+ */
238
+ this.updateSubmenu = () => {
239
+ this.submenu = this.domChildren().find((element) => {
240
+ return element.getAttribute("role") === "menu";
241
+ });
242
+ this.hasSubmenu = this.submenu === undefined ? false : true;
243
+ };
244
+ }
245
+ expandedChanged(oldValue) {
246
+ if (this.$fastController.isConnected) {
247
+ if (this.submenu === undefined) {
248
+ return;
249
+ }
250
+ if (this.expanded === false) {
251
+ this.submenu.collapseExpandedItem();
252
+ }
253
+ else {
254
+ this.currentDirection = getDirection(this);
255
+ }
256
+ this.$emit("expanded-change", this, { bubbles: false });
257
+ }
258
+ }
259
+ checkedChanged(oldValue, newValue) {
260
+ if (this.$fastController.isConnected) {
261
+ this.$emit("change");
262
+ }
263
+ }
264
+ /**
265
+ * @internal
266
+ */
267
+ connectedCallback() {
268
+ super.connectedCallback();
269
+ DOM.queueUpdate(() => {
270
+ this.updateSubmenu();
271
+ });
272
+ if (!this.startColumnCount) {
273
+ this.startColumnCount = 1;
274
+ }
275
+ this.observer = new MutationObserver(this.updateSubmenu);
276
+ }
277
+ /**
278
+ * @internal
279
+ */
280
+ disconnectedCallback() {
281
+ super.disconnectedCallback();
282
+ this.submenu = undefined;
283
+ if (this.observer !== undefined) {
284
+ this.observer.disconnect();
285
+ this.observer = undefined;
286
+ }
287
+ }
288
+ /**
289
+ * get an array of valid DOM children
290
+ */
291
+ domChildren() {
292
+ return Array.from(this.children).filter(child => !child.hasAttribute("hidden"));
293
+ }
294
+ }
295
+ __decorate([
296
+ attr({ mode: "boolean" })
297
+ ], MenuItem.prototype, "disabled", void 0);
298
+ __decorate([
299
+ attr({ mode: "boolean" })
300
+ ], MenuItem.prototype, "expanded", void 0);
301
+ __decorate([
302
+ observable
303
+ ], MenuItem.prototype, "startColumnCount", void 0);
304
+ __decorate([
305
+ attr
306
+ ], MenuItem.prototype, "role", void 0);
307
+ __decorate([
308
+ attr({ mode: "boolean" })
309
+ ], MenuItem.prototype, "checked", void 0);
310
+ __decorate([
311
+ observable
312
+ ], MenuItem.prototype, "submenuRegion", void 0);
313
+ __decorate([
314
+ observable
315
+ ], MenuItem.prototype, "hasSubmenu", void 0);
316
+ __decorate([
317
+ observable
318
+ ], MenuItem.prototype, "currentDirection", void 0);
319
+ __decorate([
320
+ observable
321
+ ], MenuItem.prototype, "submenu", void 0);
322
+ applyMixins(MenuItem, StartEnd);
323
+
324
+ /**
325
+ * A Menu Custom HTML Element.
326
+ * Implements the {@link https://www.w3.org/TR/wai-aria-1.1/#menu | ARIA menu }.
327
+ *
328
+ * @slot - The default slot for the menu items
329
+ *
330
+ * @public
331
+ */
332
+ class Menu$1 extends FoundationElement {
333
+ constructor() {
334
+ super(...arguments);
335
+ this.expandedItem = null;
336
+ /**
337
+ * The index of the focusable element in the items array
338
+ * defaults to -1
339
+ */
340
+ this.focusIndex = -1;
341
+ /**
342
+ * @internal
343
+ */
344
+ this.isNestedMenu = () => {
345
+ return (this.parentElement !== null &&
346
+ isHTMLElement(this.parentElement) &&
347
+ this.parentElement.getAttribute("role") === "menuitem");
348
+ };
349
+ /**
350
+ * if focus is moving out of the menu, reset to a stable initial state
351
+ * @internal
352
+ */
353
+ this.handleFocusOut = (e) => {
354
+ if (!this.contains(e.relatedTarget) && this.menuItems !== undefined) {
355
+ this.collapseExpandedItem();
356
+ // find our first focusable element
357
+ const focusIndex = this.menuItems.findIndex(this.isFocusableElement);
358
+ // set the current focus index's tabindex to -1
359
+ this.menuItems[this.focusIndex].setAttribute("tabindex", "-1");
360
+ // set the first focusable element tabindex to 0
361
+ this.menuItems[focusIndex].setAttribute("tabindex", "0");
362
+ // set the focus index
363
+ this.focusIndex = focusIndex;
364
+ }
365
+ };
366
+ this.handleItemFocus = (e) => {
367
+ const targetItem = e.target;
368
+ if (this.menuItems !== undefined &&
369
+ targetItem !== this.menuItems[this.focusIndex]) {
370
+ this.menuItems[this.focusIndex].setAttribute("tabindex", "-1");
371
+ this.focusIndex = this.menuItems.indexOf(targetItem);
372
+ targetItem.setAttribute("tabindex", "0");
373
+ }
374
+ };
375
+ this.handleExpandedChanged = (e) => {
376
+ if (e.defaultPrevented ||
377
+ e.target === null ||
378
+ this.menuItems === undefined ||
379
+ this.menuItems.indexOf(e.target) < 0) {
380
+ return;
381
+ }
382
+ e.preventDefault();
383
+ const changedItem = e.target;
384
+ // closing an expanded item without opening another
385
+ if (this.expandedItem !== null &&
386
+ changedItem === this.expandedItem &&
387
+ changedItem.expanded === false) {
388
+ this.expandedItem = null;
389
+ return;
390
+ }
391
+ if (changedItem.expanded) {
392
+ if (this.expandedItem !== null && this.expandedItem !== changedItem) {
393
+ this.expandedItem.expanded = false;
394
+ }
395
+ this.menuItems[this.focusIndex].setAttribute("tabindex", "-1");
396
+ this.expandedItem = changedItem;
397
+ this.focusIndex = this.menuItems.indexOf(changedItem);
398
+ changedItem.setAttribute("tabindex", "0");
399
+ }
400
+ };
401
+ this.removeItemListeners = () => {
402
+ if (this.menuItems !== undefined) {
403
+ this.menuItems.forEach((item) => {
404
+ item.removeEventListener("expanded-change", this.handleExpandedChanged);
405
+ item.removeEventListener("focus", this.handleItemFocus);
406
+ });
407
+ }
408
+ };
409
+ this.setItems = () => {
410
+ const newItems = this.domChildren();
411
+ this.removeItemListeners();
412
+ this.menuItems = newItems;
413
+ const menuItems = this.menuItems.filter(this.isMenuItemElement);
414
+ // if our focus index is not -1 we have items
415
+ if (menuItems.length) {
416
+ this.focusIndex = 0;
417
+ }
418
+ function elementIndent(el) {
419
+ const role = el.getAttribute("role");
420
+ const startSlot = el.querySelector("[slot=start]");
421
+ if (role !== MenuItemRole.menuitem && startSlot === null) {
422
+ return 1;
423
+ }
424
+ else if (role === MenuItemRole.menuitem && startSlot !== null) {
425
+ return 1;
426
+ }
427
+ else if (role !== MenuItemRole.menuitem && startSlot !== null) {
428
+ return 2;
429
+ }
430
+ else {
431
+ return 0;
432
+ }
433
+ }
434
+ const indent = menuItems.reduce((accum, current) => {
435
+ const elementValue = elementIndent(current);
436
+ return accum > elementValue ? accum : elementValue;
437
+ }, 0);
438
+ menuItems.forEach((item, index) => {
439
+ item.setAttribute("tabindex", index === 0 ? "0" : "-1");
440
+ item.addEventListener("expanded-change", this.handleExpandedChanged);
441
+ item.addEventListener("focus", this.handleItemFocus);
442
+ if (item instanceof MenuItem) {
443
+ item.startColumnCount = indent;
444
+ }
445
+ });
446
+ };
447
+ /**
448
+ * handle change from child element
449
+ */
450
+ this.changeHandler = (e) => {
451
+ if (this.menuItems === undefined) {
452
+ return;
453
+ }
454
+ const changedMenuItem = e.target;
455
+ const changeItemIndex = this.menuItems.indexOf(changedMenuItem);
456
+ if (changeItemIndex === -1) {
457
+ return;
458
+ }
459
+ if (changedMenuItem.role === "menuitemradio" &&
460
+ changedMenuItem.checked === true) {
461
+ for (let i = changeItemIndex - 1; i >= 0; --i) {
462
+ const item = this.menuItems[i];
463
+ const role = item.getAttribute("role");
464
+ if (role === MenuItemRole.menuitemradio) {
465
+ item.checked = false;
466
+ }
467
+ if (role === "separator") {
468
+ break;
469
+ }
470
+ }
471
+ const maxIndex = this.menuItems.length - 1;
472
+ for (let i = changeItemIndex + 1; i <= maxIndex; ++i) {
473
+ const item = this.menuItems[i];
474
+ const role = item.getAttribute("role");
475
+ if (role === MenuItemRole.menuitemradio) {
476
+ item.checked = false;
477
+ }
478
+ if (role === "separator") {
479
+ break;
480
+ }
481
+ }
482
+ }
483
+ };
484
+ /**
485
+ * check if the item is a menu item
486
+ */
487
+ this.isMenuItemElement = (el) => {
488
+ return (isHTMLElement(el) &&
489
+ Menu$1.focusableElementRoles.hasOwnProperty(el.getAttribute("role")));
490
+ };
491
+ /**
492
+ * check if the item is focusable
493
+ */
494
+ this.isFocusableElement = (el) => {
495
+ return this.isMenuItemElement(el);
496
+ };
497
+ }
498
+ itemsChanged(oldValue, newValue) {
499
+ // only update children after the component is connected and
500
+ // the setItems has run on connectedCallback
501
+ // (menuItems is undefined until then)
502
+ if (this.$fastController.isConnected && this.menuItems !== undefined) {
503
+ this.setItems();
504
+ }
505
+ }
506
+ /**
507
+ * @internal
508
+ */
509
+ connectedCallback() {
510
+ super.connectedCallback();
511
+ DOM.queueUpdate(() => {
512
+ // wait until children have had a chance to
513
+ // connect before setting/checking their props/attributes
514
+ this.setItems();
515
+ });
516
+ this.addEventListener("change", this.changeHandler);
517
+ }
518
+ /**
519
+ * @internal
520
+ */
521
+ disconnectedCallback() {
522
+ super.disconnectedCallback();
523
+ this.removeItemListeners();
524
+ this.menuItems = undefined;
525
+ this.removeEventListener("change", this.changeHandler);
526
+ }
527
+ /**
528
+ * Focuses the first item in the menu.
529
+ *
530
+ * @public
531
+ */
532
+ focus() {
533
+ this.setFocus(0, 1);
534
+ }
535
+ /**
536
+ * Collapses any expanded menu items.
537
+ *
538
+ * @public
539
+ */
540
+ collapseExpandedItem() {
541
+ if (this.expandedItem !== null) {
542
+ this.expandedItem.expanded = false;
543
+ this.expandedItem = null;
544
+ }
545
+ }
546
+ /**
547
+ * @internal
548
+ */
549
+ handleMenuKeyDown(e) {
550
+ if (e.defaultPrevented || this.menuItems === undefined) {
551
+ return;
552
+ }
553
+ switch (e.key) {
554
+ case keyArrowDown:
555
+ // go forward one index
556
+ this.setFocus(this.focusIndex + 1, 1);
557
+ return;
558
+ case keyArrowUp:
559
+ // go back one index
560
+ this.setFocus(this.focusIndex - 1, -1);
561
+ return;
562
+ case keyEnd:
563
+ // set focus on last item
564
+ this.setFocus(this.menuItems.length - 1, -1);
565
+ return;
566
+ case keyHome:
567
+ // set focus on first item
568
+ this.setFocus(0, 1);
569
+ return;
570
+ default:
571
+ // if we are not handling the event, do not prevent default
572
+ return true;
573
+ }
574
+ }
575
+ /**
576
+ * get an array of valid DOM children
577
+ */
578
+ domChildren() {
579
+ return Array.from(this.children).filter(child => !child.hasAttribute("hidden"));
580
+ }
581
+ setFocus(focusIndex, adjustment) {
582
+ if (this.menuItems === undefined) {
583
+ return;
584
+ }
585
+ while (focusIndex >= 0 && focusIndex < this.menuItems.length) {
586
+ const child = this.menuItems[focusIndex];
587
+ if (this.isFocusableElement(child)) {
588
+ // change the previous index to -1
589
+ if (this.focusIndex > -1 &&
590
+ this.menuItems.length >= this.focusIndex - 1) {
591
+ this.menuItems[this.focusIndex].setAttribute("tabindex", "-1");
592
+ }
593
+ // update the focus index
594
+ this.focusIndex = focusIndex;
595
+ // update the tabindex of next focusable element
596
+ child.setAttribute("tabindex", "0");
597
+ // focus the element
598
+ child.focus();
599
+ break;
600
+ }
601
+ focusIndex += adjustment;
602
+ }
603
+ }
604
+ }
605
+ Menu$1.focusableElementRoles = roleForMenuItem;
606
+ __decorate([
607
+ observable
608
+ ], Menu$1.prototype, "items", void 0);
609
+
610
+ var css_248z = ".base {\n padding-block: 8px;\n}";
611
+ styleInject(css_248z);
612
+
613
+ class Menu extends Menu$1 {
614
+ constructor() {
615
+ super(...arguments);
616
+ this.open = false;
617
+ }
618
+
619
+ }
620
+
621
+ __decorate([attr({
622
+ mode: 'boolean'
623
+ }), __metadata("design:type", Object)], Menu.prototype, "open", void 0);
624
+
625
+ __decorate([attr, __metadata("design:type", String)], Menu.prototype, "placement", void 0);
626
+
627
+ __decorate([attr, __metadata("design:type", String)], Menu.prototype, "anchor", void 0);
628
+
629
+ let _ = t => t,
630
+ _t;
631
+ const MenuTemplate = context => {
632
+ const popupTag = context.tagFor(Popup);
633
+ return html(_t || (_t = _`
634
+ <template
635
+ slot="${0}"
636
+ >
637
+ <${0}
638
+ placement=${0}
639
+ open=${0}
640
+ anchor=${0}
641
+ >
642
+ <div
643
+ class="base"
644
+ role="menu"
645
+ @keydown="${0}"
646
+ @focusout="${0}"
647
+ >
648
+ <slot ${0}></slot>
649
+ </div>
650
+ </${0}>
651
+ </template>`), x => x.slot || x.isNestedMenu() ? 'submenu' : void 0, popupTag, x => x.placement, x => x.open, x => x.anchor, (x, c) => x.handleMenuKeyDown(c.event), (x, c) => x.handleFocusOut(c.event), slotted('items'), popupTag);
652
+ };
653
+
654
+ const vividMenu = Menu.compose({
655
+ baseName: 'menu',
656
+ template: MenuTemplate,
657
+ styles: css_248z
658
+ });
659
+ designSystem.register(vividMenu());
660
+
661
+ export { vividMenu };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vonage/vivid",
3
- "version": "3.0.0-next.45",
3
+ "version": "3.0.0-next.48",
4
4
  "type": "module",
5
5
  "module": "./index.esm.js",
6
6
  "main": "./index.js",
@@ -38,12 +38,12 @@
38
38
  "./nav": "./nav",
39
39
  "./nav-item": "./nav-item",
40
40
  "./nav-disclosure": "./nav-disclosure",
41
- "./text": "./text",
42
41
  "./text-field": "./text-field",
43
42
  "./tooltip": "./tooltip",
44
43
  "./checkbox": "./checkbox",
45
44
  "./dialog": "./dialog",
46
- "./divider": "./divider"
45
+ "./divider": "./divider",
46
+ "./menu": "./menu"
47
47
  },
48
48
  "typings": "./index.d.ts",
49
49
  "dependencies": {