@cssdoc/core 0.5.3 → 0.6.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.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as CssState, B as ModifierMatcher, C as CssPart, D as CssReleaseStage, E as CssRelated, F as DEFAULT_STATE_PSEUDO_CLASSES, I as MODIFIER_PRESETS, L as ModifierConvention, M as ParseOptions, N as StructureNode, O as CssSlot, P as DEFAULT_MODIFIER_CONVENTION, R as ModifierConventionInput, S as CssParse, T as CssRecordKind, V as resolveModifierConvention, _ as CssCondition, a as DocModifier, b as CssLayer, c as parseDocComment, d as stripCommentFraming, f as CssDocConfiguration, g as CssAnimation, h as CssDocTagDefinitionOptions, i as DocCssProperty, j as CssTokenConsumed, k as CssSource, l as parseStructure, m as CssDocTagDefinition, n as toMermaid, o as ParsedDoc, p as CssDocSyntaxKind, r as DocCondition, s as RECORD_TAGS, t as toJson, u as recordNameOf, v as CssDocEntry, w as CssPropertyDeclared, x as CssModifier, y as CssFunction, z as ModifierHit } from "./lite-DtKEwRkS.mjs";
1
+ import { A as CssSource, B as ModifierHit, C as CssParse, D as CssRelated, E as CssRecordKind, F as DEFAULT_MODIFIER_CONVENTION, H as resolveModifierConvention, I as DEFAULT_STATE_PSEUDO_CLASSES, L as MODIFIER_PRESETS, M as CssTokenConsumed, N as ParseOptions, O as CssReleaseStage, P as StructureNode, R as ModifierConvention, S as CssModifier, T as CssPropertyDeclared, V as ModifierMatcher, _ as CssAnimation, a as DocModifier, b as CssFunction, c as parseDocComment, d as stripCommentFraming, f as CssDocConfiguration, g as InlineCommentMode, h as CssDocTagDefinitionOptions, i as DocCssProperty, j as CssState, k as CssSlot, l as parseStructure, m as CssDocTagDefinition, n as toMermaid, o as ParsedDoc, p as CssDocSyntaxKind, r as DocCondition, s as RECORD_TAGS, t as toJson, u as recordNameOf, v as CssCondition, w as CssPart, x as CssLayer, y as CssDocEntry, z as ModifierConventionInput } from "./lite-DzhifijA.mjs";
2
2
 
3
3
  //#region src/parse.d.ts
4
4
  /**
@@ -20,4 +20,4 @@ import { A as CssState, B as ModifierMatcher, C as CssPart, D as CssReleaseStage
20
20
  */
21
21
  declare function parseCssDocs(css: string, options?: ParseOptions): CssDocEntry[];
22
22
  //#endregion
23
- export { type CssAnimation, type CssCondition, CssDocConfiguration, type CssDocEntry, type CssDocSyntaxKind, CssDocTagDefinition, type CssDocTagDefinitionOptions, type CssFunction, type CssLayer, type CssModifier, type CssParse, type CssPart, type CssPropertyDeclared, type CssRecordKind, type CssRelated, type CssReleaseStage, type CssSlot, type CssSource, type CssState, type CssTokenConsumed, DEFAULT_MODIFIER_CONVENTION, DEFAULT_STATE_PSEUDO_CLASSES, type DocCondition, type DocCssProperty, type DocModifier, MODIFIER_PRESETS, type ModifierConvention, type ModifierConventionInput, type ModifierHit, ModifierMatcher, type ParseOptions, type ParsedDoc, RECORD_TAGS, type StructureNode, parseCssDocs, parseDocComment, parseStructure, recordNameOf, resolveModifierConvention, stripCommentFraming, toJson, toMermaid };
23
+ export { type CssAnimation, type CssCondition, CssDocConfiguration, type CssDocEntry, type CssDocSyntaxKind, CssDocTagDefinition, type CssDocTagDefinitionOptions, type CssFunction, type CssLayer, type CssModifier, type CssParse, type CssPart, type CssPropertyDeclared, type CssRecordKind, type CssRelated, type CssReleaseStage, type CssSlot, type CssSource, type CssState, type CssTokenConsumed, DEFAULT_MODIFIER_CONVENTION, DEFAULT_STATE_PSEUDO_CLASSES, type DocCondition, type DocCssProperty, type DocModifier, type InlineCommentMode, MODIFIER_PRESETS, type ModifierConvention, type ModifierConventionInput, type ModifierHit, ModifierMatcher, type ParseOptions, type ParsedDoc, RECORD_TAGS, type StructureNode, parseCssDocs, parseDocComment, parseStructure, recordNameOf, resolveModifierConvention, stripCommentFraming, toJson, toMermaid };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as parseStructure, c as CssDocConfiguration, d as DEFAULT_STATE_PSEUDO_CLASSES, f as MODIFIER_PRESETS, i as parseDocComment, l as CssDocTagDefinition, m as resolveModifierConvention, n as toMermaid, o as recordNameOf, p as ModifierMatcher, r as RECORD_TAGS, s as stripCommentFraming, t as toJson, u as DEFAULT_MODIFIER_CONVENTION } from "./lite-HEYG01Eo.mjs";
1
+ import { a as parseStructure, c as CssDocConfiguration, d as DEFAULT_STATE_PSEUDO_CLASSES, f as MODIFIER_PRESETS, i as parseDocComment, l as CssDocTagDefinition, m as resolveModifierConvention, n as toMermaid, o as recordNameOf, p as ModifierMatcher, r as RECORD_TAGS, s as stripCommentFraming, t as toJson, u as DEFAULT_MODIFIER_CONVENTION } from "./lite-CEVmwLGF.mjs";
2
2
  import postcss from "postcss";
