@dr-ishaan/remake-blocks 1.9.0 → 2.0.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/dx.js ADDED
@@ -0,0 +1,230 @@
1
+ /**
2
+ * v1.10.0: Developer Experience utilities.
3
+ *
4
+ * Exports:
5
+ * - CLASSES: frozen object of CSS class name constants for programmatic use
6
+ * - warn(message, ...args): dev-mode warning emitter (gated on devWarnings option)
7
+ * - validateCustomCallouts(configs, strict): validates customCallouts at init time
8
+ * - suggestSimilarType(type, knownTypes): Levenshtein-based "did you mean" suggester
9
+ *
10
+ * @module dx
11
+ */
12
+ // ---------------------------------------------------------------------------
13
+ // CLASSES — exported CSS class name constants
14
+ // ---------------------------------------------------------------------------
15
+ /**
16
+ * v1.10.0+: Frozen object of CSS class names used by the plugin. Use these
17
+ * in CSS-in-JS, tests, or any code that needs to reference callout classes
18
+ * programmatically without hardcoding strings.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { CLASSES } from '@dr-ishaan/remake-blocks';
23
+ *
24
+ * document.querySelector(`.${CLASSES.CALLOUT_NOTE}`) // '.callout-note'
25
+ * ```
26
+ */
27
+ export const CLASSES = Object.freeze({
28
+ // Container classes
29
+ CALLOUT: "callout",
30
+ CALLOUT_NOTE: "callout-note",
31
+ CALLOUT_TIP: "callout-tip",
32
+ CALLOUT_IMPORTANT: "callout-important",
33
+ CALLOUT_WARNING: "callout-warning",
34
+ CALLOUT_CAUTION: "callout-caution",
35
+ CALLOUT_ABSTRACT: "callout-abstract",
36
+ CALLOUT_INFO: "callout-info",
37
+ CALLOUT_SUCCESS: "callout-success",
38
+ CALLOUT_QUESTION: "callout-question",
39
+ CALLOUT_FAILURE: "callout-failure",
40
+ CALLOUT_DANGER: "callout-danger",
41
+ CALLOUT_QUOTE: "callout-quote",
42
+ CALLOUT_BUG: "callout-bug",
43
+ CALLOUT_EXAMPLE: "callout-example",
44
+ CALLOUT_TODO: "callout-todo",
45
+ CALLOUT_SUMMARY: "callout-summary",
46
+ CALLOUT_TLDR: "callout-tldr",
47
+ CALLOUT_HINT: "callout-hint",
48
+ CALLOUT_CHECK: "callout-check",
49
+ CALLOUT_DONE: "callout-done",
50
+ CALLOUT_HELP: "callout-help",
51
+ CALLOUT_FAQ: "callout-faq",
52
+ CALLOUT_ATTENTION: "callout-attention",
53
+ CALLOUT_FAIL: "callout-fail",
54
+ CALLOUT_MISSING: "callout-missing",
55
+ CALLOUT_ERROR: "callout-error",
56
+ CALLOUT_CITE: "callout-cite",
57
+ // v1.6.0 types
58
+ CALLOUT_DEFINITION: "callout-definition",
59
+ CALLOUT_ASIDE: "callout-aside",
60
+ CALLOUT_CORRECTION: "callout-correction",
61
+ CALLOUT_UPDATE: "callout-update",
62
+ CALLOUT_FIGURE: "callout-figure",
63
+ CALLOUT_FURTHER_READING: "callout-further-reading",
64
+ CALLOUT_PREREQUISITE: "callout-prerequisite",
65
+ CALLOUT_EXERCISE: "callout-exercise",
66
+ CALLOUT_SIDENOTE: "callout-sidenote",
67
+ CALLOUT_TIMELINE: "callout-timeline",
68
+ CALLOUT_ANNOUNCEMENT: "callout-announcement",
69
+ CALLOUT_BIBLIOGRAPHY: "callout-bibliography",
70
+ CALLOUT_DRAFT: "callout-draft",
71
+ CALLOUT_TRANSLATION: "callout-translation",
72
+ CALLOUT_DISCUSSION: "callout-discussion",
73
+ CALLOUT_RETRO: "callout-retro",
74
+ // Sub-element classes
75
+ CALLOUT_TITLE: "callout-title",
76
+ CALLOUT_TITLE_TEXT: "callout-title-text",
77
+ CALLOUT_ICON: "callout-icon",
78
+ CALLOUT_BODY: "callout-body",
79
+ CALLOUT_COLLAPSIBLE: "collapsible",
80
+ // Disclosure / accordion
81
+ DISCLOSURE: "disclosure",
82
+ DISCLOSURE_TITLE: "disclosure-title",
83
+ DISCLOSURE_BODY: "disclosure-body",
84
+ DISCLOSURE_ACCORDION: "disclosure-accordion",
85
+ DISCLOSURE_TREE: "disclosure-tree",
86
+ // Pull quote / epigraph
87
+ PULL_QUOTE: "pull-quote",
88
+ PULL_QUOTE_TEXT: "pull-quote-text",
89
+ PULL_QUOTE_ATTRIBUTION: "pull-quote-attribution",
90
+ EPIGRAPH: "epigraph",
91
+ EPIGRAPH_TEXT: "epigraph-text",
92
+ EPIGRAPH_ATTRIBUTION: "epigraph-attribution",
93
+ // Blockquote enhancement
94
+ BLOCKQUOTE_ENHANCED: "blockquote-enhanced",
95
+ });
96
+ // ---------------------------------------------------------------------------
97
+ // warn — dev-mode warning emitter
98
+ // ---------------------------------------------------------------------------
99
+ /**
100
+ * v1.10.0+: Emit a developer-mode warning via `console.warn`.
101
+ *
102
+ * The warning is prefixed with `[remake-blocks]` for easy identification
103
+ * and filtering. No-op when `devWarnings` is false OR when running in
104
+ * production (`process.env.NODE_ENV === 'production'`).
105
+ *
106
+ * @param enabled — whether devWarnings is enabled (from plugin options)
107
+ * @param message — warning message (without prefix)
108
+ * @param args — additional context args
109
+ */
110
+ export function warn(enabled, message, ...args) {
111
+ if (!enabled)
112
+ return;
113
+ if (typeof process !== "undefined" && process.env && process.env.NODE_ENV === "production" && enabled !== true) {
114
+ // Auto-disable in production unless explicitly enabled
115
+ return;
116
+ }
117
+ console.warn(`[remake-blocks] ${message}`, ...args);
118
+ }
119
+ // ---------------------------------------------------------------------------
120
+ // suggestSimilarType — Levenshtein-based "did you mean" suggester
121
+ // ---------------------------------------------------------------------------
122
+ /**
123
+ * Compute Levenshtein edit distance between two strings.
124
+ * Used by `suggestSimilarType` to find the closest match.
125
+ */
126
+ function levenshtein(a, b) {
127
+ const m = a.length;
128
+ const n = b.length;
129
+ if (m === 0)
130
+ return n;
131
+ if (n === 0)
132
+ return m;
133
+ const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
134
+ for (let i = 0; i <= m; i++)
135
+ dp[i][0] = i;
136
+ for (let j = 0; j <= n; j++)
137
+ dp[0][j] = j;
138
+ for (let i = 1; i <= m; i++) {
139
+ for (let j = 1; j <= n; j++) {
140
+ const cost = a[i - 1].toLowerCase() === b[j - 1].toLowerCase() ? 0 : 1;
141
+ dp[i][j] = Math.min(dp[i - 1][j] + 1, // deletion
142
+ dp[i][j - 1] + 1, // insertion
143
+ dp[i - 1][j - 1] + cost);
144
+ }
145
+ }
146
+ return dp[m][n];
147
+ }
148
+ /**
149
+ * Find the closest matching type from a list of known types.
150
+ * Returns the best match if its edit distance is ≤ 3 (reasonable threshold
151
+ * for typo detection), or null if no close match exists.
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * suggestSimilarType('TYPO', ['note', 'tip', 'warning']) // → 'tip'
156
+ * suggestSimilarType('XYZ', ['note', 'tip']) // → null
157
+ * ```
158
+ */
159
+ export function suggestSimilarType(input, knownTypes) {
160
+ let bestMatch = null;
161
+ let bestDistance = Infinity;
162
+ for (const candidate of knownTypes) {
163
+ const dist = levenshtein(input, candidate);
164
+ if (dist < bestDistance) {
165
+ bestDistance = dist;
166
+ bestMatch = candidate;
167
+ }
168
+ }
169
+ // Only suggest if the edit distance is reasonable (≤ 3 edits, or ≤ half
170
+ // the input length for longer type names like 'further-reading').
171
+ const threshold = Math.max(3, Math.floor(input.length / 2));
172
+ return bestDistance <= threshold ? bestMatch : null;
173
+ }
174
+ // ---------------------------------------------------------------------------
175
+ // validateCustomCallouts — strict config validation
176
+ // ---------------------------------------------------------------------------
177
+ const REQUIRED_CALLOUT_FIELDS = ["type", "icon", "className", "defaultTitle", "color", "backgroundColor"];
178
+ /**
179
+ * v1.10.0+: Validate `customCallouts` config entries.
180
+ *
181
+ * When `strict` is true, throws a descriptive Error for the first config
182
+ * entry that's missing a required field. When `strict` is false, returns
183
+ * an array of validation errors without throwing (caller decides what to do).
184
+ *
185
+ * Required fields per CalloutConfig:
186
+ * - type (non-empty string)
187
+ * - icon (string, may be empty for icon-less callouts)
188
+ * - className (non-empty string, used for CSS targeting)
189
+ * - defaultTitle (non-empty string)
190
+ * - color (valid CSS color)
191
+ * - backgroundColor (valid CSS color)
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * validateCustomCallouts([
196
+ * { type: 'x', icon: '🔥', className: 'callout-x', defaultTitle: 'X', color: '#f00', backgroundColor: '#fee' }
197
+ * ], true); // OK — no throw
198
+ *
199
+ * validateCustomCallouts([
200
+ * { type: 'x', icon: '🔥' /* missing className, defaultTitle, color, backgroundColor *\/ }
201
+ * ], true); // throws: "Custom callout config[0] ('x') is missing required fields: className, defaultTitle, color, backgroundColor"
202
+ * ```
203
+ */
204
+ export function validateCustomCallouts(configs, strict) {
205
+ const errors = [];
206
+ for (let i = 0; i < configs.length; i++) {
207
+ const c = configs[i];
208
+ if (!c || typeof c !== "object") {
209
+ errors.push(`Custom callout config[${i}] is not an object`);
210
+ continue;
211
+ }
212
+ const missing = [];
213
+ for (const field of REQUIRED_CALLOUT_FIELDS) {
214
+ const val = c[field];
215
+ if (val === undefined || val === null || (typeof val === "string" && val.trim() === "" && field !== "icon")) {
216
+ missing.push(field);
217
+ }
218
+ }
219
+ if (missing.length > 0) {
220
+ const name = typeof c.type === "string" ? `'${c.type}'` : `at index ${i}`;
221
+ const msg = `Custom callout config[${i}] (${name}) is missing required fields: ${missing.join(", ")}`;
222
+ errors.push(msg);
223
+ if (strict) {
224
+ throw new Error(`[remake-blocks] ${msg}`);
225
+ }
226
+ }
227
+ }
228
+ return errors;
229
+ }
230
+ //# sourceMappingURL=dx.js.map
package/dist/dx.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dx.js","sourceRoot":"","sources":["../src/dx.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -9,6 +9,8 @@
9
9
  export { remarkRemakeBlocks, remarkCalloutBlocks, BUILTIN_CALLOUTS } from "./remark-remake-blocks.js";
