origam 2.33.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,22 @@ 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
+
29
+ ## [2.34.0] - 2026-10-19
30
+
31
+ ### Added — `OrigamCommandPalette` : prop `filter` pour afficher des résultats classés en externe (#1184)
32
+
33
+ - Nouvelle prop `filter` : `true` (défaut, comportement inchangé : correspondance floue intégrée et tri par score), `false` (les `commands` sont affichées telles quelles, dans l'ordre fourni) ou une fonction `(commands, query) => commands` dont le résultat est affiché dans son ordre.
34
+ - Destinée à la recherche serveur ou plein texte alimentée par l'événement `query` : un résultat trouvé dans le corps d'un document n'est plus masqué parce que la requête n'est pas une sous-séquence de son libellé, et le classement du moteur est conservé.
35
+ - Avec `filter=false` ou une fonction, une liste `commands` vide reste vide : elle ne retombe plus sur le registre `useCommand()` (le repli est conservé avec `filter=true`).
36
+
21
37
  ## [2.33.0] - 2026-10-18
22
38
 
23
39
  ### Added — `OrigamCommandPalette` : icônes de recherche et de chargement personnalisables (#1183)
@@ -191,6 +191,7 @@ const props = defineProps({
191
191
  modelValue: { type: Boolean, required: false, default: false },
192
192
  hotkey: { type: [Array, null], required: false, default: () => COMMAND_PALETTE_DEFAULT_HOTKEY },
193
193
  commands: { type: Array, required: false },
194
+ filter: { type: [Boolean, Function], required: false, default: true },
194
195
  placeholder: { type: String, required: false, default: "origam.command_palette.placeholder" },
195
196
  emptyText: { type: String, required: false, default: "origam.command_palette.empty_text" },
196
197
  maxHeight: { type: [Number, String], required: false, default: COMMAND_PALETTE_DEFAULT_MAX_HEIGHT },
@@ -260,11 +261,17 @@ if (IN_BROWSER) {
260
261
  }, { immediate: true });
261
262
  }
262
263
  const sourceCommands = computed(() => {
263
- if (props.commands && props.commands.length > 0) return props.commands;
264
+ if (props.commands && (props.commands.length > 0 || props.filter !== true)) return props.commands;
264
265
  return registryCommands.value;
265
266
  });
266
267
  const query = ref("");
267
268
  const filtered = computed(() => {
269
+ if (typeof props.filter === "function") {
270
+ return props.filter(sourceCommands.value, query.value).map((item) => ({ item, score: 0 }));
271
+ }
272
+ if (props.filter === false) {
273
+ return sourceCommands.value.map((item) => ({ item, score: 0 }));
274
+ }
268
275
  return fuzzyMatch(sourceCommands.value, query.value, (cmd) => ({
269
276
  label: cmd.label,
270
277
  haystack: [
@@ -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,4 +1,5 @@
1
1
  import type { ICommand } from './command.interface';
2
+ import type { TCommandPaletteFilter } from '../../types/CommandPalette/command-palette.type';
2
3
  import type { IAttachProps } from '../Commons/attach.interface';
3
4
  import type { ICommonsComponentProps } from '../Commons/commons.interface';
4
5
  import type { ITypographyProps } from '../Commons/typography.interface';
@@ -27,6 +28,17 @@ export interface ICommandPaletteProps extends ICommonsComponentProps, Pick<ITypo
27
28
  * registered via the `useCommand()` global registry instead.
28
29
  */
29
30
  commands?: ReadonlyArray<ICommand>;
31
+ /*********************************************************
32
+ * filter (#1184)
33
+ *
34
+ * @description
35
+ * Comment `commands` est filtré selon la requête. `true` (défaut) : fuzzy match intégré, qui garde les sous-séquences et re-trie par score.
36
+ * @description
37
+ * `false` : `commands` affichées telles quelles, dans l'ordre fourni (liste déjà filtrée/classée par un serveur ou un moteur plein-texte, alimentée via l'event `query`).
38
+ * @description
39
+ * Fonction `(commands, query) => commands` : affiche ce qu'elle renvoie, dans son ordre. Dans les deux modes non défaut, la liste est plate (sans en-tête de groupe) tant que la requête est non vide, aucun match n'est surligné, et `commands` est honorée même vide (pas de repli sur le registre `useCommand()`).
40
+ ********************************************************/
41
+ filter?: TCommandPaletteFilter;
30
42
  /** Placeholder of the search input (already-translated string). */
31
43
  placeholder?: string;
32
44
  /** Empty-state message when no command matches the query. */
@@ -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.
@@ -1,3 +1,4 @@
1
+ import type { ICommand } from '../../interfaces/CommandPalette/command.interface';
1
2
  import OrigamCommandPalette from '../../components/CommandPalette/OrigamCommandPalette.vue';
2
3
  export type TOrigamCommandPalette = InstanceType<typeof OrigamCommandPalette>;
3
4
  /**
@@ -5,3 +6,13 @@ export type TOrigamCommandPalette = InstanceType<typeof OrigamCommandPalette>;
5
6
  * accepted by `useHotkey` (e.g. `['meta', 'k']`).
6
7
  */
7
8
  export type TCommandPaletteHotkeyCombination = ReadonlyArray<string>;
9
+ /*********************************************************
10
+ * Filter de OrigamCommandPalette (#1184)
11
+ *
12
+ * @description
13
+ * `TCommandPaletteFilterFn` : reçoit les commandes et la requête brute (non trimée), renvoie les commandes à afficher, dans l'ordre d'affichage.
14
+ * @description
15
+ * `TCommandPaletteFilter` (prop `filter`) : `true` = fuzzy match intégré (filtre par sous-séquence + tri par score), `false` = `commands` affichées telles quelles et dans l'ordre fourni, fonction = son résultat est affiché dans son ordre.
16
+ ********************************************************/
17
+ export type TCommandPaletteFilterFn = (commands: ReadonlyArray<ICommand>, query: string) => ReadonlyArray<ICommand>;
18
+ export type TCommandPaletteFilter = boolean | TCommandPaletteFilterFn;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "origam",
3
- "version": "2.33.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": [