@ngneers/controls-playwright 0.0.1-next.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 (122) hide show
  1. package/README.md +20 -0
  2. package/dist/components/accordion.d.ts +21 -0
  3. package/dist/components/accordion.d.ts.map +1 -0
  4. package/dist/components/accordion.js +42 -0
  5. package/dist/components/avatar.d.ts +9 -0
  6. package/dist/components/avatar.d.ts.map +1 -0
  7. package/dist/components/avatar.js +16 -0
  8. package/dist/components/breadcrumb.d.ts +20 -0
  9. package/dist/components/breadcrumb.d.ts.map +1 -0
  10. package/dist/components/breadcrumb.js +15 -0
  11. package/dist/components/button-group.d.ts +10 -0
  12. package/dist/components/button-group.d.ts.map +1 -0
  13. package/dist/components/button-group.js +16 -0
  14. package/dist/components/button.d.ts +6 -0
  15. package/dist/components/button.d.ts.map +1 -0
  16. package/dist/components/button.js +6 -0
  17. package/dist/components/calendar.d.ts +101 -0
  18. package/dist/components/calendar.d.ts.map +1 -0
  19. package/dist/components/calendar.js +56 -0
  20. package/dist/components/chip.d.ts +15 -0
  21. package/dist/components/chip.d.ts.map +1 -0
  22. package/dist/components/chip.js +38 -0
  23. package/dist/components/edit-inplace.d.ts +18 -0
  24. package/dist/components/edit-inplace.d.ts.map +1 -0
  25. package/dist/components/edit-inplace.js +29 -0
  26. package/dist/components/filter.d.ts +186 -0
  27. package/dist/components/filter.d.ts.map +1 -0
  28. package/dist/components/filter.js +55 -0
  29. package/dist/components/hint.d.ts +10 -0
  30. package/dist/components/hint.d.ts.map +1 -0
  31. package/dist/components/hint.js +22 -0
  32. package/dist/components/index.d.ts +38 -0
  33. package/dist/components/index.d.ts.map +1 -0
  34. package/dist/components/index.js +37 -0
  35. package/dist/components/inplace.d.ts +12 -0
  36. package/dist/components/inplace.d.ts.map +1 -0
  37. package/dist/components/inplace.js +29 -0
  38. package/dist/components/input-field.d.ts +12 -0
  39. package/dist/components/input-field.d.ts.map +1 -0
  40. package/dist/components/input-field.js +11 -0
  41. package/dist/components/input.d.ts +12 -0
  42. package/dist/components/input.d.ts.map +1 -0
  43. package/dist/components/input.js +25 -0
  44. package/dist/components/item-view.d.ts +18 -0
  45. package/dist/components/item-view.d.ts.map +1 -0
  46. package/dist/components/item-view.js +39 -0
  47. package/dist/components/list-box.d.ts +20 -0
  48. package/dist/components/list-box.d.ts.map +1 -0
  49. package/dist/components/list-box.js +24 -0
  50. package/dist/components/mask-input.d.ts +62 -0
  51. package/dist/components/mask-input.d.ts.map +1 -0
  52. package/dist/components/mask-input.js +109 -0
  53. package/dist/components/menu.d.ts +16 -0
  54. package/dist/components/menu.d.ts.map +1 -0
  55. package/dist/components/menu.js +26 -0
  56. package/dist/components/message.d.ts +10 -0
  57. package/dist/components/message.d.ts.map +1 -0
  58. package/dist/components/message.js +22 -0
  59. package/dist/components/popover.d.ts +12 -0
  60. package/dist/components/popover.d.ts.map +1 -0
  61. package/dist/components/popover.js +33 -0
  62. package/dist/components/progress.d.ts +17 -0
  63. package/dist/components/progress.d.ts.map +1 -0
  64. package/dist/components/progress.js +46 -0
  65. package/dist/components/radio.d.ts +16 -0
  66. package/dist/components/radio.d.ts.map +1 -0
  67. package/dist/components/radio.js +35 -0
  68. package/dist/components/scroller.d.ts +20 -0
  69. package/dist/components/scroller.d.ts.map +1 -0
  70. package/dist/components/scroller.js +58 -0
  71. package/dist/components/select-button.d.ts +30 -0
  72. package/dist/components/select-button.d.ts.map +1 -0
  73. package/dist/components/select-button.js +46 -0
  74. package/dist/components/select.d.ts +46 -0
  75. package/dist/components/select.d.ts.map +1 -0
  76. package/dist/components/select.js +74 -0
  77. package/dist/components/slider.d.ts +26 -0
  78. package/dist/components/slider.d.ts.map +1 -0
  79. package/dist/components/slider.js +73 -0
  80. package/dist/components/snackbar.d.ts +27 -0
  81. package/dist/components/snackbar.d.ts.map +1 -0
  82. package/dist/components/snackbar.js +70 -0
  83. package/dist/components/spinner.d.ts +11 -0
  84. package/dist/components/spinner.d.ts.map +1 -0
  85. package/dist/components/spinner.js +22 -0
  86. package/dist/components/state.d.ts +31 -0
  87. package/dist/components/state.d.ts.map +1 -0
  88. package/dist/components/state.js +31 -0
  89. package/dist/components/switch.d.ts +12 -0
  90. package/dist/components/switch.d.ts.map +1 -0
  91. package/dist/components/switch.js +39 -0
  92. package/dist/components/tabs.d.ts +22 -0
  93. package/dist/components/tabs.d.ts.map +1 -0
  94. package/dist/components/tabs.js +42 -0
  95. package/dist/components/tag.d.ts +10 -0
  96. package/dist/components/tag.d.ts.map +1 -0
  97. package/dist/components/tag.js +22 -0
  98. package/dist/components/toast.d.ts +27 -0
  99. package/dist/components/toast.d.ts.map +1 -0
  100. package/dist/components/toast.js +68 -0
  101. package/dist/components/toggle-button.d.ts +10 -0
  102. package/dist/components/toggle-button.d.ts.map +1 -0
  103. package/dist/components/toggle-button.js +29 -0
  104. package/dist/components/tooltip.d.ts +12 -0
  105. package/dist/components/tooltip.d.ts.map +1 -0
  106. package/dist/components/tooltip.js +32 -0
  107. package/dist/components/tree.d.ts +47 -0
  108. package/dist/components/tree.d.ts.map +1 -0
  109. package/dist/components/tree.js +85 -0
  110. package/dist/components/upload.d.ts +27 -0
  111. package/dist/components/upload.d.ts.map +1 -0
  112. package/dist/components/upload.js +47 -0
  113. package/dist/index.d.ts +2 -0
  114. package/dist/index.d.ts.map +1 -0
  115. package/dist/index.js +1 -0
  116. package/dist/utils/classes.d.ts +119 -0
  117. package/dist/utils/classes.d.ts.map +1 -0
  118. package/dist/utils/classes.js +31 -0
  119. package/dist/utils/theme.d.ts +3 -0
  120. package/dist/utils/theme.d.ts.map +1 -0
  121. package/dist/utils/theme.js +41 -0
  122. package/package.json +37 -0
