origam 2.34.0 → 2.35.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/CHANGELOG.md CHANGED
@@ -18,6 +18,14 @@ This project follows [Semantic Versioning](https://semver.org).
18
18
 
19
19
  ## [Unreleased]
20
20
 
21
+ ## [2.35.0] - 2026-10-19
22
+
23
+ ### Added — `OrigamCode` : `lang` ouvert, `createOrigam({ code: { langs } })` (#1180)
24
+
25
+ - `lang` n'est plus limité aux 14 grammaires de `SUPPORTED_LANGS` quand un `code.highlighter` est fourni : la valeur est transmise telle quelle au highlighter, qui sait seul ce qu'il peut servir. S'il échoue, le bloc retombe sur `plaintext` (le `try/catch` existant autour de `codeToHtml`), avec un avertissement dev-only, une fois par langage. Avant, `resolveLang()` repliait tout `lang` inconnu sur `plaintext` avant de consulter le highlighter, donc `dockerfile` ne pouvait jamais être servi.
26
+ - Nouvelle option `createOrigam({ code: { langs } })` : liste passée à `createHighlighter` (défaut : `SUPPORTED_LANGS`). Avec le highlighter intégré, un `lang` présent dans `langs` est accepté.
27
+ - Typage : la prop `lang` devient `TCodeLangOpen` (`TCodeLang | (string & {})`), l'autocomplétion des 14 valeurs est conservée. `TCodeLang` reste la liste fermée.
28
+
21
29
  ## [2.34.0] - 2026-10-19
22
30
 
23
31
  ### Added — `OrigamCommandPalette` : prop `filter` pour afficher des résultats classés en externe (#1184)
@@ -34,14 +34,14 @@ function lruSet(key, value) {
34
34
  }
35
35
  _cache.set(key, value);
36
36
  }
