chrome-devtools-frontend 1.0.1595925 → 1.0.1596535

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 (129) hide show
  1. package/.stylelintrc.json +3 -1
  2. package/docs/ui_engineering.md +0 -36
  3. package/front_end/core/common/Console.ts +6 -6
  4. package/front_end/core/common/Settings.ts +12 -8
  5. package/front_end/core/host/UserMetrics.ts +0 -1
  6. package/front_end/core/root/DevToolsContext.ts +13 -7
  7. package/front_end/core/root/ExperimentNames.ts +0 -1
  8. package/front_end/core/sdk/CPUThrottlingManager.ts +12 -9
  9. package/front_end/core/sdk/DOMModel.ts +206 -0
  10. package/front_end/core/sdk/FrameManager.ts +7 -8
  11. package/front_end/core/sdk/SourceMapScopesInfo.ts +1 -1
  12. package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +1 -1
  13. package/front_end/entrypoints/greendev_floaty/floaty.css +2 -2
  14. package/front_end/entrypoints/main/MainImpl.ts +1 -7
  15. package/front_end/foundation/Universe.ts +22 -11
  16. package/front_end/generated/InspectorBackendCommands.ts +7 -0
  17. package/front_end/generated/protocol-mapping.d.ts +16 -0
  18. package/front_end/generated/protocol-proxy-api.d.ts +25 -0
  19. package/front_end/generated/protocol.ts +71 -0
  20. package/front_end/models/ai_assistance/AiConversation.ts +6 -95
  21. package/front_end/models/ai_assistance/ConversationHandler.ts +1 -1
  22. package/front_end/models/ai_assistance/agents/AiAgent.ts +11 -1
  23. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +14 -0
  24. package/front_end/models/ai_code_completion/AiCodeCompletion.ts +1 -1
  25. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +3 -0
  26. package/front_end/panels/ai_assistance/components/ChatMessage.ts +73 -35
  27. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +30 -6
  28. package/front_end/panels/ai_assistance/components/chatMessage.css +4 -2
  29. package/front_end/panels/application/IndexedDBModel.ts +2 -4
  30. package/front_end/panels/application/ServiceWorkersView.ts +3 -11
  31. package/front_end/panels/developer_resources/DeveloperResourcesListView.ts +1 -1
  32. package/front_end/panels/elements/ComputedStyleWidget.ts +25 -16
  33. package/front_end/panels/elements/ElementsPanel.ts +0 -1
  34. package/front_end/panels/elements/StandaloneStylesContainer.ts +48 -12
  35. package/front_end/panels/elements/StylePropertiesSection.ts +97 -1
  36. package/front_end/panels/elements/StylePropertyTreeElement.ts +27 -5
  37. package/front_end/panels/elements/StylesContainer.ts +1 -0
  38. package/front_end/panels/elements/StylesSidebarPane.ts +48 -32
  39. package/front_end/panels/elements/nodeStackTraceWidget.css +1 -1
  40. package/front_end/panels/elements/stylePropertiesTreeOutline.css +1 -1
  41. package/front_end/panels/emulation/DeviceModeToolbar.ts +171 -89
  42. package/front_end/panels/greendev/GreenDevPanel.css +2 -2
  43. package/front_end/panels/issues/AffectedPermissionElementsView.ts +9 -6
  44. package/front_end/panels/lighthouse/LighthouseController.ts +13 -5
  45. package/front_end/panels/lighthouse/LighthousePanel.ts +22 -5
  46. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +11 -3
  47. package/front_end/panels/recorder/components/StepView.ts +0 -2
  48. package/front_end/panels/recorder/components/stepView.css +13 -13
  49. package/front_end/panels/recorder/components/timelineSection.css +6 -7
  50. package/front_end/panels/settings/components/SyncSection.ts +4 -13
  51. package/front_end/panels/timeline/CompatibilityTracksAppender.ts +2 -15
  52. package/front_end/panels/timeline/RecordingMetadata.ts +1 -1
  53. package/front_end/panels/timeline/TimelineDetailsView.ts +2 -2
  54. package/front_end/panels/timeline/TimelinePanel.ts +18 -0
  55. package/front_end/panels/timeline/TimelineUIUtils.ts +7 -4
  56. package/front_end/panels/timeline/components/CWVMetrics.ts +339 -0
  57. package/front_end/panels/timeline/components/Sidebar.ts +17 -0
  58. package/front_end/panels/timeline/components/SidebarInsightsTab.ts +10 -0
  59. package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +25 -267
  60. package/front_end/panels/timeline/components/Utils.ts +2 -2
  61. package/front_end/panels/timeline/components/components.ts +2 -0
  62. package/front_end/panels/timeline/components/cwvMetrics.css +107 -0
  63. package/front_end/panels/timeline/components/sidebarSingleInsightSet.css +0 -102
  64. package/front_end/panels/timeline/timeline-meta.ts +11 -0
  65. package/front_end/panels/timeline/utils/Helpers.ts +5 -1
  66. package/front_end/panels/webauthn/webauthnPane.css +1 -1
  67. package/front_end/third_party/chromium/README.chromium +1 -1
  68. package/front_end/third_party/puppeteer/README.chromium +2 -2
  69. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts +7 -0
  70. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts.map +1 -1
  71. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.js.map +1 -1
  72. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.d.ts +1 -0
  73. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.d.ts.map +1 -1
  74. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.js +3 -0
  75. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.js.map +1 -1
  76. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.d.ts +1 -0
  77. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.d.ts.map +1 -1
  78. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js +6 -0
  79. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js.map +1 -1
  80. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.d.ts +1 -0
  81. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.d.ts.map +1 -1
  82. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js +5 -0
  83. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js.map +1 -1
  84. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  85. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +2 -2
  86. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +2 -2
  87. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  88. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
  89. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
  90. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +7 -0
  91. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +14 -3
  92. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts +7 -0
  93. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts.map +1 -1
  94. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.js.map +1 -1
  95. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.d.ts +1 -0
  96. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.d.ts.map +1 -1
  97. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.js +3 -0
  98. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.js.map +1 -1
  99. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.d.ts +1 -0
  100. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.d.ts.map +1 -1
  101. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js +6 -0
  102. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js.map +1 -1
  103. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.d.ts +1 -0
  104. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.d.ts.map +1 -1
  105. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js +5 -0
  106. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js.map +1 -1
  107. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +2 -2
  108. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +2 -2
  109. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
  110. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
  111. package/front_end/third_party/puppeteer/package/lib/types.d.ts +7 -0
  112. package/front_end/third_party/puppeteer/package/package.json +1 -1
  113. package/front_end/third_party/puppeteer/package/src/api/Page.ts +8 -0
  114. package/front_end/third_party/puppeteer/package/src/bidi/Page.ts +4 -0
  115. package/front_end/third_party/puppeteer/package/src/cdp/Browser.ts +7 -0
  116. package/front_end/third_party/puppeteer/package/src/cdp/Page.ts +6 -0
  117. package/front_end/third_party/puppeteer/package/src/revisions.ts +2 -2
  118. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  119. package/front_end/ui/components/text_editor/AutocompleteHistory.ts +89 -5
  120. package/front_end/ui/components/tree_outline/TreeOutline.ts +1 -1
  121. package/front_end/ui/legacy/ProgressIndicator.ts +1 -1
  122. package/front_end/ui/legacy/Toolbar.ts +1 -1
  123. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +14 -7
  124. package/front_end/ui/legacy/legacy.ts +0 -2
  125. package/front_end/ui/visual_logging/KnownContextValues.ts +5 -0
  126. package/inspector_overlay/loadCSS.rollup.js +2 -2
  127. package/inspector_overlay/tool_source_order.css +1 -0
  128. package/package.json +1 -1
  129. package/front_end/ui/legacy/Fragment.ts +0 -234