@@ -0,0 +1,68 @@
1
+ import { toastControlTemplate } from '@ngneers/controls-themes/templates/toast';
2
+ import { themeClasses } from '../utils/theme';
3
+ import test, { expect } from '@playwright/test';
4
+ export class NgnToastHarness {
5
+ classes = themeClasses(toastControlTemplate);
6
+ locator;
7
+ header;
8
+ content;
9
+ icon;
10
+ closeButton;
11
+ constructor(locator) {
12
+ this.locator = locator;
13
+ this.header = locator.locator(this.classes.defaultHeader);
14
+ this.content = locator.locator(this.classes.defaultContent);
15
+ this.icon = this.header.locator('ngn-icon').first();
16
+ this.closeButton = this.header.locator('button');
17
+ }
18
+ async expectVisible() {
19
+ await expect(this.locator).toBeVisible();
20
+ }
21
+ async expectHidden() {
22
+ await expect(this.locator).not.toBeVisible();
23
+ }
24
+ async expectHeader(text) {
25
+ await expect(this.header).toContainText(text);
26
+ }
27
+ async expectContent(text) {
28
+ await expect(this.content).toContainText(text);
29
+ }
30
+ async expectIcon(hasIcon) {
31
+ if (hasIcon) {
32
+ await expect(this.icon).toBeVisible();
33
+ }
34
+ else {
35
+ await expect(this.icon).not.toBeVisible();
36
+ }
37
+ }
38
+ async expectClosable(closable) {
39
+ if (closable) {
40
+ await expect(this.closeButton).toBeVisible();
41
+ }
42
+ else {
43
+ await expect(this.closeButton).not.toBeVisible();
44
+ }
45
+ }
46
+ async close() {
47
+ await this.closeButton.click();
48
+ }
49
+ }
50
+ export class NgnToastHostHarness {
51
+ classes = themeClasses(toastControlTemplate);
52
+ locator;
53
+ constructor(page) {
54
+ this.locator = page.locator('ngn-toast-host');
55
+ }
56
+ getToast(index = 0) {
57
+ return new NgnToastHarness(this.locator.locator('ngn-toast').nth(index));
58
+ }
59
+ getAllToasts() {
60
+ return this.locator.locator('ngn-toast');
61
+ }
62
+ async expectToastCount(count) {
63
+ await expect(this.getAllToasts()).toHaveCount(count);
64
+ }
65
+ async expectVisible() {
66
+ await expect(this.locator).toBeVisible();
67
+ }
68
+ }
@@ -0,0 +1,10 @@
1
+ import { type Locator } from '@playwright/test';
2
+ export declare class NgnToggleButtonHarness {
3
+ readonly classes: import("@ngneers/controls-themes").ThemeClasses<import("@ngneers/controls-themes").ControlTemplate<"toggle-button", ["root", "invalid", "button", "label", "active", "placeholder", "placeholder-active"], readonly []>>;
4
+ readonly locator: Locator;
5
+ constructor(locator: Locator);
6
+ click(force?: boolean): Promise<void>;
7
+ expectActive(active: boolean): Promise<void>;
8
+ expectDisabled(disabled: boolean): Promise<void>;
9
+ }
10
+ //# sourceMappingURL=toggle-button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toggle-button.d.ts","sourceRoot":"","sources":["../../src/components/toggle-button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,kBAAkB,CAAC;AAIxD,qBAAa,sBAAsB;IACjC,SAAgB,OAAO,2NAA6C;IAEpE,SAAgB,OAAO,EAAE,OAAO,CAAC;gBAErB,OAAO,EAAE,OAAO;IAIf,KAAK,CAAC,KAAK,UAAQ;IAInB,YAAY,CAAC,MAAM,EAAE,OAAO;IAQ5B,cAAc,CAAC,QAAQ,EAAE,OAAO;CAO9C"}
@@ -0,0 +1,29 @@
1
+ import { expect } from '@playwright/test';
2
+ import { themeClasses } from '../utils/theme';
3
+ import { toggleButtonControlTemplate } from '@ngneers/controls-themes/templates/toggle-button';
4
+ export class NgnToggleButtonHarness {
5
+ classes = themeClasses(toggleButtonControlTemplate);
6
+ locator;
7
+ constructor(locator) {
8
+ this.locator = locator;
9
+ }
10
+ async click(force = false) {
11
+ await this.locator.click({ force });
12
+ }
13
+ async expectActive(active) {
14
+ if (active) {
15
+ await expect(this.locator).toHaveClass(new RegExp(this.classes.active));
16
+ }
17
+ else {
18
+ await expect(this.locator).not.toHaveClass(new RegExp(this.classes.active));
19
+ }
20
+ }
21
+ async expectDisabled(disabled) {
22
+ if (disabled) {
23
+ await expect(this.locator).toBeDisabled();
24
+ }
25
+ else {
26
+ await expect(this.locator).not.toBeDisabled();
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,12 @@
1
+ import { type Locator } from '@playwright/test';
2
+ export declare class NgnTooltipHarness {
3
+ readonly classes: import("@ngneers/controls-themes").ThemeClasses<import("@ngneers/controls-themes").ControlTemplate<"tooltip", ["root", "content", "text", "with-arrow", "closing", "fade-in", "fade-out", "top", "bottom", "left", "right", "start", "end"], readonly []>>;
4
+ private readonly _ngnDefer;
5
+ private readonly _contentWrapper;
6
+ readonly locator: Locator;
7
+ readonly content: Locator;
8
+ constructor(locator: Locator);
9
+ expectOpened(opened?: boolean): Promise<void>;
10
+ expectRendered(rendered?: boolean): Promise<void>;
11
+ }
12
+ //# sourceMappingURL=tooltip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltip.d.ts","sourceRoot":"","sources":["../../src/components/tooltip.ts"],"names":[],"mappings":"AAEA,OAAO,EAAU,KAAK,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAExD,qBAAa,iBAAiB;IAC5B,SAAgB,OAAO,6PAAwC;IAE/D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAU;IACpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAU;IAE1C,SAAgB,OAAO,EAAE,OAAO,CAAC;IACjC,SAAgB,OAAO,EAAE,OAAO,CAAC;gBAErB,OAAO,EAAE,OAAO;IAOf,YAAY,CAAC,MAAM,UAAO;IAI1B,cAAc,CAAC,QAAQ,UAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAa5D"}
@@ -0,0 +1,32 @@
1
+ import { tooltipControlTemplate } from '@ngneers/controls-themes/templates/tooltip';
2
+ import { themeClasses } from '../utils/theme';
3
+ import { expect } from '@playwright/test';
4
+ export class NgnTooltipHarness {
5
+ classes = themeClasses(tooltipControlTemplate);
6
+ _ngnDefer;
7
+ _contentWrapper;
8
+ locator;
9
+ content;
10
+ constructor(locator) {
11
+ this.locator = locator;
12
+ this._ngnDefer = locator.locator('ngn-defer');
13
+ this._contentWrapper = locator.locator(this.classes.content);
14
+ this.content = this._ngnDefer.locator('> *');
15
+ }
16
+ async expectOpened(opened = true) {
17
+ await expect(this._contentWrapper).toBeVisible({ visible: opened });
18
+ }
19
+ async expectRendered(rendered = true) {
20
+ await expect(this._ngnDefer).toBeAttached();
21
+ await expect(async () => {
22
+ const el = await this._ngnDefer.elementHandle();
23
+ const hasContent = !!(await el?.innerText()) || !!(await el?.evaluate(e => e.childElementCount));
24
+ if (rendered) {
25
+ expect(hasContent).toBeTruthy();
26
+ }
27
+ else {
28
+ expect(hasContent).toBeFalsy();
29
+ }
30
+ }).toPass({ timeout: 2000 });
31
+ }
32
+ }
@@ -0,0 +1,47 @@
1
+ import { type Locator } from '@playwright/test';
2
+ import { NgnScrollerHarness } from './scroller';
3
+ export declare class NgnTreeHarness {
4
+ locator: Locator;
5
+ readonly classes: import("@ngneers/controls-themes").ThemeClasses<import("@ngneers/controls-themes").ControlTemplate<"tree", ["root", "empty", "invalid", "item", "group", "default-item", "toggle", "toggle-placeholder", "toggle-icon", "toggle-arrow", "item-checkbox-placeholder", "item-selectable", "item-selected", "item-highlighted", "item-disabled", "item-expanded"], readonly [{
6
+ readonly class: "scroller";
7
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"scroller", ["root", "virtual", "item", "item-sticky"], readonly []>;
8
+ }, {
9
+ readonly class: "item-checkbox";
10
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"checkbox", ["root", "input", "box", "box-checked", "box-indeterminate", "box-icon", "box-icon-visible", "invalid"], readonly []>;
11
+ }]>>;
12
+ /** All currently rendered nodes (branches and leaves). */
13
+ readonly node: Locator;
14
+ /** Leaf rows. */
15
+ readonly item: Locator;
16
+ /** Branch rows (nodes with children). */
17
+ readonly group: Locator;
18
+ readonly toggle: Locator;
19
+ readonly itemSelected: Locator;
20
+ readonly itemHighlighted: Locator;
21
+ readonly itemDisabled: Locator;
22
+ readonly itemExpanded: Locator;
23
+ readonly checkbox: Locator;
24
+ readonly empty: Locator;
25
+ readonly spinner: Locator;
26
+ readonly scroller: NgnScrollerHarness;
27
+ constructor(locator: Locator);
28
+ /** A single node located by its exact (trimmed, case-insensitive) label. */
29
+ getNode(text: string): Locator;
30
+ /** A single node located by the `testId` of its item. */
31
+ getNodeByTestId(testId: string): Locator;
32
+ expectNodeCount(count: number): Promise<void>;
33
+ expectItemCount(count: number): Promise<void>;
34
+ expectGroupCount(count: number): Promise<void>;
35
+ expectNodeTexts(texts: string[]): Promise<void>;
36
+ /** Click a node's row (selects / checks it when selectable). */
37
+ clickNode(text: string): Promise<void>;
38
+ /** Click a branch node's expand/collapse toggle. */
39
+ toggleNode(text: string): Promise<void>;
40
+ expectExpanded(text: string, expanded?: boolean): Promise<void>;
41
+ expectSelected(text: string, selected?: boolean): Promise<void>;
42
+ expectChecked(text: string, state: 'true' | 'false' | 'mixed'): Promise<void>;
43
+ expectDisabled(text: string, disabled?: boolean): Promise<void>;
44
+ /** Assert the tree's `aria-activedescendant` points at the given node. */
45
+ expectActiveDescendant(text: string): Promise<void>;
46
+ }
47
+ //# sourceMappingURL=tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../src/components/tree.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAGxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEhD,qBAAa,cAAc;IAkBN,OAAO,EAAE,OAAO;IAjBnC,SAAgB,OAAO;;;;;;SAAqC;IAC5D,0DAA0D;IAC1D,SAAgB,IAAI,EAAE,OAAO,CAAC;IAC9B,iBAAiB;IACjB,SAAgB,IAAI,EAAE,OAAO,CAAC;IAC9B,yCAAyC;IACzC,SAAgB,KAAK,EAAE,OAAO,CAAC;IAC/B,SAAgB,MAAM,EAAE,OAAO,CAAC;IAChC,SAAgB,YAAY,EAAE,OAAO,CAAC;IACtC,SAAgB,eAAe,EAAE,OAAO,CAAC;IACzC,SAAgB,YAAY,EAAE,OAAO,CAAC;IACtC,SAAgB,YAAY,EAAE,OAAO,CAAC;IACtC,SAAgB,QAAQ,EAAE,OAAO,CAAC;IAClC,SAAgB,KAAK,EAAE,OAAO,CAAC;IAC/B,SAAgB,OAAO,EAAE,OAAO,CAAC;IACjC,SAAgB,QAAQ,EAAE,kBAAkB,CAAC;gBAE1B,OAAO,EAAE,OAAO;IAenC,4EAA4E;IACrE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIrC,yDAAyD;IAClD,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIxC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7C,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7C,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAItD,gEAAgE;IACzD,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7C,oDAAoD;IAC7C,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,UAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5D,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,UAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5D,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7E,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,UAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAMnE,0EAA0E;IAC7D,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAIjE"}
@@ -0,0 +1,85 @@
1
+ import { expect } from '@playwright/test';
2
+ import { themeClasses } from '../utils/theme';
3
+ import { treeControlTemplate } from '@ngneers/controls-themes/templates/tree';
4
+ import { NgnScrollerHarness } from './scroller';
5
+ export class NgnTreeHarness {
6
+ locator;
7
+ classes = themeClasses(treeControlTemplate);
8
+ /** All currently rendered nodes (branches and leaves). */
9
+ node;
10
+ /** Leaf rows. */
11
+ item;
12
+ /** Branch rows (nodes with children). */
13
+ group;
14
+ toggle;
15
+ itemSelected;
16
+ itemHighlighted;
17
+ itemDisabled;
18
+ itemExpanded;
19
+ checkbox;
20
+ empty;
21
+ spinner;
22
+ scroller;
23
+ constructor(locator) {
24
+ this.locator = locator;
25
+ this.node = locator.locator('[role="treeitem"]');
26
+ this.item = locator.locator(this.classes['item']);
27
+ this.group = locator.locator(this.classes['group']);
28
+ this.toggle = locator.locator(this.classes['toggle']);
29
+ this.itemSelected = locator.locator(this.classes['item-selected']);
30
+ this.itemHighlighted = locator.locator(this.classes['item-highlighted']);
31
+ this.itemDisabled = locator.locator(this.classes['item-disabled']);
32
+ this.itemExpanded = locator.locator(this.classes['item-expanded']);
33
+ this.checkbox = locator.locator(this.classes['item-checkbox']['root']);
34
+ this.empty = locator.locator(this.classes['empty']);
35
+ this.spinner = locator.locator('ngn-spinner');
36
+ this.scroller = new NgnScrollerHarness(locator.locator(this.classes['scroller']['root']));
37
+ }
38
+ /** A single node located by its exact (trimmed, case-insensitive) label. */
39
+ getNode(text) {
40
+ return this.node.filter({ hasText: new RegExp(`^\\s*${text}\\s*$`, 'i') });
41
+ }
42
+ /** A single node located by the `testId` of its item. */
43
+ getNodeByTestId(testId) {
44
+ return this.node.filter({ has: this.locator.locator(`[data-testid="${testId}"]`) });
45
+ }
46
+ expectNodeCount(count) {
47
+ return expect(this.node).toHaveCount(count);
48
+ }
49
+ expectItemCount(count) {
50
+ return expect(this.item).toHaveCount(count);
51
+ }
52
+ expectGroupCount(count) {
53
+ return expect(this.group).toHaveCount(count);
54
+ }
55
+ expectNodeTexts(texts) {
56
+ return expect(this.node).toHaveText(texts.map(t => new RegExp(`^\\s*${t}\\s*$`)));
57
+ }
58
+ /** Click a node's row (selects / checks it when selectable). */
59
+ clickNode(text) {
60
+ return this.getNode(text).click();
61
+ }
62
+ /** Click a branch node's expand/collapse toggle. */
63
+ toggleNode(text) {
64
+ return this.getNode(text).locator(this.classes['toggle']).click();
65
+ }
66
+ expectExpanded(text, expanded = true) {
67
+ return expect(this.getNode(text)).toHaveAttribute('aria-expanded', String(expanded));
68
+ }
69
+ expectSelected(text, selected = true) {
70
+ return expect(this.getNode(text)).toHaveAttribute('aria-selected', String(selected));
71
+ }
72
+ expectChecked(text, state) {
73
+ return expect(this.getNode(text)).toHaveAttribute('aria-checked', state);
74
+ }
75
+ expectDisabled(text, disabled = true) {
76
+ return disabled
77
+ ? expect(this.getNode(text)).toHaveAttribute('aria-disabled', 'true')
78
+ : expect(this.getNode(text)).not.toHaveAttribute('aria-disabled', 'true');
79
+ }
80
+ /** Assert the tree's `aria-activedescendant` points at the given node. */
81
+ async expectActiveDescendant(text) {
82
+ const id = await this.getNode(text).getAttribute('id');
83
+ await expect(this.locator).toHaveAttribute('aria-activedescendant', id ?? '');
84
+ }
85
+ }
@@ -0,0 +1,27 @@
1
+ import { type Locator } from '@playwright/test';
2
+ export declare class NgnUploadHarness {
3
+ readonly classes: import("@ngneers/controls-themes").ThemeClasses<import("@ngneers/controls-themes").ControlTemplate<"upload", ["root", "dragover", "disabled", "clickable", "draggable", "has-files", "list-top", "list-bottom", "list-left", "list-right", "main", "zone", "icon", "placeholder", "list", "item", "item-pending", "item-uploading", "item-done", "item-failed", "status", "file", "name", "size", "progress", "actions", "action", "footer", "trigger"], readonly []>>;
4
+ readonly locator: Locator;
5
+ readonly zone: Locator;
6
+ readonly input: Locator;
7
+ readonly list: Locator;
8
+ readonly items: Locator;
9
+ constructor(locator: Locator);
10
+ /** Item `<li>` at the given index. */
11
+ item(index?: number): Locator;
12
+ name(index?: number): Locator;
13
+ expectItemCount(count: number): Promise<void>;
14
+ expectItemName(index: number, text: string): Promise<void>;
15
+ expectItemState(index: number, state: 'pending' | 'uploading' | 'done' | 'failed'): Promise<void>;
16
+ /** Select files through the projected native input (click-select path). */
17
+ selectFiles(files: Array<{
18
+ name: string;
19
+ mimeType: string;
20
+ buffer: Buffer;
21
+ }>): Promise<void>;
22
+ /** Action button ('upload' | 'cancel' | 'retry' | 'remove') on an item. */
23
+ actionButton(index: number, label: string): Locator;
24
+ /** The "Upload" button rendered in confirm mode (`confirmTrigger: 'all'`). */
25
+ uploadAllButton(): Locator;
26
+ }
27
+ //# sourceMappingURL=upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/components/upload.ts"],"names":[],"mappings":"AAEA,OAAO,EAAU,KAAK,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAExD,qBAAa,gBAAgB;IAC3B,SAAgB,OAAO,ycAAuC;IAE9D,SAAgB,OAAO,EAAE,OAAO,CAAC;IACjC,SAAgB,IAAI,EAAE,OAAO,CAAC;IAC9B,SAAgB,KAAK,EAAE,OAAO,CAAC;IAC/B,SAAgB,IAAI,EAAE,OAAO,CAAC;IAC9B,SAAgB,KAAK,EAAE,OAAO,CAAC;gBAEnB,OAAO,EAAE,OAAO;IAQ5B,sCAAsC;IAC/B,IAAI,CAAC,KAAK,GAAE,MAAU,GAAG,OAAO;IAIhC,IAAI,CAAC,KAAK,GAAE,MAAU,GAAG,OAAO;IAI1B,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7C,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1D,eAAe,CAC1B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,SAAS,GAAG,WAAW,GAAG,MAAM,GAAG,QAAQ,GACjD,OAAO,CAAC,IAAI,CAAC;IAKhB,2EAA2E;IAC9D,WAAW,CACtB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,GAC/D,OAAO,CAAC,IAAI,CAAC;IAIhB,2EAA2E;IACpE,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IAI1D,8EAA8E;IACvE,eAAe,IAAI,OAAO;CAGlC"}
@@ -0,0 +1,47 @@
1
+ import { uploadControlTemplate } from '@ngneers/controls-themes/templates/upload';
2
+ import { themeClasses } from '../utils/theme';
3
+ import { expect } from '@playwright/test';
4
+ export class NgnUploadHarness {
5
+ classes = themeClasses(uploadControlTemplate);
6
+ locator;
7
+ zone;
8
+ input;
9
+ list;
10
+ items;
11
+ constructor(locator) {
12
+ this.locator = locator;
13
+ this.zone = locator.locator(this.classes.zone);
14
+ this.input = locator.locator('input[type=file]');
15
+ this.list = locator.locator(this.classes.list);
16
+ this.items = this.list.locator(this.classes.item);
17
+ }
18
+ /** Item `<li>` at the given index. */
19
+ item(index = 0) {
20
+ return this.items.nth(index);
21
+ }
22
+ name(index = 0) {
23
+ return this.item(index).locator(this.classes.name);
24
+ }
25
+ async expectItemCount(count) {
26
+ await expect(this.items).toHaveCount(count);
27
+ }
28
+ async expectItemName(index, text) {
29
+ await expect(this.name(index)).toHaveText(text);
30
+ }
31
+ async expectItemState(index, state) {
32
+ const stateClass = this.classes[`item-${state}`].slice(1); // strip leading '.'
33
+ await expect(this.item(index)).toHaveClass(new RegExp(stateClass));
34
+ }
35
+ /** Select files through the projected native input (click-select path). */
36
+ async selectFiles(files) {
37
+ await this.input.setInputFiles(files);
38
+ }
39
+ /** Action button ('upload' | 'cancel' | 'retry' | 'remove') on an item. */
40
+ actionButton(index, label) {
41
+ return this.item(index).getByLabel(new RegExp(`^${label}`, 'i'));
42
+ }
43
+ /** The "Upload" button rendered in confirm mode (`confirmTrigger: 'all'`). */
44
+ uploadAllButton() {
45
+ return this.locator.locator(this.classes.trigger);
46
+ }
47
+ }
@@ -0,0 +1,2 @@
1
+ export * from './components';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from './components';
@@ -0,0 +1,119 @@
1
+ export declare const NGN_CLASSES: {
2
+ avatar: import("@ngneers/controls-themes").ThemeClasses<import("@ngneers/controls-themes").ControlTemplate<"avatar", ["root", "image", "initials", `kind-${string}`], readonly []>>;
3
+ button: import("@ngneers/controls-themes").ThemeClasses<import("@ngneers/controls-themes").ControlTemplate<"button", ["root", "inline", "loading", `kind-${string}`, `color-${string}`], readonly []>>;
4
+ calendar: import("@ngneers/controls-themes").ThemeClasses<import("@ngneers/controls-themes").ControlTemplate<"calendar", ["root", "inline", "invalid", "details", "header", "navigation", "days", "week-day", "day", "day-selected", "day-today", "day-other-month", "months", "month", "time", "input-field"], readonly [{
5
+ readonly class: "input";
6
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"mask-input", ["root", "section", "section-active", "section-placeholder", "separator", "proxy", "sr-only"], readonly []>;
7
+ }, {
8
+ readonly class: "trigger-icon";
9
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"icon", ["root", "default"], readonly []>;
10
+ }, {
11
+ readonly class: "popover";
12
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"popover", ["root", "content", "content-closing"], readonly []>;
13
+ }, {
14
+ readonly class: "previous";
15
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"button", ["root", "inline", "loading", `kind-${string}`, `color-${string}`], readonly []>;
16
+ }, {
17
+ readonly class: "next";
18
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"button", ["root", "inline", "loading", `kind-${string}`, `color-${string}`], readonly []>;
19
+ }, {
20
+ readonly class: "current-month";
21
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"select", ["root", "combobox", "input", "icon", "input-editable", "popover-content", "filter-icon", "list-box-empty"], readonly [{
22
+ readonly class: "popover";
23
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"popover", ["root", "content", "content-closing"], readonly []>;
24
+ }, {
25
+ readonly class: "list-box";
26
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"list-box", ["root", "empty", "invalid", "item", "group", "default-item", "default-group", "item-selected", "item-highlighted", "item-disabled"], readonly [{
27
+ readonly class: "checkbox";
28
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"checkbox", ["root", "input", "box", "box-checked", "box-indeterminate", "box-icon", "box-icon-visible", "invalid"], readonly []>;
29
+ }, {
30
+ readonly class: "scroller";
31
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"scroller", ["root", "virtual", "item", "item-sticky"], readonly []>;
32
+ }]>;
33
+ }, {
34
+ readonly class: "filter";
35
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"input-field", ["root", "host", "disabled", "readonly", "invalid", "label", "clear-button", `labelKind-${string}`], readonly [{
36
+ readonly class: "input";
37
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"input", ["root", "invalid", "empty"], readonly []>;
38
+ readonly projected: true;
39
+ }]>;
40
+ }]>;
41
+ }, {
42
+ readonly class: "current-year";
43
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"select", ["root", "combobox", "input", "icon", "input-editable", "popover-content", "filter-icon", "list-box-empty"], readonly [{
44
+ readonly class: "popover";
45
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"popover", ["root", "content", "content-closing"], readonly []>;
46
+ }, {
47
+ readonly class: "list-box";
48
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"list-box", ["root", "empty", "invalid", "item", "group", "default-item", "default-group", "item-selected", "item-highlighted", "item-disabled"], readonly [{
49
+ readonly class: "checkbox";
50
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"checkbox", ["root", "input", "box", "box-checked", "box-indeterminate", "box-icon", "box-icon-visible", "invalid"], readonly []>;
51
+ }, {
52
+ readonly class: "scroller";
53
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"scroller", ["root", "virtual", "item", "item-sticky"], readonly []>;
54
+ }]>;
55
+ }, {
56
+ readonly class: "filter";
57
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"input-field", ["root", "host", "disabled", "readonly", "invalid", "label", "clear-button", `labelKind-${string}`], readonly [{
58
+ readonly class: "input";
59
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"input", ["root", "invalid", "empty"], readonly []>;
60
+ readonly projected: true;
61
+ }]>;
62
+ }]>;
63
+ }, {
64
+ readonly class: "current-month-field";
65
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"input-field", ["root", "host", "disabled", "readonly", "invalid", "label", "clear-button", `labelKind-${string}`], readonly [{
66
+ readonly class: "input";
67
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"input", ["root", "invalid", "empty"], readonly []>;
68
+ readonly projected: true;
69
+ }]>;
70
+ }, {
71
+ readonly class: "current-year-field";
72
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"input-field", ["root", "host", "disabled", "readonly", "invalid", "label", "clear-button", `labelKind-${string}`], readonly [{
73
+ readonly class: "input";
74
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"input", ["root", "invalid", "empty"], readonly []>;
75
+ readonly projected: true;
76
+ }]>;
77
+ }]>>;
78
+ checkbox: import("@ngneers/controls-themes").ThemeClasses<import("@ngneers/controls-themes").ControlTemplate<"checkbox", ["root", "input", "box", "box-checked", "box-indeterminate", "box-icon", "box-icon-visible", "invalid"], readonly []>>;
79
+ input: import("@ngneers/controls-themes").ThemeClasses<import("@ngneers/controls-themes").ControlTemplate<"input", ["root", "invalid", "empty"], readonly []>>;
80
+ inputField: import("@ngneers/controls-themes").ThemeClasses<import("@ngneers/controls-themes").ControlTemplate<"input-field", ["root", "host", "disabled", "readonly", "invalid", "label", "clear-button", `labelKind-${string}`], readonly [{
81
+ readonly class: "input";
82
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"input", ["root", "invalid", "empty"], readonly []>;
83
+ readonly projected: true;
84
+ }]>>;
85
+ maskInput: import("@ngneers/controls-themes").ThemeClasses<import("@ngneers/controls-themes").ControlTemplate<"mask-input", ["root", "section", "section-active", "section-placeholder", "separator", "proxy", "sr-only"], readonly []>>;
86
+ listBox: import("@ngneers/controls-themes").ThemeClasses<import("@ngneers/controls-themes").ControlTemplate<"list-box", ["root", "empty", "invalid", "item", "group", "default-item", "default-group", "item-selected", "item-highlighted", "item-disabled"], readonly [{
87
+ readonly class: "checkbox";
88
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"checkbox", ["root", "input", "box", "box-checked", "box-indeterminate", "box-icon", "box-icon-visible", "invalid"], readonly []>;
89
+ }, {
90
+ readonly class: "scroller";
91
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"scroller", ["root", "virtual", "item", "item-sticky"], readonly []>;
92
+ }]>>;
93
+ popover: import("@ngneers/controls-themes").ThemeClasses<import("@ngneers/controls-themes").ControlTemplate<"popover", ["root", "content", "content-closing"], readonly []>>;
94
+ scroller: import("@ngneers/controls-themes").ThemeClasses<import("@ngneers/controls-themes").ControlTemplate<"scroller", ["root", "virtual", "item", "item-sticky"], readonly []>>;
95
+ select: import("@ngneers/controls-themes").ThemeClasses<import("@ngneers/controls-themes").ControlTemplate<"select", ["root", "combobox", "input", "icon", "input-editable", "popover-content", "filter-icon", "list-box-empty"], readonly [{
96
+ readonly class: "popover";
97
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"popover", ["root", "content", "content-closing"], readonly []>;
98
+ }, {
99
+ readonly class: "list-box";
100
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"list-box", ["root", "empty", "invalid", "item", "group", "default-item", "default-group", "item-selected", "item-highlighted", "item-disabled"], readonly [{
101
+ readonly class: "checkbox";
102
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"checkbox", ["root", "input", "box", "box-checked", "box-indeterminate", "box-icon", "box-icon-visible", "invalid"], readonly []>;
103
+ }, {
104
+ readonly class: "scroller";
105
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"scroller", ["root", "virtual", "item", "item-sticky"], readonly []>;
106
+ }]>;
107
+ }, {
108
+ readonly class: "filter";
109
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"input-field", ["root", "host", "disabled", "readonly", "invalid", "label", "clear-button", `labelKind-${string}`], readonly [{
110
+ readonly class: "input";
111
+ readonly template: import("@ngneers/controls-themes").ControlTemplate<"input", ["root", "invalid", "empty"], readonly []>;
112
+ readonly projected: true;
113
+ }]>;
114
+ }]>>;
115
+ splitter: import("@ngneers/controls-themes").ThemeClasses<import("@ngneers/controls-themes").ControlTemplate<"splitter", ["root", "horizontal", "vertical", "panel", "divider", "divider-handle", "dragging", "divider-dragging", `kind-${string}`], readonly []>>;
116
+ tabs: import("@ngneers/controls-themes").ThemeClasses<import("@ngneers/controls-themes").ControlTemplate<"tabs", ["root", "headers-container", "headers", "header", "header-active", "header-active-indicator", "scroll-left", "scroll-right", "content"], readonly []>>;
117
+ tooltip: import("@ngneers/controls-themes").ThemeClasses<import("@ngneers/controls-themes").ControlTemplate<"tooltip", ["root", "content", "text", "with-arrow", "closing", "fade-in", "fade-out", "top", "bottom", "left", "right", "start", "end"], readonly []>>;
118
+ };
119
+ //# sourceMappingURL=classes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classes.d.ts","sourceRoot":"","sources":["../../src/utils/classes.ts"],"names":[],"mappings":"AAgBA,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAevB,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { avatarControlTemplate } from '@ngneers/controls-themes/templates/avatar';
2
+ import { themeClasses } from './theme';
3
+ import { buttonControlTemplate } from '@ngneers/controls-themes/templates/button';
4
+ import { calendarControlTemplate } from '@ngneers/controls-themes/templates/calendar';
5
+ import { checkboxControlTemplate } from '@ngneers/controls-themes/templates/checkbox';
6
+ import { inputControlTemplate } from '@ngneers/controls-themes/templates/input';
7
+ import { maskInputControlTemplate } from '@ngneers/controls-themes/templates/mask-input';
8
+ import { inputFieldControlTemplate } from '@ngneers/controls-themes/templates/input-field';
9
+ import { listBoxControlTemplate } from '@ngneers/controls-themes/templates/list-box';
10
+ import { popoverControlTemplate } from '@ngneers/controls-themes/templates/popover';
11
+ import { scrollerControlTemplate } from '@ngneers/controls-themes/templates/scroller';
12
+ import { selectControlTemplate } from '@ngneers/controls-themes/templates/select';
13
+ import { splitterControlTemplate } from '@ngneers/controls-themes/templates/splitter';
14
+ import { tabsControlTemplate } from '@ngneers/controls-themes/templates/tabs';
15
+ import { tooltipControlTemplate } from '@ngneers/controls-themes/templates/tooltip';
16
+ export const NGN_CLASSES = {
17
+ avatar: themeClasses(avatarControlTemplate),
18
+ button: themeClasses(buttonControlTemplate),
19
+ calendar: themeClasses(calendarControlTemplate),
20
+ checkbox: themeClasses(checkboxControlTemplate),
21
+ input: themeClasses(inputControlTemplate),
22
+ inputField: themeClasses(inputFieldControlTemplate),
23
+ maskInput: themeClasses(maskInputControlTemplate),
24
+ listBox: themeClasses(listBoxControlTemplate),
25
+ popover: themeClasses(popoverControlTemplate),
26
+ scroller: themeClasses(scrollerControlTemplate),
27
+ select: themeClasses(selectControlTemplate),
28
+ splitter: themeClasses(splitterControlTemplate),
29
+ tabs: themeClasses(tabsControlTemplate),
30
+ tooltip: themeClasses(tooltipControlTemplate),
31
+ };
@@ -0,0 +1,3 @@
1
+ import type { ControlTemplate, ThemeClasses } from '@ngneers/controls-themes';
2
+ export declare function themeClasses<CT extends ControlTemplate>(template: CT): ThemeClasses<CT>;
3
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/utils/theme.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE9E,wBAAgB,YAAY,CAAC,EAAE,SAAS,eAAe,EAAE,QAAQ,EAAE,EAAE,GAAG,YAAY,CAAC,EAAE,CAAC,CAwBvF"}
@@ -0,0 +1,41 @@
1
+ export function themeClasses(template) {
2
+ const result = {};
3
+ for (const className of template.classNames) {
4
+ result[className] = `.ngn-${template.scope}-${className}`;
5
+ }
6
+ for (const dep of template.dependencies ?? []) {
7
+ const childClasses = themeClasses(dep.template);
8
+ // Non-projected deps get a `[ptDep]` marker class (`.ngn-{scope}-{depClass}`) applied to
9
+ // the child's host element (see `NgnPt`/`apply-theme.ts`'s `d()`). Whether a given child
10
+ // class then lives on that SAME host element or on an element nested inside the child's own
11
+ // template depends on which classNames the child passes as `hostClass` to
12
+ // `injectThemeTemplate` (e.g. list-box binds its own 'root' straight to its host, so marker
13
+ // and 'root' land on one element; input-field reserves a separate 'host' key for its host
14
+ // binding and keeps 'root' on an inner div, so marker and 'root' land on two elements).
15
+ // The theme package has no visibility into that per-component choice, so instead of guessing
16
+ // we match BOTH shapes via `:is()`: compound (same element) or descendant (nested element).
17
+ // Projected deps have no marker (no host the parent controls), so their classes stay raw,
18
+ // matching how `apply-theme.ts` resolves `projected: true` deps directly off the child's own
19
+ // scope.
20
+ result[dep.class] = dep.projected
21
+ ? childClasses
22
+ : scopeByMarker(childClasses, `.ngn-${template.scope}-${dep.class}`);
23
+ }
24
+ return result;
25
+ }
26
+ /**
27
+ * Scopes every selector string in a (possibly nested) theme-classes object to `marker`, matching
28
+ * either a compound (same-element) or descendant (nested-element) relationship — see the
29
+ * `dep.projected` branch above for why both shapes are needed. Recurses into nested dependency
30
+ * objects so grandchild slots are scoped transitively through every ancestor marker.
31
+ */
32
+ function scopeByMarker(classes, marker) {
33
+ const scoped = {};
34
+ for (const [key, value] of Object.entries(classes)) {
35
+ scoped[key] =
36
+ typeof value === 'string'
37
+ ? `:is(${marker}${value}, ${marker} ${value})`
38
+ : scopeByMarker(value, marker);
39
+ }
40
+ return scoped;
41
+ }