37
- async function loadHighlighter(loader) {
37
+ async function loadHighlighter(loader, langs) {
38
38
  if (_highlighterUnavailable) return null;
39
39
  if (_highlighterPromise) return _highlighterPromise;
40
40
  _highlighterPromise = (async () => {
41
41
  const shiki = loader ? await loader() : await Promise.resolve().then(() => require("shiki"));
42
42
  const highlighter = await shiki.createHighlighter({
43
43
  themes: [_code.CODE_LIGHT_THEME, _code.CODE_DARK_THEME],
44
- langs: [..._code.SUPPORTED_LANGS]
44
+ langs: [...(langs ?? _code.SUPPORTED_LANGS)]
45
45
  });
46
46
  _highlighterReady = true;
47
47
  return highlighter;
@@ -56,22 +56,26 @@ async function loadHighlighter(loader) {
56
56
  return null;
57
57
  });
58
58
  }
59
- function resolveLang(lang) {
59
+ function resolveLang(lang, hasCustomHighlighter, extraLangs) {
60
+ if (hasCustomHighlighter) return lang;
60
61
  if (_code.SUPPORTED_LANGS.includes(lang)) return lang;
62
+ if (extraLangs?.includes(lang)) return lang;
61
63
  if (!_unsupportedLangWarned.has(lang)) {
62
64
  _unsupportedLangWarned.add(lang);
63
- console.warn(`[origam] OrigamCode: lang="${lang}" is not in the bundled grammar set. Falling back to "${_enums.CODE_LANG.PLAINTEXT}". Supported: ${_code.SUPPORTED_LANGS.join(", ")}.`);
65
+ console.warn(`[origam] OrigamCode: lang="${lang}" is not in the bundled grammar set. Falling back to "${_enums.CODE_LANG.PLAINTEXT}". Supported: ${[..._code.SUPPORTED_LANGS, ...(extraLangs ?? [])].join(", ")}. Add it with createOrigam({ code: { langs } }) or supply a code.highlighter.`);
64
66
  }
65
67
  return _enums.CODE_LANG.PLAINTEXT;
66
68
  }
67
69
  function useCode() {
68
- const highlighterLoader = (0, _vue.inject)(_code.ORIGAM_CODE_KEY)?.highlighter;
70
+ const codeOptions = (0, _vue.inject)(_code.ORIGAM_CODE_KEY);
71
+ const highlighterLoader = codeOptions?.highlighter;
72
+ const customLangs = codeOptions?.langs;
69
73
  async function highlight(code, lang) {
70
- const resolvedLang = resolveLang(lang);
74
+ const resolvedLang = resolveLang(lang, highlighterLoader !== void 0, customLangs);
71
75
  const key = makeCacheKey(code, resolvedLang);
72
76
  const cached = lruGet(key);
73
77
  if (cached !== void 0) return cached;
74
- const highlighter = await loadHighlighter(highlighterLoader);
78
+ const highlighter = await loadHighlighter(highlighterLoader, customLangs);
75
79
  if (!highlighter) {
76
80
  const plain = escapeHtml(code);
77
81
  lruSet(key, plain);
@@ -89,7 +93,10 @@ function useCode() {
89
93
  try {
90
94
  html = highlighter.codeToHtml(code, opts);
91
95
  } catch (err) {
92
- console.warn("[origam] OrigamCode: shiki failed to tokenise, falling back to plaintext.", err);
96
+ if (process.env?.DEV && !_unsupportedLangWarned.has(resolvedLang)) {
97
+ _unsupportedLangWarned.add(resolvedLang);
98
+ console.warn(`[origam] OrigamCode: shiki could not tokenise lang="${resolvedLang}", falling back to plaintext.`, err);
99
+ }
93
100
  html = highlighter.codeToHtml(code, {
94
101
  ...opts,
95
102
  lang: _enums.CODE_LANG.PLAINTEXT
@@ -99,7 +106,7 @@ function useCode() {
99
106
  return html;
100
107
  }
101
108
  async function prime() {
102
- await loadHighlighter(highlighterLoader);
109
+ await loadHighlighter(highlighterLoader, customLangs);
103
110
  }
104
111
  function isReady() {
105
112
  return _highlighterReady;
@@ -27,7 +27,7 @@ function lruSet(key, value) {
27
27
  }
28
28
  _cache.set(key, value);
29
29
  }
30
- async function loadHighlighter(loader) {
30
+ async function loadHighlighter(loader, langs) {
31
31
  if (_highlighterUnavailable) return null;
32
32
  if (_highlighterPromise) return _highlighterPromise;
33
33
  _highlighterPromise = (async () => {
@@ -37,7 +37,7 @@ async function loadHighlighter(loader) {
37
37
  );
38
38
  const highlighter = await shiki.createHighlighter({
39
39
  themes: [LIGHT_THEME, DARK_THEME],
40
- langs: [...SUPPORTED_LANGS]
40
+ langs: [...langs ?? SUPPORTED_LANGS]
41
41
  });
42
42
  _highlighterReady = true;
43
43
  return highlighter;
@@ -55,24 +55,28 @@ async function loadHighlighter(loader) {
55
55
  return null;
56
56
  });
57
57
  }
58
- function resolveLang(lang) {
58
+ function resolveLang(lang, hasCustomHighlighter, extraLangs) {
59
+ if (hasCustomHighlighter) return lang;
59
60
  if (SUPPORTED_LANGS.includes(lang)) return lang;
61
+ if (extraLangs?.includes(lang)) return lang;
60
62
  if (!_unsupportedLangWarned.has(lang)) {
61
63
  _unsupportedLangWarned.add(lang);
62
64
  console.warn(
63
- `[origam] OrigamCode: lang="${lang}" is not in the bundled grammar set. Falling back to "${CODE_LANG.PLAINTEXT}". Supported: ${SUPPORTED_LANGS.join(", ")}.`
65
+ `[origam] OrigamCode: lang="${lang}" is not in the bundled grammar set. Falling back to "${CODE_LANG.PLAINTEXT}". Supported: ${[...SUPPORTED_LANGS, ...extraLangs ?? []].join(", ")}. Add it with createOrigam({ code: { langs } }) or supply a code.highlighter.`
64
66
  );
65
67
  }
66
68
  return CODE_LANG.PLAINTEXT;
67
69
  }
68
70
  export function useCode() {
69
- const highlighterLoader = inject(ORIGAM_CODE_KEY)?.highlighter;
71
+ const codeOptions = inject(ORIGAM_CODE_KEY);
72
+ const highlighterLoader = codeOptions?.highlighter;
73
+ const customLangs = codeOptions?.langs;
70
74
  async function highlight(code, lang) {
71
- const resolvedLang = resolveLang(lang);
75
+ const resolvedLang = resolveLang(lang, highlighterLoader !== void 0, customLangs);
72
76
  const key = makeCacheKey(code, resolvedLang);
73
77
  const cached = lruGet(key);
74
78
  if (cached !== void 0) return cached;
75
- const highlighter = await loadHighlighter(highlighterLoader);
79
+ const highlighter = await loadHighlighter(highlighterLoader, customLangs);
76
80
  if (!highlighter) {
77
81
  const plain = escapeHtml(code);
78
82
  lruSet(key, plain);
@@ -87,14 +91,17 @@ export function useCode() {
87
91
  try {
88
92
  html = highlighter.codeToHtml(code, opts);
89
93
  } catch (err) {
90
- console.warn("[origam] OrigamCode: shiki failed to tokenise, falling back to plaintext.", err);
94
+ if (import.meta.env?.DEV && !_unsupportedLangWarned.has(resolvedLang)) {
95
+ _unsupportedLangWarned.add(resolvedLang);
96
+ console.warn(`[origam] OrigamCode: shiki could not tokenise lang="${resolvedLang}", falling back to plaintext.`, err);
97
+ }
91
98
  html = highlighter.codeToHtml(code, { ...opts, lang: CODE_LANG.PLAINTEXT });
92
99
  }
93
100
  lruSet(key, html);
94
101
  return html;
95
102
  }
96
103
  async function prime() {
97
- await loadHighlighter(highlighterLoader);
104
+ await loadHighlighter(highlighterLoader, customLangs);
98
105
  }
99
106
  function isReady() {
100
107
  return _highlighterReady;
@@ -7,7 +7,7 @@ import type { IMarginProps } from '../Commons/margin.interface';
7
7
  import type { IPaddingProps } from '../Commons/padding.interface';
8
8
  import type { IRoundedProps } from '../Commons/rounded.interface';
9
9
  import type { ITypographyProps } from '../Commons/typography.interface';
10
- import type { TCodeLang, TShikiHighlighterLoader } from '../../types/Code/code.type';
10
+ import type { TCodeLangOpen, TShikiHighlighterLoader } from '../../types/Code/code.type';
11
11
  /**
12
12
  * Props for `<OrigamCode>` — a shiki-powered code block with line numbers,
13
13
  * line highlighting, copy button and theme awareness.
@@ -26,11 +26,15 @@ export interface ICodeProps extends ICommonsComponentProps, ITagProps, IBorderPr
26
26
  */
27
27
  code?: string;
28
28
  /**
29
- * Language grammar to apply. One of the values in `SUPPORTED_LANGS`.
30
- * Unknown values fall back to `plaintext` (with a one-shot console
31
- * warning).
29
+ * Language grammar to apply. Autocompletes the 14 values of
30
+ * `SUPPORTED_LANGS` but accepts any string (#1180). Without a custom
31
+ * `code.highlighter`, a value outside `SUPPORTED_LANGS` and outside
32
+ * `createOrigam({ code: { langs } })` falls back to `plaintext` (one-shot
33
+ * console warning). With a custom `code.highlighter`, the value is handed
34
+ * to it as-is; if it cannot tokenise it, the block falls back to
35
+ * `plaintext` (dev-only warning).
32
36
  */
33
- lang?: TCodeLang;
37
+ lang?: TCodeLangOpen;
34
38
  /**
35
39
  * Show a left gutter with line numbers. Pure CSS — uses a CSS counter
36
40
  * incremented per `.origam-code__row`, no JS layout work.
@@ -90,7 +94,7 @@ export interface ICodeProps extends ICommonsComponentProps, ITagProps, IBorderPr
90
94
  *
91
95
  * @description
92
96
  * App-level config for `<OrigamCode>`, passed via `createOrigam({ code })`.
93
- * Today it carries a single knob — `highlighter` — but it is its own
97
+ * Today it carries two knobs — `highlighter` and `langs` — and it is its own
94
98
  * interface (rather than a bare function on `IOrigamOptions`) so a future
95
99
  * `OrigamCode`-wide setting (e.g. a default theme pair) has somewhere to
96
100
  * land without another breaking shape change.
@@ -116,6 +120,23 @@ export interface ICodeProps extends ICommonsComponentProps, ITagProps, IBorderPr
116
120
  ********************************************************/
117
121
  export interface ICodeOptions {
118
122
  highlighter?: TShikiHighlighterLoader;
123
+ /*********************************************************
124
+ * langs
125
+ *
126
+ * @description
127
+ * Grammars handed to `createHighlighter({ langs })`. Default:
128
+ * `SUPPORTED_LANGS` (the 14 bundled ones). Replaces the default list when
129
+ * set — include `plaintext` if you rely on it.
130
+ *
131
+ * @description
132
+ * With the built-in highlighter, a `lang` listed here is accepted instead
133
+ * of being folded onto `plaintext`; with a custom `highlighter`, `lang` is
134
+ * never filtered, and this only controls what is loaded up front.
135
+ *
136
+ * @description
137
+ * Like `highlighter`, read once (first `<OrigamCode>` to mount wins).
138
+ ********************************************************/
139
+ langs?: ReadonlyArray<string>;
119
140
  }
120
141
  /** Emits fired by `<OrigamCode>` — copy-to-clipboard confirmation. */
121
142
  export interface ICodeEmits {
@@ -127,7 +148,7 @@ export interface ICodeSlots {
127
148
  * consumer can rebuild the default filename/lang badge + copy button. */
128
149
  header?: (data: {
129
150
  filename: string | undefined;
130
- langName: TCodeLang | undefined;
151
+ langName: TCodeLangOpen | undefined;
131
152
  copy: () => Promise<void>;
132
153
  copied: boolean;
133
154
  }) => any;
@@ -146,7 +167,7 @@ export interface IUseCodeReturn {
146
167
  * underlying shiki highlighter has loaded the requested language /
147
168
  * theme (lazy; ~200 ms cold, instant on subsequent calls).
148
169
  */
149
- highlight(code: string, lang: TCodeLang): Promise<string>;
170
+ highlight(code: string, lang: TCodeLangOpen): Promise<string>;
150
171
  /**
151
172
  * Force the highlighter to load. Useful for stories / tests that want
152
173
  * to warm the cache before mounting consumers.
@@ -1,7 +1,21 @@
1
1
  import { CODE_LANG, CODE_THEME } from '../../enums/Code/code.enum';
2
2
  import OrigamCode from '../../components/Code/OrigamCode.vue';
3
+ import type { TAnyString } from '../Commons/commons.type';
3
4
  export type TOrigamCode = InstanceType<typeof OrigamCode>;
4
5
  export type TCodeLang = `${CODE_LANG}`;
6
+ /*********************************************************
7
+ * TCodeLangOpen
8
+ *
9
+ * @description
10
+ * Open form of `TCodeLang` accepted by the `lang` prop (#1180): the 14 bundled
11
+ * grammars keep their autocompletion, any other string is allowed because a
12
+ * `createOrigam({ code: { highlighter, langs } })` highlighter can serve it.
13
+ *
14
+ * @description
15
+ * `TAnyString` (Commons) stops the union from collapsing to plain `string`,
16
+ * and avoids the empty-object intersection Sonar flags (#771).
17
+ ********************************************************/
18
+ export type TCodeLangOpen = TCodeLang | TAnyString;
5
19
  export type TCodeTheme = `${CODE_THEME}`;
6
20
  /**
7
21
  * The slice of shiki's highlighter API that `useCode()` actually calls.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "origam",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "type": "module",
5
5
  "description": "origam — a Vue 3 design system with multi-theme design tokens, CSS-first components and TypeScript types.",
6
6
  "keywords": [