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.
- package/.stylelintrc.json +3 -1
- package/docs/ui_engineering.md +0 -36
- package/front_end/core/common/Console.ts +6 -6
- package/front_end/core/common/Settings.ts +12 -8
- package/front_end/core/host/UserMetrics.ts +0 -1
- package/front_end/core/root/DevToolsContext.ts +13 -7
- package/front_end/core/root/ExperimentNames.ts +0 -1
- package/front_end/core/sdk/CPUThrottlingManager.ts +12 -9
- package/front_end/core/sdk/DOMModel.ts +206 -0
- package/front_end/core/sdk/FrameManager.ts +7 -8
- package/front_end/core/sdk/SourceMapScopesInfo.ts +1 -1
- package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +1 -1
- package/front_end/entrypoints/greendev_floaty/floaty.css +2 -2
- package/front_end/entrypoints/main/MainImpl.ts +1 -7
- package/front_end/foundation/Universe.ts +22 -11
- package/front_end/generated/InspectorBackendCommands.ts +7 -0
- package/front_end/generated/protocol-mapping.d.ts +16 -0
- package/front_end/generated/protocol-proxy-api.d.ts +25 -0
- package/front_end/generated/protocol.ts +71 -0
- package/front_end/models/ai_assistance/AiConversation.ts +6 -95
- package/front_end/models/ai_assistance/ConversationHandler.ts +1 -1
- package/front_end/models/ai_assistance/agents/AiAgent.ts +11 -1
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +14 -0
- package/front_end/models/ai_code_completion/AiCodeCompletion.ts +1 -1
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +3 -0
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +73 -35
- package/front_end/panels/ai_assistance/components/WalkthroughView.ts +30 -6
- package/front_end/panels/ai_assistance/components/chatMessage.css +4 -2
- package/front_end/panels/application/IndexedDBModel.ts +2 -4
- package/front_end/panels/application/ServiceWorkersView.ts +3 -11
- package/front_end/panels/developer_resources/DeveloperResourcesListView.ts +1 -1
- package/front_end/panels/elements/ComputedStyleWidget.ts +25 -16
- package/front_end/panels/elements/ElementsPanel.ts +0 -1
- package/front_end/panels/elements/StandaloneStylesContainer.ts +48 -12
- package/front_end/panels/elements/StylePropertiesSection.ts +97 -1
- package/front_end/panels/elements/StylePropertyTreeElement.ts +27 -5
- package/front_end/panels/elements/StylesContainer.ts +1 -0
- package/front_end/panels/elements/StylesSidebarPane.ts +48 -32
- package/front_end/panels/elements/nodeStackTraceWidget.css +1 -1
- package/front_end/panels/elements/stylePropertiesTreeOutline.css +1 -1
- package/front_end/panels/emulation/DeviceModeToolbar.ts +171 -89
- package/front_end/panels/greendev/GreenDevPanel.css +2 -2
- package/front_end/panels/issues/AffectedPermissionElementsView.ts +9 -6
- package/front_end/panels/lighthouse/LighthouseController.ts +13 -5
- package/front_end/panels/lighthouse/LighthousePanel.ts +22 -5
- package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +11 -3
- package/front_end/panels/recorder/components/StepView.ts +0 -2
- package/front_end/panels/recorder/components/stepView.css +13 -13
- package/front_end/panels/recorder/components/timelineSection.css +6 -7
- package/front_end/panels/settings/components/SyncSection.ts +4 -13
- package/front_end/panels/timeline/CompatibilityTracksAppender.ts +2 -15
- package/front_end/panels/timeline/RecordingMetadata.ts +1 -1
- package/front_end/panels/timeline/TimelineDetailsView.ts +2 -2
- package/front_end/panels/timeline/TimelinePanel.ts +18 -0
- package/front_end/panels/timeline/TimelineUIUtils.ts +7 -4
- package/front_end/panels/timeline/components/CWVMetrics.ts +339 -0
- package/front_end/panels/timeline/components/Sidebar.ts +17 -0
- package/front_end/panels/timeline/components/SidebarInsightsTab.ts +10 -0
- package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +25 -267
- package/front_end/panels/timeline/components/Utils.ts +2 -2
- package/front_end/panels/timeline/components/components.ts +2 -0
- package/front_end/panels/timeline/components/cwvMetrics.css +107 -0
- package/front_end/panels/timeline/components/sidebarSingleInsightSet.css +0 -102
- package/front_end/panels/timeline/timeline-meta.ts +11 -0
- package/front_end/panels/timeline/utils/Helpers.ts +5 -1
- package/front_end/panels/webauthn/webauthnPane.css +1 -1
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/puppeteer/README.chromium +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts +7 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.d.ts +1 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.js +3 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.d.ts +1 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js +6 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.d.ts +1 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js +5 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +7 -0
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +14 -3
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts +7 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.d.ts +1 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.js +3 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.d.ts +1 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js +6 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.d.ts +1 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js +5 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +2 -2
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +2 -2
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/types.d.ts +7 -0
- package/front_end/third_party/puppeteer/package/package.json +1 -1
- package/front_end/third_party/puppeteer/package/src/api/Page.ts +8 -0
- package/front_end/third_party/puppeteer/package/src/bidi/Page.ts +4 -0
- package/front_end/third_party/puppeteer/package/src/cdp/Browser.ts +7 -0
- package/front_end/third_party/puppeteer/package/src/cdp/Page.ts +6 -0
- package/front_end/third_party/puppeteer/package/src/revisions.ts +2 -2
- package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
- package/front_end/ui/components/text_editor/AutocompleteHistory.ts +89 -5
- package/front_end/ui/components/tree_outline/TreeOutline.ts +1 -1
- package/front_end/ui/legacy/ProgressIndicator.ts +1 -1
- package/front_end/ui/legacy/Toolbar.ts +1 -1
- package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +14 -7
- package/front_end/ui/legacy/legacy.ts +0 -2
- package/front_end/ui/visual_logging/KnownContextValues.ts +5 -0
- package/inspector_overlay/loadCSS.rollup.js +2 -2
- package/inspector_overlay/tool_source_order.css +1 -0
- package/package.json +1 -1
- 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
|
|
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
|
-
|
|
87
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
@@ -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
|
|
642
|
+
return element;
|
|
641
643
|
}
|
|
642
644
|
if (/^\s|\s$|^$|\n/.test(name)) {
|
|
643
|
-
|
|
645
|
+
element.textContent = `"${name.replace(/\n/g, '\u21B5')}"`;
|
|
646
|
+
return element;
|
|
644
647
|
}
|
|
645
648
|
if (isPrivate) {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
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',
|
package/package.json
CHANGED
|
@@ -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
|
-
}
|