3
3
  //#region src/parse.ts
4
4
  /**
@@ -38,12 +38,20 @@ function collectFunction(node, acc) {
38
38
  });
39
39
  }
40
40
  /** Extract every fact from one record's nodes into `acc`. */
41
- function collect(nodes, acc, matcher, baseNoDot, prefixNoDot, inScope) {
41
+ function collect(nodes, acc, matcher, baseNoDot, prefixNoDot, inScope, inlineMode) {
42
42
  let pendingCanonical;
43
+ let pendingDescription;
43
44
  for (const node of nodes) {
44
45
  if (node.type === "comment") {
45
46
  const dep = node.text.match(/@deprecated.*?use\s+(\.[\w-]+|\[[^\]]*\])/u);
46
47
  if (dep) pendingCanonical = matcher.normalizeMember(dep[1]);
48
+ else if (/(^|\s)@todo\b/u.test(node.text)) {
49
+ const todo = stripCommentFraming(node.text).replace(/^@todo\b[:\s]*/u, "").trim();
50
+ if (todo) acc.todos.push(todo);
51
+ } else if (!/^\s*cssdoc-/u.test(node.text) && inlineMode !== "ignore") {
52
+ const text = stripCommentFraming(node.text).replace(/\s+/gu, " ").trim();
53
+ if (text) pendingDescription = text;
54
+ }
47
55
  continue;
48
56
  }
49
57
  if (node.type === "decl") {
@@ -98,7 +106,7 @@ function collect(nodes, acc, matcher, baseNoDot, prefixNoDot, inScope) {
98
106
  type: "media",
99
107
  query: node.params.trim()
100
108
  });
101
- if (node.nodes) collect(node.nodes, acc, matcher, baseNoDot, prefixNoDot, inScope || node.name === "scope");
109
+ if (node.nodes) collect(node.nodes, acc, matcher, baseNoDot, prefixNoDot, inScope || node.name === "scope", inlineMode);
102
110
  continue;
103
111
  }
104
112
  if (node.type === "rule") {
@@ -112,6 +120,10 @@ function collect(nodes, acc, matcher, baseNoDot, prefixNoDot, inScope) {
112
120
  kind: "pseudo-class"
113
121
  });
114
122
  for (const sp of selector.matchAll(/::part\(\s*([\w-]+)\s*\)/gu)) if (!acc.shadowParts.has(sp[1])) acc.shadowParts.set(sp[1], { name: sp[1] });
123
+ for (const pe of matcher.pseudoElementsIn(selector)) if (!acc.pseudoElements.has(pe.name)) acc.pseudoElements.set(pe.name, {
124
+ name: pe.name,
125
+ description: pendingDescription
126
+ });
115
127
  const bare = selector.replace(/::?[\w-]+(\([^)]*\))?/gu, "");
116
128
  const mods = matcher.modifiersIn(bare, baseNoDot);
117
129
  const modNames = new Set(mods.map((mod) => mod.name));
@@ -123,10 +135,12 @@ function collect(nodes, acc, matcher, baseNoDot, prefixNoDot, inScope) {
123
135
  ...mod.pattern ? { pattern: true } : {}
124
136
  };
125
137
  if (pendingCanonical) entry.deprecated = { canonical: pendingCanonical };
138
+ if (pendingDescription && !entry.description) entry.description = pendingDescription;
126
139
  acc.modifiers.set(mod.name, entry);
127
140
  }
128
141
  for (const el of matcher.elementsIn(bare, baseNoDot)) {
129
142
  const part = acc.parts.get(el.name) ?? { name: el.name };
143
+ if (pendingDescription && !part.description) part.description = pendingDescription;
130
144
  for (const m of el.modifiers) {
131
145
  part.modifiers ??= [];
132
146
  if (!part.modifiers.some((x) => x.name === m.name)) part.modifiers.push({
@@ -145,17 +159,33 @@ function collect(nodes, acc, matcher, baseNoDot, prefixNoDot, inScope) {
145
159
  const part = m[1];
146
160
  if (modNames.has(part)) continue;
147
161
  if (prefixNoDot && part.startsWith(prefixNoDot)) continue;
148
- if (!acc.parts.has(part)) acc.parts.set(part, { name: part });
162
+ if (!acc.parts.has(part)) acc.parts.set(part, {
163
+ name: part,
164
+ description: pendingDescription
165
+ });
149
166
  }
150
167
  }
151
168
  for (const child of node.nodes ?? []) if (child.type === "decl") for (const m of child.value.matchAll(VAR_RE)) acc.consumed.add(m[1]);
152
169
  pendingCanonical = void 0;
170
+ pendingDescription = void 0;
153
171
  }
154
172
  }
155
173
  }
156
174
  const byName = (a, b) => a.name.localeCompare(b.name);