10
10
  export { escapeHtml, escapeAttribute, sanitizeColor } from "./remark-remake-blocks.js";
11
11
  export { generateCss, generateMinimalThemeCss } from "./css-generator.js";
12
+ export { CLASSES, validateCustomCallouts, suggestSimilarType } from "./dx.js";
13
+ export type { CalloutClassName } from "./dx.js";
12
14
  export { default } from "./astro.js";
13
- export type { RemakeBlocksOptions, AstroRemakeBlocksOptions, CalloutConfig, CalloutType, BuiltinCalloutType, ParsedCallout, CustomCalloutType, CalloutConfigMap, } from "./types.js";
15
+ export type { RemakeBlocksOptions, AstroRemakeBlocksOptions, CalloutConfig, CalloutType, BuiltinCalloutType, LucideIconName, ParsedCallout, CustomCalloutType, CalloutConfigMap, } from "./types.js";
14
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAEtG,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAIvF,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAG1E,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAGrC,YAAY,EACV,mBAAmB,EACnB,wBAAwB,EACxB,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAEtG,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAIvF,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAG1E,OAAO,EAAE,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAC9E,YAAY,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAGhD,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAGrC,YAAY,EACV,mBAAmB,EACnB,wBAAwB,EACxB,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -13,6 +13,8 @@ export { escapeHtml, escapeAttribute, sanitizeColor } from "./remark-remake-bloc
13
13
  // v1.7.0+: CSS generator (for advanced usage outside Astro)