@@ -26,6 +26,18 @@ export class AutocompleteHistory {
26
26
  */
27
27
  #editedEntries = new Map<number, string>();
28
28
 
29
+ /**
30
+ * The prefix used for filtering history during navigation (zsh-style).
31
+ * Set on first navigation when user has typed something.
32
+ */
33
+ #searchPrefix = '';
34
+
35
+ /**
36
+ * Stack of history indices visited during filtered navigation.
37
+ * Used to navigate forward through the same filtered entries.
38
+ */
39
+ #filteredIndices: number[] = [];
40
+
29
41
  /**
30
42
  * Creates a new settings-backed history. The class assumes it has sole
31
43
  * ownership of the setting.
@@ -40,6 +52,8 @@ export class AutocompleteHistory {
40
52
  this.#setting.set([]);
41
53
  this.#historyOffset = 1;
42
54
  this.#editedEntries.clear();
55
+ this.#searchPrefix = '';
56
+ this.#filteredIndices = [];
43
57
  }
44
58
 
45
59
  length(): number {
@@ -57,6 +71,8 @@ export class AutocompleteHistory {
57
71
 
58
72
  this.#historyOffset = 1;
59
73
  this.#editedEntries.clear();
74
+ this.#searchPrefix = '';
75
+ this.#filteredIndices = [];
60
76
  if (text !== this.#currentHistoryItem()) {
61
77
  this.#data.push(text);
62
78
  }
@@ -74,17 +90,64 @@ export class AutocompleteHistory {
74
90
  this.#data.push(currentText);
75
91
  }
76
92
 
77
- previous(currentText: string): string|undefined {
93
+ previous(currentText?: string): string|undefined {
78
94
  if (this.#historyOffset > this.#data.length) {
79
95
  return undefined;
80
96
  }
97
+ currentText = currentText ?? '';
81
98
  if (this.#historyOffset === 1) {
82
99
  this.#pushCurrentText(currentText);
100
+
101
+ this.#filteredIndices = [];
102
+ this.#searchPrefix = currentText;
83
103
  } else {
104
+ // Save edits made to history entries at non-uncommitted positions.
105
+ // #saveCurrentEdit() compares against #currentHistoryItem() and no-ops for
106
+ // pure navigation.
84
107
  this.#saveCurrentEdit(currentText);
85
108
  }
86
- ++this.#historyOffset;
87
- return this.#currentHistoryItem();
109
+
110
+ // If no prefix filter, use simple sequential navigation
111
+ if (this.#searchPrefix.length === 0) {
112
+ ++this.#historyOffset;
113
+ return this.#currentHistoryItem();
114
+ }
115
+
116
+ // Find the next matching entry for prefix-filtered navigation
117
+ const result = this.#findNextMatch();
118
+
119
+ if (!result) {
120
+ // On initial navigation with a non-empty prefix and no matches, fall back to
121
+ // unfiltered history navigation.
122
+ if (this.#historyOffset === 1) {
123
+ this.#searchPrefix = '';
124
+ ++this.#historyOffset;
125
+ return this.#currentHistoryItem();
126
+ }
127
+ return undefined;
128
+ }
129
+
130
+ this.#filteredIndices.push(result.index);
131
+ this.#historyOffset = this.#data.length - result.index;
132
+ return result.value;
133
+ }
134
+
135
+ /**
136
+ * Finds the next history entry that matches the search prefix.
137
+ * Returns undefined if no more matches are found.
138
+ */
139
+ #findNextMatch(): {index: number, value: string}|undefined {
140
+ // Start searching from the current position
141
+ const startIndex = this.#data.length - this.#historyOffset - 1;
142
+
143
+ for (let i = startIndex; i >= 0; --i) {
144
+ const storedValue = this.#data[i];
145
+ if (storedValue.startsWith(this.#searchPrefix)) {
146
+ const value = this.#editedEntries.get(i) ?? storedValue;
147
+ return {index: i, value};
148
+ }
149
+ }
150
+ return undefined;
88
151
  }
89
152
 
90
153
  /**
@@ -107,12 +170,33 @@ export class AutocompleteHistory {
107
170
  }
108
171
  }
109
172
 
110
- next(currentText: string): string|undefined {
173
+ next(currentText?: string): string|undefined {
111
174
  if (this.#historyOffset === 1) {
112
175
  return undefined;
113
176
  }
177
+
178
+ currentText = currentText ?? this.#currentHistoryItem() ?? '';
179
+
114
180
  this.#saveCurrentEdit(currentText);
115
- --this.#historyOffset;
181
+
182
+ // If no prefix filter was used, use simple sequential navigation
183
+ if (this.#searchPrefix.length === 0) {
184
+ --this.#historyOffset;
185
+ return this.#currentHistoryItem();
186
+ }
187
+
188
+ // Pop the current position from the filtered indices stack
189
+ this.#filteredIndices.pop();
190
+
191
+ if (this.#filteredIndices.length === 0) {
192
+ // No more filtered entries - return to the uncommitted text
193
+ this.#historyOffset = 1;
194
+ return this.#currentHistoryItem();
195
+ }
196
+
197
+ // Move to the previous filtered position
198
+ const prevIndex = this.#filteredIndices[this.#filteredIndices.length - 1];
199
+ this.#historyOffset = this.#data.length - prevIndex;
116
200
  return this.#currentHistoryItem();
117
201
  }
118
202
 
@@ -527,7 +527,7 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
527
527
 
528
528
  const ulClasses = Lit.Directives.classMap({hasNoChildren: !hasChildrenInTree});
529
529
 
530
- await RenderCoordinator.write('TreeOutline render', () => {
530
+ await RenderCoordinator.write(() => {
531
531
  // Disabled until https://crbug.com/1079231 is fixed.
532
532
  // clang-format off
533
533
  // Unfortunately the TreeOutline web component adds the
@@ -66,7 +66,7 @@ export class ProgressIndicator extends HTMLElement implements Common.Progress.Pr
66
66
  }
67
67
 
68
68
  override get title(): string {
69
- return this.#labelElement.textContent ?? '';
69
+ return this.#labelElement.textContent;
70
70
  }
71
71
 
72
72
  set totalWork(totalWork: number) {
@@ -562,7 +562,7 @@ export class ToolbarText extends ToolbarItem<void, HTMLElement> {
562
562
  }
563
563
 
564
564
  text(): string {
565
- return this.element.textContent ?? '';
565
+ return this.element.textContent;
566
566
  }
567
567
 
568
568
  setText(text: string): void {
@@ -636,18 +636,25 @@ export class ObjectPropertiesSection extends UI.TreeOutline.TreeOutlineInShadow
636
636
  }
637
637
 
638
638
  static createNameElement(name: string|null, isPrivate?: boolean): Element {
639
+ const element = document.createElement('span');
640
+ element.classList.add('name');
639
641
  if (name === null) {
640
- return UI.Fragment.html`<span class="name"></span>`;
642
+ return element;
641
643
  }
642
644
  if (/^\s|\s$|^$|\n/.test(name)) {
643
- return UI.Fragment.html`<span class="name">"${name.replace(/\n/g, '\u21B5')}"</span>`;
645
+ element.textContent = `"${name.replace(/\n/g, '\u21B5')}"`;
646
+ return element;
644
647
  }
645
648
  if (isPrivate) {
646
- return UI.Fragment.html`<span class="name">
647
- <span class="private-property-hash">${name[0]}</span>${name.substring(1)}
648
- </span>`;
649
- }
650
- return UI.Fragment.html`<span class="name">${name}</span>`;
649
+ const privatePropertyHash = document.createElement('span');
650
+ privatePropertyHash.classList.add('private-property-hash');
651
+ privatePropertyHash.textContent = name[0];
652
+ element.appendChild(privatePropertyHash);
653
+ element.appendChild(document.createTextNode(name.substring(1)));
654
+ return element;
655
+ }
656
+ element.textContent = name;
657
+ return element;
651
658
  }
652
659
 
653
660
  static valueElementForFunctionDescription(
@@ -16,7 +16,6 @@ import * as EmptyWidget from './EmptyWidget.js';
16
16
  import * as FilterBar from './FilterBar.js';
17
17
  import * as FilterSuggestionBuilder from './FilterSuggestionBuilder.js';
18
18
  import * as ForwardedInputEventHandler from './ForwardedInputEventHandler.js';
19
- import * as Fragment from './Fragment.js';
20
19
  import * as GlassPane from './GlassPane.js';
21
20
  import * as Infobar from './Infobar.js';
22
21
  import * as InplaceEditor from './InplaceEditor.js';
@@ -67,7 +66,6 @@ export {
67
66
  FilterBar,
68
67
  FilterSuggestionBuilder,
69
68
  ForwardedInputEventHandler,
70
- Fragment,
71
69
  GlassPane,
72
70
  Infobar,
73
71
  InplaceEditor,
@@ -1359,6 +1359,10 @@ export const knownContextValues = new Set([
1359
1359
  'dominant-baseline',
1360
1360
  'dont-show-again',
1361
1361
  'download',
1362
+ 'dpr-1',
1363
+ 'dpr-2',
1364
+ 'dpr-3',
1365
+ 'dpr-default',
1362
1366
  'drag',
1363
1367
  'drag-drop',
1364
1368
  'dragend',
@@ -2449,6 +2453,7 @@ export const knownContextValues = new Set([
2449
2453
  'lighthouse.enable-sampling',
2450
2454
  'lighthouse.end-time-span',
2451
2455
  'lighthouse.mode',
2456
+ 'lighthouse.record',
2452
2457
  'lighthouse.start',
2453
2458
  'lighthouse.throttling',
2454
2459
  'lighting-color',
@@ -10,8 +10,8 @@ export default function loadCSS() {
10
10
  return {
11
11
  name: 'loadCSS',
12
12
  /**
13
- * @param code
14
- * @param id
13
+ * @param code {string}
14
+ * @param id {string}
15
15
  */
16
16
  transform(code, id) {
17
17
  if (id.endsWith('.css')) {
@@ -15,6 +15,7 @@
15
15
  text-align: center;
16
16
  align-items: center;
17
17
  background-color: #fff;
18
+ /* stylelint-disable-next-line plugin/use_monospace_font */
18
19
  font-family: Menlo, Consolas, monospace;
19
20
  font-size: 12px;
20
21
  font-weight: bold;
package/package.json CHANGED
@@ -105,5 +105,5 @@
105
105
  "flat-cache": "6.1.12"
106
106
  }
107
107
  },
108
- "version": "1.0.1595925"
108
+ "version": "1.0.1596535"
109
109
  }
