sveld 0.22.3 → 0.22.5
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/lib/ComponentParser.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ interface ForwardedEvent {
|
|
|
29
29
|
name: string;
|
|
30
30
|
element: ComponentInlineElement | ComponentElement;
|
|
31
31
|
description?: string;
|
|
32
|
+
detail?: string;
|
|
32
33
|
}
|
|
33
34
|
interface DispatchedEvent {
|
|
34
35
|
type: "dispatched";
|
|
@@ -92,8 +93,8 @@ export default class ComponentParser {
|
|
|
92
93
|
private static assignValue;
|
|
93
94
|
private static formatComment;
|
|
94
95
|
/**
|
|
95
|
-
* Finds the last
|
|
96
|
-
*
|
|
96
|
+
* Finds the last comment from an array of leading comments.
|
|
97
|
+
* TypeScript directives are stripped before parsing, so we can safely take the last comment.
|
|
97
98
|
*/
|
|
98
99
|
private static findJSDocComment;
|
|
99
100
|
private sourceAtPos;
|
|
@@ -105,6 +106,10 @@ export default class ComponentParser {
|
|
|
105
106
|
private addDispatchedEvent;
|
|
106
107
|
private parseCustomTypes;
|
|
107
108
|
cleanup(): void;
|
|
109
|
+
/**
|
|
110
|
+
* Strips TypeScript directive comments from script blocks only.
|
|
111
|
+
*/
|
|
112
|
+
private static stripTypeScriptDirectivesFromScripts;
|
|
108
113
|
parseSvelteComponent(source: string, diagnostics: ComponentParserDiagnostics): ParsedComponent;
|
|
109
114
|
}
|
|
110
115
|
export {};
|
package/lib/ComponentParser.js
CHANGED
|
@@ -78,19 +78,13 @@ class ComponentParser {
|
|
|
78
78
|
return formatted_comment;
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
|
-
* Finds the last
|
|
82
|
-
*
|
|
81
|
+
* Finds the last comment from an array of leading comments.
|
|
82
|
+
* TypeScript directives are stripped before parsing, so we can safely take the last comment.
|
|
83
83
|
*/
|
|
84
84
|
static findJSDocComment(leadingComments) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// JSDoc comments are block comments (/* */) where the value starts with *
|
|
89
|
-
if (comment.type === "Block" && comment.value.trimStart().startsWith("*")) {
|
|
90
|
-
return comment;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return undefined;
|
|
85
|
+
if (!leadingComments || leadingComments.length === 0)
|
|
86
|
+
return undefined;
|
|
87
|
+
return leadingComments[leadingComments.length - 1];
|
|
94
88
|
}
|
|
95
89
|
sourceAtPos(start, end) {
|
|
96
90
|
return this.source?.slice(start, end);
|
|
@@ -107,8 +101,6 @@ class ComponentParser {
|
|
|
107
101
|
return;
|
|
108
102
|
if (this.props.has(prop_name)) {
|
|
109
103
|
const existing_slot = this.props.get(prop_name);
|
|
110
|
-
if (!existing_slot)
|
|
111
|
-
return;
|
|
112
104
|
this.props.set(prop_name, {
|
|
113
105
|
...existing_slot,
|
|
114
106
|
...data,
|
|
@@ -123,8 +115,6 @@ class ComponentParser {
|
|
|
123
115
|
return;
|
|
124
116
|
if (this.moduleExports.has(prop_name)) {
|
|
125
117
|
const existing_slot = this.moduleExports.get(prop_name);
|
|
126
|
-
if (!existing_slot)
|
|
127
|
-
return;
|
|
128
118
|
this.moduleExports.set(prop_name, {
|
|
129
119
|
...existing_slot,
|
|
130
120
|
...data,
|
|
@@ -147,8 +137,6 @@ class ComponentParser {
|
|
|
147
137
|
const description = slot_description?.split("-").pop()?.trim();
|
|
148
138
|
if (this.slots.has(name)) {
|
|
149
139
|
const existing_slot = this.slots.get(name);
|
|
150
|
-
if (!existing_slot)
|
|
151
|
-
return;
|
|
152
140
|
this.slots.set(name, {
|
|
153
141
|
...existing_slot,
|
|
154
142
|
fallback,
|
|
@@ -262,14 +250,27 @@ class ComponentParser {
|
|
|
262
250
|
this.generics = null;
|
|
263
251
|
this.bindings.clear();
|
|
264
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Strips TypeScript directive comments from script blocks only.
|
|
255
|
+
*/
|
|
256
|
+
static stripTypeScriptDirectivesFromScripts(source) {
|
|
257
|
+
// Find all script blocks and strip directives only from within them
|
|
258
|
+
return source.replace(/(<script[^>]*>)([\s\S]*?)(<\/script>)/gi, (_match, openTag, scriptContent, closeTag) => {
|
|
259
|
+
// Remove TypeScript directives from script content only
|
|
260
|
+
const cleanedContent = scriptContent.replace(/\/\/\s*@ts-[^\n\r]*/g, "");
|
|
261
|
+
return openTag + cleanedContent + closeTag;
|
|
262
|
+
});
|
|
263
|
+
}
|
|
265
264
|
parseSvelteComponent(source, diagnostics) {
|
|
266
265
|
if (this.options?.verbose) {
|
|
267
266
|
console.log(`[parsing] "${diagnostics.moduleName}" ${diagnostics.filePath}`);
|
|
268
267
|
}
|
|
269
268
|
this.cleanup();
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
this.
|
|
269
|
+
// Strip TypeScript directives from script blocks only to prevent interference with JSDoc
|
|
270
|
+
const cleanedSource = ComponentParser.stripTypeScriptDirectivesFromScripts(source);
|
|
271
|
+
this.source = cleanedSource;
|
|
272
|
+
this.compiled = (0, compiler_1.compile)(cleanedSource);
|
|
273
|
+
this.parsed = (0, compiler_1.parse)(cleanedSource);
|
|
273
274
|
this.collectReactiveVars();
|
|
274
275
|
this.parseCustomTypes();
|
|
275
276
|
if (this.parsed?.module) {
|
|
@@ -554,8 +555,6 @@ class ComponentParser {
|
|
|
554
555
|
const element_name = parent.name;
|
|
555
556
|
if (this.bindings.has(prop_name)) {
|
|
556
557
|
const existing_bindings = this.bindings.get(prop_name);
|
|
557
|
-
if (!existing_bindings)
|
|
558
|
-
return;
|
|
559
558
|
if (!existing_bindings.elements.includes(element_name)) {
|
|
560
559
|
this.bindings.set(prop_name, {
|
|
561
560
|
...existing_bindings,
|
|
@@ -601,12 +600,19 @@ class ComponentParser {
|
|
|
601
600
|
if (event && event.type === "dispatched" && !actuallyDispatchedEvents.has(eventName)) {
|
|
602
601
|
const description = this.eventDescriptions.get(eventName);
|
|
603
602
|
const event_description = description?.split("-").pop()?.trim();
|
|
604
|
-
|
|
603
|
+
const forwardedEvent = {
|
|
605
604
|
type: "forwarded",
|
|
606
605
|
name: eventName,
|
|
607
606
|
element: element,
|
|
608
607
|
description: event_description,
|
|
609
|
-
}
|
|
608
|
+
};
|
|
609
|
+
// Preserve detail type if it was explicitly set in @event tag
|
|
610
|
+
// Note: "null" is a valid explicit type (e.g., @event {null} eventname)
|
|
611
|
+
// Only skip if detail is truly undefined or the string "undefined"
|
|
612
|
+
if (event.detail !== undefined && event.detail !== "undefined") {
|
|
613
|
+
forwardedEvent.detail = event.detail;
|
|
614
|
+
}
|
|
615
|
+
this.events.set(eventName, forwardedEvent);
|
|
610
616
|
}
|
|
611
617
|
});
|
|
612
618
|
return {
|
|
@@ -168,6 +168,108 @@ function genEventDef(def) {
|
|
|
168
168
|
return detail;
|
|
169
169
|
return `CustomEvent<${detail}>`;
|
|
170
170
|
};
|
|
171
|
+
// Check if an event name is a standard DOM event that exists in WindowEventMap
|
|
172
|
+
const isStandardDomEvent = (eventName) => {
|
|
173
|
+
// Standard DOM events that should use WindowEventMap
|
|
174
|
+
const standardEvents = new Set([
|
|
175
|
+
// Mouse events
|
|
176
|
+
"click",
|
|
177
|
+
"dblclick",
|
|
178
|
+
"mousedown",
|
|
179
|
+
"mouseup",
|
|
180
|
+
"mousemove",
|
|
181
|
+
"mouseover",
|
|
182
|
+
"mouseout",
|
|
183
|
+
"mouseenter",
|
|
184
|
+
"mouseleave",
|
|
185
|
+
"contextmenu",
|
|
186
|
+
"wheel",
|
|
187
|
+
// Keyboard events
|
|
188
|
+
"keydown",
|
|
189
|
+
"keyup",
|
|
190
|
+
"keypress",
|
|
191
|
+
// Form events
|
|
192
|
+
"submit",
|
|
193
|
+
"change",
|
|
194
|
+
"input",
|
|
195
|
+
"focus",
|
|
196
|
+
"blur",
|
|
197
|
+
"focusin",
|
|
198
|
+
"focusout",
|
|
199
|
+
"reset",
|
|
200
|
+
"select",
|
|
201
|
+
// Touch events
|
|
202
|
+
"touchstart",
|
|
203
|
+
"touchend",
|
|
204
|
+
"touchmove",
|
|
205
|
+
"touchcancel",
|
|
206
|
+
// Drag events
|
|
207
|
+
"drag",
|
|
208
|
+
"dragstart",
|
|
209
|
+
"dragend",
|
|
210
|
+
"dragover",
|
|
211
|
+
"dragenter",
|
|
212
|
+
"dragleave",
|
|
213
|
+
"drop",
|
|
214
|
+
// Pointer events
|
|
215
|
+
"pointerdown",
|
|
216
|
+
"pointerup",
|
|
217
|
+
"pointermove",
|
|
218
|
+
"pointerover",
|
|
219
|
+
"pointerout",
|
|
220
|
+
"pointerenter",
|
|
221
|
+
"pointerleave",
|
|
222
|
+
"pointercancel",
|
|
223
|
+
"gotpointercapture",
|
|
224
|
+
"lostpointercapture",
|
|
225
|
+
// Media events
|
|
226
|
+
"play",
|
|
227
|
+
"pause",
|
|
228
|
+
"ended",
|
|
229
|
+
"volumechange",
|
|
230
|
+
"timeupdate",
|
|
231
|
+
"loadeddata",
|
|
232
|
+
"loadedmetadata",
|
|
233
|
+
"canplay",
|
|
234
|
+
"canplaythrough",
|
|
235
|
+
"seeking",
|
|
236
|
+
"seeked",
|
|
237
|
+
"playing",
|
|
238
|
+
"waiting",
|
|
239
|
+
"stalled",
|
|
240
|
+
"suspend",
|
|
241
|
+
"abort",
|
|
242
|
+
"error",
|
|
243
|
+
"emptied",
|
|
244
|
+
"ratechange",
|
|
245
|
+
"durationchange",
|
|
246
|
+
"loadstart",
|
|
247
|
+
"progress",
|
|
248
|
+
"loadend",
|
|
249
|
+
// Animation/Transition events
|
|
250
|
+
"animationstart",
|
|
251
|
+
"animationend",
|
|
252
|
+
"animationiteration",
|
|
253
|
+
"animationcancel",
|
|
254
|
+
"transitionstart",
|
|
255
|
+
"transitionend",
|
|
256
|
+
"transitionrun",
|
|
257
|
+
"transitioncancel",
|
|
258
|
+
// Other events
|
|
259
|
+
"scroll",
|
|
260
|
+
"resize",
|
|
261
|
+
"load",
|
|
262
|
+
"unload",
|
|
263
|
+
"beforeunload",
|
|
264
|
+
"cut",
|
|
265
|
+
"copy",
|
|
266
|
+
"paste",
|
|
267
|
+
"compositionstart",
|
|
268
|
+
"compositionupdate",
|
|
269
|
+
"compositionend",
|
|
270
|
+
]);
|
|
271
|
+
return standardEvents.has(eventName);
|
|
272
|
+
};
|
|
171
273
|
if (def.events.length === 0)
|
|
172
274
|
return EMPTY_EVENTS;
|
|
173
275
|
const events_map = def.events
|
|
@@ -176,7 +278,33 @@ function genEventDef(def) {
|
|
|
176
278
|
if (event.description) {
|
|
177
279
|
description = `/** ${event.description} */\n`;
|
|
178
280
|
}
|
|
179
|
-
|
|
281
|
+
let eventType;
|
|
282
|
+
if (event.type === "dispatched") {
|
|
283
|
+
eventType = createDispatchedEvent(event.detail);
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
// For forwarded events, determine the type based on @event JSDoc and element/event type
|
|
287
|
+
const elementName = typeof event.element === "string" ? event.element : event.element.name;
|
|
288
|
+
const isComponent = elementName && /^[A-Z]/.test(elementName);
|
|
289
|
+
const isStandardEvent = !isComponent || isStandardDomEvent(event.name);
|
|
290
|
+
// Check if there's an explicit non-null detail type from @event JSDoc
|
|
291
|
+
// Note: detail="null" on standard DOM events is treated as "not explicitly typed"
|
|
292
|
+
// because @event click (without {type}) defaults to null but shouldn't override WindowEventMap
|
|
293
|
+
const hasExplicitNonNullDetail = event.detail !== undefined && event.detail !== "undefined" && !(event.detail === "null" && isStandardEvent);
|
|
294
|
+
if (hasExplicitNonNullDetail) {
|
|
295
|
+
// If @event tag explicitly provides a non-null detail type, always use it (highest priority)
|
|
296
|
+
eventType = createDispatchedEvent(event.detail);
|
|
297
|
+
}
|
|
298
|
+
else if (isStandardEvent) {
|
|
299
|
+
// Standard DOM event (native element or standard event name) without explicit type
|
|
300
|
+
eventType = `${mapEvent()}["${event.name}"]`;
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
// Custom event from component with no explicit type or explicit null
|
|
304
|
+
eventType = event.detail === "null" ? createDispatchedEvent("null") : createDispatchedEvent();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return `${description}${clampKey(event.name)}: ${eventType};\n`;
|
|
180
308
|
})
|
|
181
309
|
.join("");
|
|
182
310
|
return `{${events_map}}`;
|