14
14
  // v1.9.0+: also exports generateMinimalThemeCss for tree-shaken theme CSS
15
15
  export { generateCss, generateMinimalThemeCss } from "./css-generator.js";
16
+ // v1.10.0+: DX utilities — CSS class constants, validation, "did you mean" suggester
17
+ export { CLASSES, validateCustomCallouts, suggestSimilarType } from "./dx.js";
16
18
  // Astro integration (default export, re-exported for backward compatibility)
17
19
  export { default } from "./astro.js";
18
20
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,qBAAqB;AACrB,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACtG,kEAAkE;AAClE,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAEvF,4DAA4D;AAC5D,0EAA0E;AAC1E,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAE1E,6EAA6E;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,qBAAqB;AACrB,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACtG,kEAAkE;AAClE,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAEvF,4DAA4D;AAC5D,0EAA0E;AAC1E,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAE1E,qFAAqF;AACrF,OAAO,EAAE,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAG9E,6EAA6E;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"remark-remake-blocks.d.ts","sourceRoot":"","sources":["../src/remark-remake-blocks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAqC,MAAM,OAAO,CAAC;AACrE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EAId,MAAM,YAAY,CAAC;AAMpB,QAAA,MAAM,gBAAgB,EAAE,aAAa,EAyQpC,CAAC;AA4NF,iBAAS,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE5C;AAUD,iBAAS,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAW9D;AAgTD,iBAAS,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOvC;AA08BD,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,CAAC,mBAAmB,CAAC,CAAC,EAAE,IAAI,CAkJnE,CAAC;AAiFF,eAAO,MAAM,mBAAmB,oDAAqB,CAAC;AAItD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,CAAC;AAEtD,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"remark-remake-blocks.d.ts","sourceRoot":"","sources":["../src/remark-remake-blocks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAqC,MAAM,OAAO,CAAC;AACrE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EAId,MAAM,YAAY,CAAC;AAQpB,QAAA,MAAM,gBAAgB,EAAE,aAAa,EAyQpC,CAAC;AA0OF,iBAAS,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE5C;AAUD,iBAAS,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAW9D;AA6TD,iBAAS,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOvC;AA89BD,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,CAAC,mBAAmB,CAAC,CAAC,EAAE,IAAI,CAgMnE,CAAC;AA2GF,eAAO,MAAM,mBAAmB,oDAAqB,CAAC;AAItD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,CAAC;AAEtD,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
@@ -14,6 +14,9 @@
14
14
  * `allowDangerousHtml: true` in plugin options to disable escaping —
15
15
  * only do this if you fully trust your markdown source.
16
16
  */
17
+ import { visit } from "unist-util-visit";
18
+ // v1.10.0: DX utilities — warnings, validation, "did you mean" suggester
19
+ import { warn, validateCustomCallouts, suggestSimilarType } from "./dx.js";
17
20
  // ---------------------------------------------------------------------------
18
21
  // 27 Built-in callout configurations
19
22
  // ---------------------------------------------------------------------------
@@ -446,6 +449,13 @@ const DEFAULT_OPTIONS = {
446
449
  // v1.9.0 defaults — all opt-in
447
450
  icons: undefined,
448
451
  cssMinimalTheme: false,
452
+ // v1.10.0 defaults — devWarnings auto-resolves (undefined = auto), strict off
453
+ devWarnings: undefined,
454
+ strictConfigValidation: false,
455
+ // v2.0.0 defaults — roles off (all use "note"), srIconText off, ariaAccordion on
456
+ roles: undefined,
457
+ srIconText: false,
458
+ ariaAccordion: true,
449
459
  };
450
460
  // ---------------------------------------------------------------------------
451
461
  // Helper: Validate + normalize a single custom callout config.
@@ -693,7 +703,7 @@ function parseDirectiveAttrs(attrsBlock) {
693
703
  // ---------------------------------------------------------------------------
694
704
  // Helper: Parse the first paragraph of a blockquote to detect a callout
695
705
  // ---------------------------------------------------------------------------
696
- function parseCalloutDirective(blockquote, calloutPattern, configMap, enableDisclosures, defaultCollapse) {
706
+ function parseCalloutDirective(blockquote, calloutPattern, configMap, enableDisclosures, defaultCollapse, options) {
697
707
  const firstChild = blockquote.children[0];
698
708
  if (!firstChild || firstChild.type !== "paragraph")
699
709
  return null;
@@ -708,8 +718,19 @@ function parseCalloutDirective(blockquote, calloutPattern, configMap, enableDisc
708
718
  const lowerType = rawType.toLowerCase();
709
719
  const isPullQuote = lowerType === "pull";
710
720
  const isEpigraph = lowerType === "epigraph";
711
- if (!isDisclosure && !isPullQuote && !isEpigraph && !configMap.has(lowerType))
721
+ if (!isDisclosure && !isPullQuote && !isEpigraph && !configMap.has(lowerType)) {
722
+ // v1.10.0: dev warning for unknown callout types with "did you mean" suggestion
723
+ const devWarn = options?._devWarnings;
724
+ if (devWarn) {
725
+ const known = options?._knownTypes;
726
+ const suggestion = known && known.length > 0 ? suggestSimilarType(rawType, known) : null;
727
+ const msg = suggestion
728
+ ? `Unknown callout type '${rawType}'. Did you mean '${suggestion}'?`
729
+ : `Unknown callout type '${rawType}'. It will render as a plain blockquote.`;
730
+ warn(true, msg);
731
+ }
712
732
  return null;
733
+ }
713
734
  const type = isDisclosure ? "disclosure"
714
735
  : isPullQuote ? "pull"
715
736
  : isEpigraph ? "epigraph"
@@ -927,7 +948,11 @@ function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options
927
948
  // Sanitize colors before interpolating into style attribute
928
949
  const safeIconColor = sanitizeColor(config.iconColor || config.color, "#57606a");
929
950
  // v1.2.0: WCAG fix — use role="note" for ALL callouts (was: role="alert" for warnings).
930
- const ariaRole = "note";
951
+ // v2.0.0: per-type role override via `roles` option.
952
+ const roleOverride = options.roles?.[parsed.type];
953
+ const ariaRole = roleOverride === "none" ? "" : (roleOverride ?? "note");
954
+ // v2.0.0: when ariaRole is empty (role="none"), omit the attribute entirely.
955
+ const roleAttr = ariaRole ? ` role="${ariaRole}"` : "";
931
956
  // Escape className + calloutClass to prevent attribute breakout
932
957
  const safeCalloutClass = escapeAttribute(options.calloutClass);
933
958
  const safeConfigClassName = escapeAttribute(config.className);
@@ -1005,8 +1030,12 @@ function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options
1005
1030
  : "";
1006
1031
  // v1.2.0: title-text always rendered (unless appearance="hidden")
1007
1032
  const renderTitle = appearance !== "hidden";
1008
- const titleTextHtml = renderTitle
1009
- ? `<span class="callout-title-text">${escapeHtml(title)}</span>`
1033
+ // v2.0.0: sr-only text — prepended to title for screen readers when
1034
+ // srIconText is enabled. Uses the callout's default title (or custom title)
1035
+ // so screen readers announce "Warning:" even when the visual title is
1036
+ // just an icon or when appearance="minimal" hides the text.
1037
+ const srOnlyHtml = options.srIconText && renderTitle
1038
+ ? `<span class="sr-only">${escapeHtml(title)}:</span>`
1010
1039
  : "";
1011
1040
  // v1.2.0: tags option (override element names)
1012
1041
  const tags = options.tags ?? {};
@@ -1021,10 +1050,12 @@ function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options
1021
1050
  ? `<${iconTag} class="callout-icon" style="color:${safeIconColor}" aria-hidden="true">${effectiveIconHtml}</${iconTag}>`
1022
1051
  : "";
1023
1052
  // Build the title block (or skip for "hidden" appearance)
1053
+ // v2.0.0: prepend srOnlyHtml (when srIconText is on) before the visible title text.
1024
1054
  const titleBlock = renderTitle
1025
1055
  ? [
1026
1056
  ` <${titleTag} class="${safeCalloutTitleClass}" style="color:${safeIconColor}" id="${titleId}">`,
1027
1057
  iconSpan && ` ${iconSpan}`,
1058
+ srOnlyHtml && ` ${srOnlyHtml}`,
1028
1059
  ` <${titleTextTag} class="callout-title-text">${escapeHtml(title)}</${titleTextTag}>`,
1029
1060
  ` </${titleTag}>`,
1030
1061
  ].filter(Boolean).join("\n")
@@ -1040,6 +1071,7 @@ function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options
1040
1071
  ? [
1041
1072
  ` <${titleTag} class="${safeCalloutTitleClass}" style="color:${safeIconColor}" id="${titleId}"${titleDirAuto}>`,
1042
1073
  iconSpan && ` ${iconSpan}`,
1074
+ srOnlyHtml && ` ${srOnlyHtml}`,
1043
1075
  ` <${titleTextTag} class="callout-title-text">${escapeHtml(title)}</${titleTextTag}>`,
1044
1076
  ` </${titleTag}>`,
1045
1077
  ].filter(Boolean).join("\n")
@@ -1047,7 +1079,7 @@ function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options
1047
1079
  if (parsed.collapsible) {
1048
1080
  const openAttr = parsed.collapsibleOpen ? " open" : "";
1049
1081
  return html([
1050
- `<${containerTag} class="${safeCalloutClass} ${safeConfigClassName} collapsible${appearanceClass}${inlineClass}${directiveClasses}"${dataAttr}${collapsibleDataAttr} role="${ariaRole}"${labelledby}${dirAuto}${openAttr}${directiveId}${extraAttrs}${directiveAttrs}>`,
1082
+ `<${containerTag} class="${safeCalloutClass} ${safeConfigClassName} collapsible${appearanceClass}${inlineClass}${directiveClasses}"${dataAttr}${collapsibleDataAttr}${roleAttr}${labelledby}${dirAuto}${openAttr}${directiveId}${extraAttrs}${directiveAttrs}>`,
1051
1083
  titleBlockWithDir,
1052
1084
  ` <${bodyTag} class="${safeCalloutBodyClass}">`,
1053
1085
  bodyHtml,
@@ -1056,7 +1088,7 @@ function buildCalloutHtml(parsed, blockquote, calloutPattern, configMap, options
1056
1088
  ].filter(Boolean).join("\n"));
1057
1089
  }
1058
1090
  return html([
1059
- `<${containerTag} class="${safeCalloutClass} ${safeConfigClassName}${appearanceClass}${inlineClass}${directiveClasses}"${dataAttr}${collapsibleDataAttr} role="${ariaRole}"${labelledby}${dirAuto}${directiveId}${extraAttrs}${directiveAttrs}>`,
1091
+ `<${containerTag} class="${safeCalloutClass} ${safeConfigClassName}${appearanceClass}${inlineClass}${directiveClasses}"${dataAttr}${collapsibleDataAttr}${roleAttr}${labelledby}${dirAuto}${directiveId}${extraAttrs}${directiveAttrs}>`,
1060
1092
  titleBlockWithDir,
1061
1093
  ` <${bodyTag} class="${safeCalloutBodyClass}">`,
1062
1094
  bodyHtml,
@@ -1548,7 +1580,10 @@ function buildCalloutFromParts(parsed, config, bodyHtml, options) {
1548
1580
  const dataAttr = options.dataCalloutType ? ` data-callout-type="${escapeAttribute(parsed.type)}"` : "";
1549
1581
  const collapsibleDataAttr = ` data-collapsible="${parsed.collapsible ? "true" : "false"}"`;
1550
1582
  const safeIconColor = sanitizeColor(config.iconColor || config.color, "#57606a");
1551
- const ariaRole = "note";
1583
+ // v2.0.0: per-type role override via `roles` option (same logic as buildCalloutHtml).
1584
+ const roleOverride = options.roles?.[parsed.type];
1585
+ const ariaRole = roleOverride === "none" ? "" : (roleOverride ?? "note");
1586
+ const roleAttr = ariaRole ? ` role="${ariaRole}"` : "";
1552
1587
  const safeCalloutClass = escapeAttribute(options.calloutClass);
1553
1588
  const safeConfigClassName = escapeAttribute(config.className);
1554
1589
  const safeCalloutTitleClass = escapeAttribute(options.calloutTitleClass);
@@ -1588,10 +1623,15 @@ function buildCalloutFromParts(parsed, config, bodyHtml, options) {
1588
1623
  const labelledby = renderTitle ? ` aria-labelledby="${titleId}"` : "";
1589
1624
  const appearanceClass = appearance !== "default" ? ` callout-${appearance}` : "";
1590
1625
  const inlineClass = parsed.overrides?.inline ? ` callout-${parsed.overrides.inline}` : "";
1626
+ // v2.0.0: sr-only text (same as buildCalloutHtml)
1627
+ const srOnlyHtml = options.srIconText && renderTitle
1628
+ ? `<span class="sr-only">${escapeHtml(title)}:</span>`
1629
+ : "";
1591
1630
  const titleBlock = renderTitle
1592
1631
  ? [
1593
1632
  ` <div class="${safeCalloutTitleClass}" style="color:${safeIconColor}" id="${titleId}" dir="auto">`,
1594
1633
  iconSpan && ` ${iconSpan}`,
1634
+ srOnlyHtml && ` ${srOnlyHtml}`,
1595
1635
  ` <span class="callout-title-text">${escapeHtml(title)}</span>`,
1596
1636
  ` </div>`,
1597
1637
  ].filter(Boolean).join("\n")
@@ -1599,7 +1639,7 @@ function buildCalloutFromParts(parsed, config, bodyHtml, options) {
1599
1639
  if (parsed.collapsible) {
1600
1640
  const openAttr = parsed.collapsibleOpen ? " open" : "";
1601
1641
  return [
1602
- `<details class="${safeCalloutClass} ${safeConfigClassName} collapsible${appearanceClass}${inlineClass}${directiveClasses}"${dataAttr}${collapsibleDataAttr} role="${ariaRole}"${labelledby} dir="auto"${openAttr}${directiveId}${directiveAttrs}>`,
1642
+ `<details class="${safeCalloutClass} ${safeConfigClassName} collapsible${appearanceClass}${inlineClass}${directiveClasses}"${dataAttr}${collapsibleDataAttr}${roleAttr}${labelledby} dir="auto"${openAttr}${directiveId}${directiveAttrs}>`,
1603
1643
  titleBlock.replace(/<div /, "<summary ").replace(/<\/div>$/, "</summary>"),
1604
1644
  ` <div class="${safeCalloutBodyClass}">`,
1605
1645
  bodyHtml,
@@ -1608,7 +1648,7 @@ function buildCalloutFromParts(parsed, config, bodyHtml, options) {
1608
1648
  ].filter(Boolean).join("\n");
1609
1649
  }
1610
1650
  return [
1611
- `<aside class="${safeCalloutClass} ${safeConfigClassName}${appearanceClass}${inlineClass}${directiveClasses}"${dataAttr}${collapsibleDataAttr} role="${ariaRole}"${labelledby} dir="auto"${directiveId}${directiveAttrs}>`,
1651
+ `<aside class="${safeCalloutClass} ${safeConfigClassName}${appearanceClass}${inlineClass}${directiveClasses}"${dataAttr}${collapsibleDataAttr}${roleAttr}${labelledby} dir="auto"${directiveId}${directiveAttrs}>`,
1612
1652
  titleBlock,
1613
1653
  ` <div class="${safeCalloutBodyClass}">`,
1614
1654
  bodyHtml,
@@ -1624,6 +1664,19 @@ export const remarkRemakeBlocks = (userOptions) => {
1624
1664
  ...DEFAULT_OPTIONS,
1625
1665
  ...userOptions,
1626
1666
  };
1667
+ // v1.10.0: resolve devWarnings. undefined → auto (true in dev, false in prod).
1668
+ const isProd = typeof process !== "undefined" && process.env && process.env.NODE_ENV === "production";
1669
+ const devWarningsEnabled = options.devWarnings === undefined ? !isProd : options.devWarnings;
1670
+ // v1.10.0: strict customCallouts validation. Throws at init time when
1671
+ // strictConfigValidation is true and any config entry is missing required
1672
+ // fields. In non-strict mode, emits a dev warning instead.
1673
+ if (options.customCallouts && options.customCallouts.length > 0) {
1674
+ const errors = validateCustomCallouts(options.customCallouts, options.strictConfigValidation);
1675
+ if (errors.length > 0 && !options.strictConfigValidation) {
1676
+ for (const e of errors)
1677
+ warn(devWarningsEnabled, e);
1678
+ }
1679
+ }
1627
1680
  // v1.8.0: resolve `syntax` into enableDirectiveSyntax / enableMkDocsSyntax.
1628
1681
  // Explicit `enableDirectiveSyntax` / `enableMkDocsSyntax` (v1.3.0 / v1.4.0
1629
1682
  // API) take precedence over `syntax` for backward compatibility.
@@ -1643,6 +1696,10 @@ export const remarkRemakeBlocks = (userOptions) => {
1643
1696
  }
1644
1697
  const configMap = buildCalloutConfigMap(options);
1645
1698
  const calloutPattern = options.calloutPattern || buildCalloutPattern(configMap, options.enableDisclosures);
1699
+ // v1.10.0: stash devWarnings flag + known types list on options so
1700
+ // buildCalloutHtml / parseCalloutDirective can emit warnings.
1701
+ options._devWarnings = devWarningsEnabled;
1702
+ options._knownTypes = Array.from(configMap.keys());
1646
1703
  // v1.9.0: sprite collector — accumulates { symbolId: svgInnerHtml } entries
1647
1704
  // across all callouts in this document. Populated by buildCalloutHtml when
1648
1705
  // icons.strategy === 'sprite'. Emitted as a single <svg> sprite at the top
@@ -1667,6 +1724,37 @@ export const remarkRemakeBlocks = (userOptions) => {
1667
1724
  if (options.enableMkDocsSyntax) {
1668
1725
  transformMkDocsSyntax(tree, configMap, options);
1669
1726
  }
1727
+ // v1.10.0: Pass 0c — Scan for unknown callout directives and emit dev warnings.
1728
+ // The calloutPattern only matches KNOWN types, so unknown types like
1729
+ // [!TYPO] fall through to plain blockquotes WITHOUT entering parseCalloutDirective.
1730
+ // This pre-scan catches them so users get a "did you mean" hint.
1731
+ if (devWarningsEnabled) {
1732
+ const knownTypes = options._knownTypes;
1733
+ const anyDirectiveRe = /^\[!([A-Za-z][A-Za-z0-9-]*)\]/;
1734
+ visit(tree, "blockquote", (bq) => {
1735
+ const first = bq.children[0];
1736
+ if (!first || first.type !== "paragraph")
1737
+ return;
1738
+ const text = extractTextContent(first);
1739
+ if (!text)
1740
+ return;
1741
+ const m = text.match(anyDirectiveRe);
1742
+ if (!m)
1743
+ return;
1744
+ const rawType = m[1];
1745
+ const lowerType = rawType.toLowerCase();
1746
+ // Skip disclosures (empty type), pull quotes, epigraphs, and known types
1747
+ if (lowerType === "" || lowerType === "pull" || lowerType === "epigraph")
1748
+ return;
1749
+ if (configMap.has(lowerType))
1750
+ return;
1751
+ const suggestion = suggestSimilarType(rawType, knownTypes);
1752
+ const msg = suggestion
1753
+ ? `Unknown callout type '${rawType}'. Did you mean '${suggestion}'?`
1754
+ : `Unknown callout type '${rawType}'. It will render as a plain blockquote.`;
1755
+ warn(true, msg);
1756
+ });
1757
+ }
1670
1758
  // ── Pass 1: Transform blockquotes → callouts / disclosures ──────
1671
1759
  // We must process DEEPEST blockquotes first (inside-out) so that
1672
1760
  // nested [!] directives are converted before their parents read them.
@@ -1703,7 +1791,7 @@ export const remarkRemakeBlocks = (userOptions) => {
1703
1791
  if (foundIdx === -1)
1704
1792
  continue; // Already replaced
1705
1793
  }
1706
- const parsed = parseCalloutDirective(bq, calloutPattern, configMap, options.enableDisclosures, options.defaultCollapse);
1794
+ const parsed = parseCalloutDirective(bq, calloutPattern, configMap, options.enableDisclosures, options.defaultCollapse, options);
1707
1795
  if (parsed) {
1708
1796
  // For disclosures: depth > 0 means it's nested inside another blockquote
1709
1797
  // (which is likely another disclosure or callout) → tree view
@@ -1733,7 +1821,7 @@ export const remarkRemakeBlocks = (userOptions) => {
1733
1821
  }
1734
1822
  // ── Pass 2: Group consecutive disclosures into accordions ───────
1735
1823
  if (options.enableAccordion && options.enableDisclosures) {
1736
- groupAccordions(tree, options.accordionClass);
1824
+ groupAccordions(tree, options.accordionClass, options.ariaAccordion);
1737
1825
  }
1738
1826
  // v1.9.0: Pass 3 — Emit icon sprite at the top of the document.
1739
1827
  // Only when icons.strategy === 'sprite' AND at least one callout
@@ -1753,7 +1841,7 @@ export const remarkRemakeBlocks = (userOptions) => {
1753
1841
  // ---------------------------------------------------------------------------
1754
1842
  // Pass 2: Group consecutive disclosure <details> into accordion wrapper
1755
1843
  // ---------------------------------------------------------------------------
1756
- function groupAccordions(tree, accordionClass) {
1844
+ function groupAccordions(tree, accordionClass, ariaAccordion) {
1757
1845
  function walk(node) {
1758
1846
  if (!node.children)
1759
1847
  return;
@@ -1784,9 +1872,28 @@ function groupAccordions(tree, accordionClass) {
1784
1872
  }
1785
1873
  // If we found 2+ consecutive disclosures, wrap in accordion
1786
1874
  if (group.length >= 2) {
1875
+ // v2.0.0: when ariaAccordion is enabled, add role="accordion" to the
1876
+ // wrapper and aria-expanded to each <summary> (reflecting open state).
1877
+ // Also add tabindex="0" to <summary> if not already focusable (native
1878
+ // summary is focusable by default, but explicit tabindex aids older
1879
+ // browsers and signals intent for the accordion.js keyboard handler).
1880
+ const accordionRole = ariaAccordion ? ` role="accordion"` : "";
1881
+ let groupHtml = group;
1882
+ if (ariaAccordion) {
1883
+ groupHtml = group.map(detailsHtml => {
1884
+ // Inject aria-expanded onto each <summary> based on whether the
1885
+ // parent <details> has the `open` attribute.
1886
+ const isOpen = /<details[^>]*\sopen[\s>]/.test(detailsHtml);
1887
+ const expanded = isOpen ? "true" : "false";
1888
+ if (/<summary[^>]*>/.test(detailsHtml)) {
1889
+ return detailsHtml.replace(/(<summary[^>]*?)(>)/, `$1 aria-expanded="${expanded}"$2`);
1890
+ }
1891
+ return detailsHtml;
1892
+ });
1893
+ }
1787
1894
  const accordionHtml = [
1788
- `<div class="${escapeAttribute(accordionClass)}" data-accordion>`,
1789
- ...group,
1895
+ `<div class="${escapeAttribute(accordionClass)}"${accordionRole} data-accordion>`,
1896
+ ...groupHtml,
1790
1897
  `</div>`,
1791
1898
  ].join("\n");
1792
1899
  newChildren.push(html(accordionHtml));
@@ -1816,8 +1923,15 @@ function groupAccordions(tree, accordionClass) {
1816
1923
  */
1817
1924
  function isDisclosureHtml(htmlStr) {
1818
1925
  const trimmed = htmlStr.trim();
1926
+ // v2.0.0: fix pre-existing bug — the old `!trimmed.includes('callout')` check
1927
+ // accidentally rejected ALL disclosures because their HTML contains
1928
+ // `aria-labelledby="callout-disclosure-N"`. The intent was to exclude
1929
+ // callout-details (which have `class="callout ..."`) from accordion grouping.
1930
+ // The correct check: the details element must have class="disclosure" (the
1931
+ // exact disclosure class) and NOT have the `callout` class (which would make
1932
+ // it a collapsible callout instead).
1819
1933
  return trimmed.startsWith('<details class="disclosure"') &&
1820
- !trimmed.includes('callout');
1934
+ !trimmed.startsWith('<details class="disclosure-accordion"');
1821
1935
  }
1822
1936
  // ---------------------------------------------------------------------------
1823
1937
  // Backward-compatible alias