@@ -1,234 +0,0 @@
1
- // Copyright 2017 The Chromium Authors
2
- // Use of this source code is governed by a BSD-style license that can be
3
- // found in the LICENSE file.
4
- /* eslint-disable @devtools/no-imperative-dom-api */
5
-
6
- function getNodeData(node: Node): string {
7
- return (node as unknown as {
8
- data: string,
9
- })
10
- .data;
11
- }
12
-
13
- function setNodeData<T>(node: Node, value: T): void {
14
- (node as unknown as {
15
- data: T,
16
- }).data = value;
17
- }
18
-
19
- export class Fragment {
20
- readonly #element: Element;
21
- private readonly elementsById = new Map<string, Element>();
22
-
23
- constructor(element: Element) {
24
- this.#element = element;
25
- }
26
-
27
- element(): Element {
28
- return this.#element;
29
- }
30
-
31
- $(elementId: string): Element {
32
- return this.elementsById.get(elementId) as Element;
33
- }
34
-
35
- static build(strings: TemplateDefinition, ...values: any[]): Fragment {
36
- return Fragment.render(Fragment.template(strings), values);
37
- }
38
-
39
- static cached(strings: TemplateDefinition, ...values: any[]): Fragment {
40
- let template = templateCache.get(strings);
41
- if (!template) {
42
- template = Fragment.template(strings);
43
- templateCache.set(strings, template);
44
- }
45
- return Fragment.render(template, values);
46
- }
47
-
48
- private static template(strings: TemplateDefinition): Template {
49
- let html = '';
50
- let insideText = true;
51
- for (let i = 0; i < strings.length - 1; i++) {
52
- html += strings[i];
53
- const close = strings[i].lastIndexOf('>');
54
- const open = strings[i].indexOf('<', close + 1);
55
- if (close !== -1 && open === -1) {
56
- insideText = true;
57
- } else if (open !== -1) {
58
- insideText = false;
59
- }
60
- html += insideText ? textMarker : attributeMarker(i);
61
- }
62
- html += strings[strings.length - 1];
63
-
64
- const template = document.createElement('template');
65
- template.innerHTML = html;
66
- const walker =
67
- template.ownerDocument.createTreeWalker(template.content, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, null);
68
- let valueIndex = 0;
69
- const emptyTextNodes = [];
70
- const binds: Bind[] = [];
71
- const nodesToMark = [];
72
- while (walker.nextNode()) {
73
- const node = (walker.currentNode as HTMLElement);
74
- if (node.nodeType === Node.ELEMENT_NODE && node.hasAttributes()) {
75
- if (node.hasAttribute('$')) {
76
- nodesToMark.push(node);
77
- binds.push({elementId: node.getAttribute('$') || ''});
78
- node.removeAttribute('$');
79
- }
80
-
81
- const attributesToRemove = [];
82
- for (let i = 0; i < node.attributes.length; i++) {
83
- const name = node.attributes[i].name;
84
-
85
- if (!attributeMarkerRegex.test(name) && !attributeMarkerRegex.test(node.attributes[i].value)) {
86
- continue;
87
- }
88
-
89
- attributesToRemove.push(name);
90
- nodesToMark.push(node);
91
-
92
- const attr = {
93
- index: valueIndex,
94
- names: name.split(attributeMarkerRegex),
95
- values: node.attributes[i].value.split(attributeMarkerRegex),
96
- };
97
- valueIndex += attr.names.length - 1;
98
- valueIndex += attr.values.length - 1;
99
- const bind: Bind = {
100
- attr,
101
- };
102
- binds.push(bind);
103
- }
104
- for (let i = 0; i < attributesToRemove.length; i++) {
105
- node.removeAttribute(attributesToRemove[i]);
106
- }
107
- }
108
-
109
- if (node.nodeType === Node.TEXT_NODE && getNodeData(node).indexOf(textMarker) !== -1) {
110
- const texts = getNodeData(node).split(textMarkerRegex);
111
- setNodeData(node, texts[texts.length - 1]);
112
- const parentNode = (node.parentNode as HTMLElement);
113
- for (let i = 0; i < texts.length - 1; i++) {
114
- if (texts[i]) {
115
- parentNode.insertBefore(document.createTextNode(texts[i]), node);
116
- }
117
- const nodeToReplace = document.createElement('span');
118
- nodesToMark.push(nodeToReplace);
119
- binds.push({replaceNodeIndex: valueIndex++});
120
- parentNode.insertBefore(nodeToReplace, node);
121
- }
122
- }
123
-
124
- if (node.nodeType === Node.TEXT_NODE &&
125
- (!node.previousSibling || node.previousSibling.nodeType === Node.ELEMENT_NODE) &&
126
- (!node.nextSibling || node.nextSibling.nodeType === Node.ELEMENT_NODE) && /^\s*$/.test(getNodeData(node))) {
127
- emptyTextNodes.push(node);
128
- }
129
- }
130
-
131
- for (let i = 0; i < nodesToMark.length; i++) {
132
- nodesToMark[i].classList.add(generateClassName(i));
133
- }
134
-
135
- for (const emptyTextNode of emptyTextNodes) {
136
- emptyTextNode.remove();
137
- }
138
- return {template, binds};
139
- }
140
-
141
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
142
- private static render(template: Template, values: any[]): Fragment {
143
- const content = template.template.ownerDocument.importNode(template.template.content, true);
144
- const resultElement = (content.firstChild === content.lastChild ? content.firstChild : content) as Element;
145
- const result = new Fragment(resultElement);
146
-
147
- const boundElements = [];
148
- for (let i = 0; i < template.binds.length; i++) {
149
- const className = generateClassName(i);
150
- const element = (content.querySelector('.' + className) as Element);
151
- element.classList.remove(className);
152
- boundElements.push(element);
153
- }
154
-
155
- for (let bindIndex = 0; bindIndex < template.binds.length; bindIndex++) {
156
- const bind = template.binds[bindIndex];
157
- const element = boundElements[bindIndex];
158
- if (bind.elementId !== undefined) {
159
- result.elementsById.set(bind.elementId, element);
160
- } else if (bind.replaceNodeIndex !== undefined) {
161
- const value = values[bind.replaceNodeIndex];
162
- (element.parentNode as HTMLElement).replaceChild(this.nodeForValue(value), element);
163
- } else if (bind.attr !== undefined) {
164
- if (bind.attr.names.length === 2 && bind.attr.values.length === 1 &&
165
- typeof values[bind.attr.index] === 'function') {
166
- values[bind.attr.index].call(null, element);
167
- } else {
168
- let name = bind.attr.names[0];
169
- for (let i = 1; i < bind.attr.names.length; i++) {
170
- name += values[bind.attr.index + i - 1];
171
- name += bind.attr.names[i];
172
- }
173
- if (name) {
174
- let value = bind.attr.values[0];
175
- for (let i = 1; i < bind.attr.values.length; i++) {
176
- value += values[bind.attr.index + bind.attr.names.length - 1 + i - 1];
177
- value += bind.attr.values[i];
178
- }
179
- element.setAttribute(name, value);
180
- }
181
- }
182
- } else {
183
- throw new Error('Unexpected bind');
184
- }
185
- }
186
- return result;
187
- }
188
-
189
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
190
- private static nodeForValue(value: any): Node {
191
- if (value instanceof Node) {
192
- return value;
193
- }
194
- if (value instanceof Fragment) {
195
- return value.#element;
196
- }
197
- if (Array.isArray(value)) {
198
- const node = document.createDocumentFragment();
199
- for (const v of value) {
200
- node.appendChild(this.nodeForValue(v));
201
- }
202
- return node;
203
- }
204
- return document.createTextNode(String(value));
205
- }
206
- }
207
-
208
- export const textMarker = '{{template-text}}';
209
- const textMarkerRegex = /{{template-text}}/;
210
- export const attributeMarker = (index: number): string => 'template-attribute' + index;
211
- const attributeMarkerRegex = /template-attribute\d+/;
212
- const generateClassName = (index: number): string => 'template-class-' + index;
213
- const templateCache = new Map<TemplateDefinition, Template>();
214
-
215
- export const html = (strings: TemplateDefinition, ...vararg: any[]): Element => {
216
- return Fragment.cached(strings, ...vararg).element();
217
- };
218
-
219
- export type TemplateDefinition = string[]|TemplateStringsArray;
220
-
221
- export interface Bind {
222
- elementId?: string;
223
- attr?: {
224
- index: number,
225
- names: string[],
226
- values: string[],
227
- };
228
- replaceNodeIndex?: number;
229
- }
230
-
231
- export interface Template {
232
- template: HTMLTemplateElement;
233
- binds: Bind[];
234
- }