175
+ /**
176
+ * Combine a member's authored tag prose with an inline `/* … *\/` comment, per the configured
177
+ * {@link InlineCommentMode}. Either side may be absent; when both are present, `append`/`prepend` join
178
+ * them (tag-first / comment-first), `replace` takes the comment, and `ignore` keeps only the tag.
179
+ */
180
+ function combineDescription(mode, tag, inline) {
181
+ if (mode === "ignore") return tag;
182
+ if (!tag) return inline;
183
+ if (!inline) return tag;
184
+ if (mode === "replace") return inline;
185
+ return mode === "prepend" ? `${inline}\n\n${tag}` : `${tag}\n\n${inline}`;
186
+ }
157
187
  /** Build one entry from its record name, doc comment, and nodes. */
158
- function buildEntry(name, doc, nodes, matcher, source) {
188
+ function buildEntry(name, doc, nodes, matcher, inlineMode, source) {
159
189
  let className = doc.className ?? "";
160
190
  if (!className) {
161
191
  const bare = nodes.filter((n) => n.type === "rule").map((n) => n.selector.trim()).filter((sel) => /^\.[a-z][\w-]*$/u.test(sel));
@@ -169,15 +199,17 @@ function buildEntry(name, doc, nodes, matcher, source) {
169
199
  modifiers: /* @__PURE__ */ new Map(),
170
200
  parts: /* @__PURE__ */ new Map(),
171
201
  shadowParts: /* @__PURE__ */ new Map(),
202
+ pseudoElements: /* @__PURE__ */ new Map(),
172
203
  states: /* @__PURE__ */ new Map(),
173
204
  consumed: /* @__PURE__ */ new Set(),
174
205
  declared: /* @__PURE__ */ new Map(),
175
206
  functions: /* @__PURE__ */ new Map(),
176
207
  animations: /* @__PURE__ */ new Map(),
177
208
  layers: /* @__PURE__ */ new Map(),
178
- conditions: []
209
+ conditions: [],
210
+ todos: []
179
211
  };
180
- collect(nodes, acc, matcher, className.replace(/^\./u, ""), className.endsWith(name) ? className.slice(1, className.length - name.length) : "", false);
212
+ collect(nodes, acc, matcher, className.replace(/^\./u, ""), className.endsWith(name) ? className.slice(1, className.length - name.length) : "", false, inlineMode);
181
213
  for (const [modName, mdoc] of doc.modifiers) {
182
214
  const existing = acc.modifiers.get(modName);
183
215
  const dep = mdoc.deprecated || mdoc.deprecatedCanonical || mdoc.deprecatedFlag ? {
@@ -185,7 +217,7 @@ function buildEntry(name, doc, nodes, matcher, source) {
185
217
  ...mdoc.deprecatedCanonical ? { canonical: mdoc.deprecatedCanonical } : {}
186
218
  } : void 0;
187
219
  if (existing) {
188
- if (mdoc.description) existing.description = mdoc.description;
220
+ existing.description = combineDescription(inlineMode, mdoc.description, existing.description);
189
221
  if (dep) existing.deprecated = {
190
222
  ...existing.deprecated,
191
223
  ...dep
@@ -204,7 +236,7 @@ function buildEntry(name, doc, nodes, matcher, source) {
204
236
  }
205
237
  for (const [part, description] of doc.parts) {
206
238
  const existing = acc.parts.get(part);
207
- if (existing) existing.description = description || existing.description;
239
+ if (existing) existing.description = combineDescription(inlineMode, description || void 0, existing.description);
208
240
  else acc.parts.set(part, {
209
241
  name: part,
210
242
  description
@@ -218,6 +250,14 @@ function buildEntry(name, doc, nodes, matcher, source) {
218
250
  description: description || void 0
219
251
  });
220
252
  }
253
+ for (const [pseudo, description] of doc.pseudoElements) {
254
+ const existing = acc.pseudoElements.get(pseudo);
255
+ if (existing) existing.description = combineDescription(inlineMode, description || void 0, existing.description);
256
+ else acc.pseudoElements.set(pseudo, {
257
+ name: pseudo,
258
+ description: description || void 0
259
+ });
260
+ }
221
261
  for (const [rawState, description] of doc.cssStates) {
222
262
  const isPseudo = rawState.startsWith(":");
223
263
  const state = isPseudo ? rawState.slice(1) : rawState;
@@ -284,6 +324,17 @@ function buildEntry(name, doc, nodes, matcher, source) {
284
324
  description: description || void 0
285
325
  });
286
326
  }
327
+ if (doc.wrappers.size && doc.structure?.length) {
328
+ const applyWrappers = (nodes) => {
329
+ for (const node of nodes) {
330
+ const cls = node.selector.match(/\.([\w-]+)/u)?.[1];
331
+ const description = cls ? doc.wrappers.get(cls) : void 0;
332
+ if (description) node.description = description;
333
+ applyWrappers(node.children);
334
+ }
335
+ };
336
+ applyWrappers(doc.structure);
337
+ }
287
338
  return {
288
339
  name,
289
340
  kind: doc.kind ?? "component",
@@ -301,11 +352,13 @@ function buildEntry(name, doc, nodes, matcher, source) {
301
352
  modifiers: [...p.modifiers].sort(byName)
302
353
  } : p),
303
354
  shadowParts: [...acc.shadowParts.values()].sort(byName),
355
+ pseudoElements: [...acc.pseudoElements.values()].sort(byName),
304
356
  states: [...acc.states.values()].sort(byName),
305
357
  slots: [...doc.slots].map(([slotName, description]) => ({
306
358
  name: slotName,
307
359
  description: description || void 0
308
360
  })).sort(byName),
361
+ todos: [...doc.todos, ...acc.todos],
309
362
  cssPropertiesConsumed: [...consumedTokens.values()].sort(byName),
310
363
  cssPropertiesDeclared: [...acc.declared.values()].sort(byName),
311
364
  functions: [...acc.functions.values()].sort(byName),
@@ -383,7 +436,7 @@ function parseCssDocs(css, options = {}) {
383
436
  }
384
437
  if (current) current.nodes.push(node);
385
438
  }
386
- return records.map((r) => buildEntry(r.name, r.doc, r.nodes, matcher, r.source));
439
+ return records.map((r) => buildEntry(r.name, r.doc, r.nodes, matcher, configuration.inlineComments, r.source));
387
440
  }
388
441
  //#endregion
389
442
  export { CssDocConfiguration, CssDocTagDefinition, DEFAULT_MODIFIER_CONVENTION, DEFAULT_STATE_PSEUDO_CLASSES, MODIFIER_PRESETS, ModifierMatcher, RECORD_TAGS, parseCssDocs, parseDocComment, parseStructure, recordNameOf, resolveModifierConvention, stripCommentFraming, toJson, toMermaid };
@@ -22,6 +22,23 @@ const DEFAULT_STATE_PSEUDO_CLASSES = [
22
22
  "in-range",
23
23
  "out-of-range"
24
24
  ];
25
+ /**
26
+ * The native pseudo-elements cssdoc documents by default when a component styles them. A curated set of
27
+ * the standard ones — vendor/experimental pseudo-elements (`::-webkit-*`) are left out so incidental
28
+ * rules don't become documented API. `::part()`/`::slotted()` are handled separately (shadow parts).
29
+ */
30
+ const DEFAULT_PSEUDO_ELEMENTS = [
31
+ "before",
32
+ "after",
33
+ "marker",
34
+ "placeholder",
35
+ "selection",
36
+ "backdrop",
37
+ "first-line",
38
+ "first-letter",
39
+ "file-selector-button",
40
+ "details-content"
41
+ ];
25
42
  /** The built-in convention presets. Other schemes use the custom object (see the docs). */
26
43
  const MODIFIER_PRESETS = {
27
44
  /** BEM / SUIT — `.button--primary`, with `.button__element` sub-elements. The default. */
@@ -65,6 +82,7 @@ function resolveModifierConvention(input) {
65
82
  ...base.elementSeparator !== void 0 ? { elementSeparator: base.elementSeparator } : {},
66
83
  ...base.statePrefixes !== void 0 ? { statePrefixes: base.statePrefixes } : {},
67
84
  statePseudoClasses: base.statePseudoClasses ?? [...DEFAULT_STATE_PSEUDO_CLASSES],
85
+ pseudoElements: base.pseudoElements ?? [...DEFAULT_PSEUDO_ELEMENTS],
68
86
  propValue: base.propValue ?? false,
69
87
  propValueSeparator: base.propValueSeparator ?? "-"
70
88
  };
@@ -92,6 +110,8 @@ var ModifierMatcher = class {
92
110
  statePrefixes;
93
111
  /** Native pseudo-classes (no `:`) recognized as states. */
94
112
  statePseudoClasses;
113
+ /** Native pseudo-elements (no `::`) recognized as documented pseudo-elements. */
114
+ pseudoElements;
95
115
  constructor(convention) {
96
116
  this.convention = convention;
97
117
  const longestFirst = (value) => (Array.isArray(value) ? value : [value]).slice().sort((a, b) => b.length - a.length);
@@ -101,6 +121,7 @@ var ModifierMatcher = class {
101
121
  this.elementSepAlt = this.elementSeparators.length ? `(?:${this.elementSeparators.map(escapeRe).join("|")})` : "";
102
122
  this.statePrefixes = (convention.statePrefixes ?? []).filter((p) => p !== "").slice().sort((a, b) => b.length - a.length);
103
123
  this.statePseudoClasses = new Set(convention.statePseudoClasses ?? []);
124
+ this.pseudoElements = new Set(convention.pseudoElements ?? []);
104
125
  }
105
126
  /** Does a class name (no leading dot) start with one of the convention's state prefixes? */
106
127
  isStateClass(name) {
@@ -268,6 +289,24 @@ var ModifierMatcher = class {
268
289
  }
269
290
  return out;
270
291
  }
292
+ /**
293
+ * Every native pseudo-element on the selector recognized for documentation — a `::name` whose `name`
294
+ * is in the convention's {@link ModifierConvention.pseudoElements} (e.g. `.alert::before` → `before`).
295
+ * `::part()`/`::slotted()` and pseudo-elements not in the set are ignored.
296
+ */
297
+ pseudoElementsIn(selector) {
298
+ if (this.pseudoElements.size === 0) return [];
299
+ const seen = /* @__PURE__ */ new Set();
300
+ const out = [];
301
+ for (const m of selector.matchAll(/::([\w-]+)/gu)) {
302
+ const name = m[1];
303
+ if (this.pseudoElements.has(name) && !seen.has(name)) {
304
+ seen.add(name);
305
+ out.push({ name });
306
+ }
307
+ }
308
+ return out;
309
+ }
271
310
  /** Derive `prop`/`value` for a modifier `name` (as returned by {@link modifiersIn} or authored). */
272
311
  analyze(name) {
273
312
  if (name.includes("*")) {
@@ -402,24 +441,12 @@ var CssDocTagDefinition = class {
402
441
  function def(options) {
403
442
  return new CssDocTagDefinition(options);
404
443
  }
405
- /**
406
- * A parse configuration: the set of tag definitions plus which are supported. Construct one to get the
407
- * full standard vocabulary, then add custom tags or disable standard ones.
408
- *
409
- * @example
410
- * ```ts
411
- * import { CssDocConfiguration, CssDocTagDefinition, parseCssDocs } from "@cssdoc/core";
412
- *
413
- * const config = new CssDocConfiguration();
414
- * config.addTagDefinition(new CssDocTagDefinition({ tagName: "@token", syntaxKind: "block" }), true);
415
- * const model = parseCssDocs(css, { configuration: config });
416
- * ```
417
- */
418
444
  var CssDocConfiguration = class CssDocConfiguration {
419
445
  _tagDefinitions = [];
420
446
  _byName = /* @__PURE__ */ new Map();
421
447
  _supported = /* @__PURE__ */ new Set();
422
448
  _modifierConvention = DEFAULT_MODIFIER_CONVENTION;
449
+ _inlineComments = "append";
423
450
  constructor() {
424
451
  this.addTagDefinitions(CssDocConfiguration.standardTags(), true);
425
452
  }
@@ -431,6 +458,14 @@ var CssDocConfiguration = class CssDocConfiguration {
431
458
  setModifierConvention(input) {
432
459
  this._modifierConvention = resolveModifierConvention(input);
433
460
  }
461
+ /** How inline `/* … *\/` comments combine with tag prose (defaults to `append`). */
462
+ get inlineComments() {
463
+ return this._inlineComments;
464
+ }
465
+ /** Set the inline-comment combine mode. */
466
+ setInlineComments(mode) {
467
+ this._inlineComments = mode;
468
+ }
434
469
  /** Every registered tag definition, in registration order. */
435
470
  get tagDefinitions() {
436
471
  return this._tagDefinitions;
@@ -561,6 +596,9 @@ function parseDocComment(raw, configuration = new CssDocConfiguration(), parse)
561
596
  parts: /* @__PURE__ */ new Map(),
562
597
  tokens: /* @__PURE__ */ new Map(),
563
598
  cssParts: /* @__PURE__ */ new Map(),
599
+ pseudoElements: /* @__PURE__ */ new Map(),
600
+ wrappers: /* @__PURE__ */ new Map(),
601
+ todos: [],
564
602
  cssProperties: [],
565
603
  cssStates: /* @__PURE__ */ new Map(),
566
604
  slots: /* @__PURE__ */ new Map(),
@@ -613,6 +651,11 @@ function applyBlockTag(doc, canonical, tagName, rest, parse) {
613
651
  case "privateRemarks":
614
652
  doc.privateRemarks = rest.trim();
615
653
  break;
654
+ case "todo": {
655
+ const todo = rest.trim();
656
+ if (todo) doc.todos.push(todo);
657
+ break;
658
+ }
616
659
  case "since":
617
660
  doc.since = rest.trim();
618
661
  break;
@@ -648,6 +691,16 @@ function applyBlockTag(doc, canonical, tagName, rest, parse) {
648
691
  doc.cssParts.set(head.replace(/^\./u, ""), description ?? "");
649
692
  break;
650
693
  }
694
+ case "pseudo": {
695
+ const { head, description } = splitDesc(rest);
696
+ doc.pseudoElements.set(head.replace(/^::/u, ""), description ?? "");
697
+ break;
698
+ }
699
+ case "wrapper": {
700
+ const { head, description } = splitDesc(rest);
701
+ doc.wrappers.set(head.replace(/^\./u, ""), description ?? "");
702
+ break;
703
+ }
651
704
  case "cssproperty": {
652
705
  const propMatch = rest.match(/^(--[\w-]+)\s*(<[^>]+>)?\s*(?:(?:—|-{1,2})\s*(.*))?$/u);
653
706
  if (propMatch) doc.cssProperties.push({
@@ -700,7 +753,7 @@ function applyBlockTag(doc, canonical, tagName, rest, parse) {
700
753
  break;
701
754
  }
702
755
  case "example":
703
- doc.examples.push(rest);
756
+ doc.examples.push(rest.replace(/\\`/gu, "`"));
704
757
  break;
705
758
  case "deprecated":
706
759
  doc.deprecated = rest;
@@ -828,6 +881,12 @@ const EDGE = {
828
881
  many: "-->|0..n|",
829
882
  "one-or-more": "-->|1..n|"
830
883
  };
884
+ /** The ER range token for a cardinality — used on the root's label, which has no incoming edge. */
885
+ const CARDINALITY_TOKEN = {
886
+ optional: "0..1",
887
+ many: "0..n",
888
+ "one-or-more": "1..n"
889
+ };
831
890
  /** Wrap a node's label in the shape mermaid draws for its class. */
832
891
  const SHAPE = {
833
892
  "cssdoc-root": (id, l) => `${id}["${l}"]`,
@@ -853,10 +912,13 @@ function classify(node, isRoot, options) {
853
912
  klass: "cssdoc-slot",
854
913
  label: slot[1] ? `‹content: ${slot[1]}›` : "‹content›"
855
914
  };
856
- if (isRoot) return {
857
- klass: "cssdoc-root",
858
- label: node.selector
859
- };
915
+ if (isRoot) {
916
+ const card = node.cardinality ? ` (${CARDINALITY_TOKEN[node.cardinality]})` : "";
917
+ return {
918
+ klass: "cssdoc-root",
919
+ label: `${node.selector}${card}`
920
+ };
921
+ }
860
922
  const primary = firstClass(node.selector);
861
923
  if (primary && primary !== options.self) {
862
924
  const component = options.resolveComponent?.(primary);
@@ -50,6 +50,12 @@ interface ModifierConvention {
50
50
  * of form/UI states, deliberately excluding ubiquitous interaction pseudos (`:hover`, `:focus`).
51
51
  */
52
52
  statePseudoClasses?: string[];
53
+ /**
54
+ * Native pseudo-elements (without the `::`) to document when they appear on a component's selectors,
55
+ * e.g. `["before", "after"]`. Defaults to {@link DEFAULT_PSEUDO_ELEMENTS} — a curated set of standard
56
+ * pseudo-elements, excluding vendor/experimental ones. Shadow `::part()` is handled separately.
57
+ */
58
+ pseudoElements?: string[];
53
59
  /**
54
60
  * Split the modifier body into `prop`/`value` on {@link ModifierConvention.propValueSeparator}?
55
61
  * Defaults to `false`. Ignored for `attribute` (which always derives `prop` from the attribute name
@@ -127,6 +133,8 @@ declare class ModifierMatcher {
127
133
  private readonly statePrefixes;
128
134
  /** Native pseudo-classes (no `:`) recognized as states. */
129
135
  private readonly statePseudoClasses;
136
+ /** Native pseudo-elements (no `::`) recognized as documented pseudo-elements. */
137
+ private readonly pseudoElements;
130
138
  constructor(convention: ModifierConvention);
131
139
  /** Does a class name (no leading dot) start with one of the convention's state prefixes? */
132
140
  private isStateClass;
@@ -180,6 +188,14 @@ declare class ModifierMatcher {
180
188
  pseudoStatesIn(selector: string): {
181
189
  name: string;
182
190
  }[];
191
+ /**
192
+ * Every native pseudo-element on the selector recognized for documentation — a `::name` whose `name`
193
+ * is in the convention's {@link ModifierConvention.pseudoElements} (e.g. `.alert::before` → `before`).
194
+ * `::part()`/`::slotted()` and pseudo-elements not in the set are ignored.
195
+ */
196
+ pseudoElementsIn(selector: string): {
197
+ name: string;
198
+ }[];
183
199
  /** Derive `prop`/`value` for a modifier `name` (as returned by {@link modifiersIn} or authored). */
184
200
  analyze(name: string): {
185
201
  prop: string;
@@ -283,6 +299,17 @@ interface CssState {
283
299
  /** Prose from a `@cssstate` doc tag, when authored. */
284
300
  description?: string;
285
301
  }
302
+ /**
303
+ * A native pseudo-element a component styles (`::before`, `::marker`, `::selection`, …) — from a
304
+ * `@pseudo` doc tag or derived from a `::name` selector. Shadow `::part()` parts are modeled separately
305
+ * as {@link CssPart} (`shadowParts`), since only they map to a Custom Elements Manifest entry.
306
+ */
307
+ interface CssPseudoElement {
308
+ /** The pseudo-element name without the leading `::`, e.g. `before`. */
309
+ name: string;
310
+ /** Prose from a `@pseudo` doc tag, when authored. */
311
+ description?: string;
312
+ }
286
313
  /** A named slot a component shell exposes (`@slot`, Custom Elements Manifest). */
287
314
  interface CssSlot {
288
315
  /** The slot name (empty string for the default slot). */
@@ -367,6 +394,8 @@ interface StructureNode {
367
394
  * the stored selector.
368
395
  */
369
396
  cardinality?: "optional" | "many" | "one-or-more";
397
+ /** Prose from a `@wrapper` doc tag matching this node's class, when authored (annotates the node). */
398
+ description?: string;
370
399
  /** Child nodes (rules nested one brace level deeper). */
371
400
  children: StructureNode[];
372
401
  }
@@ -426,10 +455,17 @@ interface CssDocEntry {
426
455
  parts: CssPart[];
427
456
  /** Shadow-DOM exposed parts (`::part(name)`), from `@csspart` or a `::part()` selector. */
428
457
  shadowParts: CssPart[];
458
+ /** Native pseudo-elements the component styles (`::before`, `::marker`, …), from `@pseudo` or a selector. */
459
+ pseudoElements: CssPseudoElement[];
429
460
  /** States the component reacts to, from `@cssstate`, `:state()`, pseudo-classes, or state classes. */
430
461
  states: CssState[];
431
462
  /** Named slots the component shell exposes, from `@slot`. */
432
463
  slots: CssSlot[];
464
+ /**
465
+ * Internal to-do notes, from `@todo` tags and `/* @todo … *\/` inline comments. Development notes,
466
+ * not public API — emitters may omit them (like {@link CssDocEntry.privateRemarks}).
467
+ */
468
+ todos: string[];
433
469
  /**
434
470
  * Design tokens this component consumes: every `--*` custom property referenced via `var(...)` inside
435
471
  * its rules, each annotated with `@tokens` prose where authored (and including any `@tokens`-declared
@@ -553,16 +589,27 @@ declare class CssDocTagDefinition {
553
589
  * const model = parseCssDocs(css, { configuration: config });
554
590
  * ```
555
591
  */
592
+ /**
593
+ * How a `/* … *\/` comment on a member's rule combines with the member's authored tag prose:
594
+ * `append` (tag then comment, the default), `prepend` (comment then tag), `replace` (comment wins when
595
+ * present), or `ignore` (comments are never used as descriptions).
596
+ */
597
+ type InlineCommentMode = "append" | "prepend" | "replace" | "ignore";
556
598
  declare class CssDocConfiguration {
557
599
  private readonly _tagDefinitions;
558
600
  private readonly _byName;
559
601
  private readonly _supported;
560
602
  private _modifierConvention;
603
+ private _inlineComments;
561
604
  constructor();
562
605
  /** The resolved modifier convention this configuration parses with (defaults to BEM). */
563
606
  get modifierConvention(): ModifierConvention;
564
607
  /** Set the modifier convention from a preset name or a custom {@link ModifierConvention}. */
565
608
  setModifierConvention(input: ModifierConventionInput): void;
609
+ /** How inline `/* … *\/` comments combine with tag prose (defaults to `append`). */
610
+ get inlineComments(): InlineCommentMode;
611
+ /** Set the inline-comment combine mode. */
612
+ setInlineComments(mode: InlineCommentMode): void;
566
613
  /** Every registered tag definition, in registration order. */
567
614
  get tagDefinitions(): readonly CssDocTagDefinition[];
568
615
  /** Only the tag definitions that are currently supported. */
@@ -656,6 +703,12 @@ interface ParsedDoc {
656
703
  tokens: Map<string, string>;
657
704
  /** `@csspart` descriptions (shadow-DOM `::part()`), keyed by the bare part name (e.g. `header`). */
658
705
  cssParts: Map<string, string>;
706
+ /** `@pseudo` descriptions (native pseudo-elements), keyed by the bare name (e.g. `before`). */
707
+ pseudoElements: Map<string, string>;
708
+ /** `@wrapper` descriptions (optional-ancestor wrappers), keyed by the bare class (e.g. `badge-wrapper`). */
709
+ wrappers: Map<string, string>;
710
+ /** `@todo` notes (internal development notes). */
711
+ todos: string[];
659
712
  /** `@cssproperty` declarations. */
660
713
  cssProperties: DocCssProperty[];
661
714
  /** `@cssstate` descriptions, keyed by state name. */
@@ -768,4 +821,4 @@ declare function toMermaid(roots: StructureNode[], options?: MermaidOptions): st
768
821
  */
769
822
  declare function toJson(model: CssDocEntry[]): string;
770
823
  //#endregion
771
- export { CssState as A, ModifierMatcher as B, CssPart as C, CssReleaseStage as D, CssRelated as E, DEFAULT_STATE_PSEUDO_CLASSES as F, MODIFIER_PRESETS as I, ModifierConvention as L, ParseOptions as M, StructureNode as N, CssSlot as O, DEFAULT_MODIFIER_CONVENTION as P, ModifierConventionInput as R, CssParse as S, CssRecordKind as T, resolveModifierConvention as V, CssCondition as _, DocModifier as a, CssLayer as b, parseDocComment as c, stripCommentFraming as d, CssDocConfiguration as f, CssAnimation as g, CssDocTagDefinitionOptions as h, DocCssProperty as i, CssTokenConsumed as j, CssSource as k, parseStructure as l, CssDocTagDefinition as m, toMermaid as n, ParsedDoc as o, CssDocSyntaxKind as p, DocCondition as r, RECORD_TAGS as s, toJson as t, recordNameOf as u, CssDocEntry as v, CssPropertyDeclared as w, CssModifier as x, CssFunction as y, ModifierHit as z };
824
+ export { CssSource as A, ModifierHit as B, CssParse as C, CssRelated as D, CssRecordKind as E, DEFAULT_MODIFIER_CONVENTION as F, resolveModifierConvention as H, DEFAULT_STATE_PSEUDO_CLASSES as I, MODIFIER_PRESETS as L, CssTokenConsumed as M, ParseOptions as N, CssReleaseStage as O, StructureNode as P, ModifierConvention as R, CssModifier as S, CssPropertyDeclared as T, ModifierMatcher as V, CssAnimation as _, DocModifier as a, CssFunction as b, parseDocComment as c, stripCommentFraming as d, CssDocConfiguration as f, InlineCommentMode as g, CssDocTagDefinitionOptions as h, DocCssProperty as i, CssState as j, CssSlot as k, parseStructure as l, CssDocTagDefinition as m, toMermaid as n, ParsedDoc as o, CssDocSyntaxKind as p, DocCondition as r, RECORD_TAGS as s, toJson as t, recordNameOf as u, CssCondition as v, CssPart as w, CssLayer as x, CssDocEntry as y, ModifierConventionInput as z };
package/dist/lite.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { A as CssState, B as ModifierMatcher, C as CssPart, D as CssReleaseStage, E as CssRelated, F as DEFAULT_STATE_PSEUDO_CLASSES, I as MODIFIER_PRESETS, L as ModifierConvention, M as ParseOptions, N as StructureNode, O as CssSlot, P as DEFAULT_MODIFIER_CONVENTION, R as ModifierConventionInput, S as CssParse, T as CssRecordKind, V as resolveModifierConvention, _ as CssCondition, a as DocModifier, b as CssLayer, c as parseDocComment, d as stripCommentFraming, f as CssDocConfiguration, g as CssAnimation, h as CssDocTagDefinitionOptions, i as DocCssProperty, j as CssTokenConsumed, k as CssSource, l as parseStructure, m as CssDocTagDefinition, n as toMermaid, o as ParsedDoc, p as CssDocSyntaxKind, r as DocCondition, s as RECORD_TAGS, t as toJson, u as recordNameOf, v as CssDocEntry, w as CssPropertyDeclared, x as CssModifier, y as CssFunction, z as ModifierHit } from "./lite-DtKEwRkS.mjs";
2
- export { type CssAnimation, type CssCondition, CssDocConfiguration, type CssDocEntry, type CssDocSyntaxKind, CssDocTagDefinition, type CssDocTagDefinitionOptions, type CssFunction, type CssLayer, type CssModifier, type CssParse, type CssPart, type CssPropertyDeclared, type CssRecordKind, type CssRelated, type CssReleaseStage, type CssSlot, type CssSource, type CssState, type CssTokenConsumed, DEFAULT_MODIFIER_CONVENTION, DEFAULT_STATE_PSEUDO_CLASSES, type DocCondition, type DocCssProperty, type DocModifier, MODIFIER_PRESETS, type ModifierConvention, type ModifierConventionInput, type ModifierHit, ModifierMatcher, type ParseOptions, type ParsedDoc, RECORD_TAGS, type StructureNode, parseDocComment, parseStructure, recordNameOf, resolveModifierConvention, stripCommentFraming, toJson, toMermaid };
1
+ import { A as CssSource, B as ModifierHit, C as CssParse, D as CssRelated, E as CssRecordKind, F as DEFAULT_MODIFIER_CONVENTION, H as resolveModifierConvention, I as DEFAULT_STATE_PSEUDO_CLASSES, L as MODIFIER_PRESETS, M as CssTokenConsumed, N as ParseOptions, O as CssReleaseStage, P as StructureNode, R as ModifierConvention, S as CssModifier, T as CssPropertyDeclared, V as ModifierMatcher, _ as CssAnimation, a as DocModifier, b as CssFunction, c as parseDocComment, d as stripCommentFraming, f as CssDocConfiguration, g as InlineCommentMode, h as CssDocTagDefinitionOptions, i as DocCssProperty, j as CssState, k as CssSlot, l as parseStructure, m as CssDocTagDefinition, n as toMermaid, o as ParsedDoc, p as CssDocSyntaxKind, r as DocCondition, s as RECORD_TAGS, t as toJson, u as recordNameOf, v as CssCondition, w as CssPart, x as CssLayer, y as CssDocEntry, z as ModifierConventionInput } from "./lite-DzhifijA.mjs";
2
+ export { type CssAnimation, type CssCondition, CssDocConfiguration, type CssDocEntry, type CssDocSyntaxKind, CssDocTagDefinition, type CssDocTagDefinitionOptions, type CssFunction, type CssLayer, type CssModifier, type CssParse, type CssPart, type CssPropertyDeclared, type CssRecordKind, type CssRelated, type CssReleaseStage, type CssSlot, type CssSource, type CssState, type CssTokenConsumed, DEFAULT_MODIFIER_CONVENTION, DEFAULT_STATE_PSEUDO_CLASSES, type DocCondition, type DocCssProperty, type DocModifier, type InlineCommentMode, MODIFIER_PRESETS, type ModifierConvention, type ModifierConventionInput, type ModifierHit, ModifierMatcher, type ParseOptions, type ParsedDoc, RECORD_TAGS, type StructureNode, parseDocComment, parseStructure, recordNameOf, resolveModifierConvention, stripCommentFraming, toJson, toMermaid };
package/dist/lite.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as parseStructure, c as CssDocConfiguration, d as DEFAULT_STATE_PSEUDO_CLASSES, f as MODIFIER_PRESETS, i as parseDocComment, l as CssDocTagDefinition, m as resolveModifierConvention, n as toMermaid, o as recordNameOf, p as ModifierMatcher, r as RECORD_TAGS, s as stripCommentFraming, t as toJson, u as DEFAULT_MODIFIER_CONVENTION } from "./lite-HEYG01Eo.mjs";
1
+ import { a as parseStructure, c as CssDocConfiguration, d as DEFAULT_STATE_PSEUDO_CLASSES, f as MODIFIER_PRESETS, i as parseDocComment, l as CssDocTagDefinition, m as resolveModifierConvention, n as toMermaid, o as recordNameOf, p as ModifierMatcher, r as RECORD_TAGS, s as stripCommentFraming, t as toJson, u as DEFAULT_MODIFIER_CONVENTION } from "./lite-CEVmwLGF.mjs";
2
2
  export { CssDocConfiguration, CssDocTagDefinition, DEFAULT_MODIFIER_CONVENTION, DEFAULT_STATE_PSEUDO_CLASSES, MODIFIER_PRESETS, ModifierMatcher, RECORD_TAGS, parseDocComment, parseStructure, recordNameOf, resolveModifierConvention, stripCommentFraming, toJson, toMermaid };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cssdoc/core",
3
- "version": "0.5.3",
3
+ "version": "0.6.0",
4
4
  "description": "A generic CSS documentation extractor: parse doc-comments + the CSS AST into a serializable model (TSDoc, for CSS).",
5
5
  "keywords": [
6
6
  "css",
@@ -32,8 +32,8 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "postcss": "^8.5.17",
36
- "@cssdoc/spec": "0.5.3"
35
+ "postcss": "^8.5.19",
36
+ "@cssdoc/spec": "0.6.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "^24.13.3",