@navette-js/infolists 1.0.0-beta.1 → 1.0.0-beta.3

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/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @navette-js/infolists
2
2
 
3
- Infolist entries for Navette, the Node.js port of Filament.
3
+ Read-only infolist entries for Navette admin panels.
4
4
 
5
- Part of [Navette](https://github.com/NavetteJS/navette), a Node.js port of [Filament](https://filamentphp.com). Install the whole framework with `npm install @navette-js/panels`.
5
+ Part of [Navette](https://github.com/NavetteJS/navette), admin panels for Node.js. Start a project with `npm create navette@latest`, or install the framework with `npm install @navette-js/panels`.
6
6
 
7
7
  **Beta:** the API may still change between beta releases.
8
8
 
package/lib/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Component, type ComponentContext } from '@navette-js/schemas';
2
2
  import { type ColorManager } from '@navette-js/support';
3
3
  export declare const useColorManager: (manager: ColorManager) => void;
4
- /** Base des entrées : libellé, valeur lue dans l'enregistrement (chemin pointé accepté) ou calculée. */
4
+ /** Base entry: a label and a value read from the record (dotted paths allowed) or computed. */
5
5
  declare abstract class Entry extends Component {
6
6
  protected _label: string | null;
7
7
  protected _state: ((ctx: ComponentContext) => unknown | Promise<unknown>) | null;
@@ -9,16 +9,17 @@ declare abstract class Entry extends Component {
9
9
  hasStatePath(): boolean;
10
10
  label(label: string): this;
11
11
  protected _hiddenLabel: boolean;
12
- /** Libellé gardé pour les lecteurs d'écran seulement. */
12
+ /** Keeps the label for screen readers only. */
13
13
  hiddenLabel(condition?: boolean): this;
14
- /** Valeur calculée ; sans cela, l'attribut du même nom de l'enregistrement est affiché. */
14
+ /** Computed value; without it, the record attribute of the same name is shown. */
15
15
  state(resolver: (ctx: ComponentContext) => unknown | Promise<unknown>): this;
16
16
  protected getState(): Promise<unknown>;
17
- /** Balisage de Entry::wrapEmbeddedHtml(). */
17
+ /** Wraps the content with the entry label. */
18
18
  protected wrap(content: string): string;
19
19
  }
20
- /** Filtre appliqué au HTML qu'une entrée affiche tel quel (`markdown()`). Sans filtre, le contenu est échappé. */
20
+ /** Sanitizer applied to HTML an entry renders as is (`markdown()`). Without one, the content is escaped. */
21
21
  export declare const useHtmlSanitizer: (sanitizer: ((html: string) => string) | null) => void;
22
+ /** Read-only text: badge, color, icon, money, date, placeholder. */
22
23
  export declare class TextEntry extends Entry {
23
24
  private _badge;
24
25
  private _color;
@@ -46,48 +47,48 @@ export declare class TextEntry extends Entry {
46
47
  suffix(text: string | null): this;
47
48
  private _prose;
48
49
  private _markdown;
49
- /** Mise en forme typographique du contenu (`fi-prose`). */
50
+ /** Typographic styling of the content (`fi-prose`). */
50
51
  prose(condition?: boolean): this;
51
52
  /**
52
- * Le contenu est du texte enrichi : il est rendu en HTML après nettoyage par le filtre posé avec `useHtmlSanitizer()`.
53
- * Port partiel de `markdown()` : le HTML déjà présent est conservé (nettoyé), le texte brut devient des paragraphes ; la syntaxe Markdown n'est pas convertie.
53
+ * Rich text content, rendered as HTML after the sanitizer set with `useHtmlSanitizer()`.
54
+ * Existing HTML is kept (sanitized) and plain text becomes paragraphs; Markdown syntax itself is not converted.
54
55
  */
55
56
  markdown(condition?: boolean): this;
56
57
  private _copyable;
57
58
  private _numeric;
58
- /** Un clic copie la valeur dans le presse-papiers. */
59
+ /** Clicking copies the value to the clipboard. */
59
60
  copyable(condition?: boolean): this;
60
- /** Nombre formaté (séparateurs de milliers), aligné comme un nombre. */
61
+ /** Formatted number (thousands separators), aligned as a number. */
61
62
  numeric(condition?: boolean): this;
62
63
  toHtml(): Promise<string>;
63
64
  }
64
- /** Pastille de couleur (port de ColorEntry) : seules les couleurs hexadécimales ou fonctionnelles simples sont injectées dans le style. */
65
+ /** Color swatch: only hex or simple functional colors are injected into the style attribute. */
65
66
  export declare class ColorEntry extends Entry {
66
67
  private _placeholder;
67
68
  static make(name: string): ColorEntry;
68
69
  placeholder(text: string | null): this;
69
70
  toHtml(): Promise<string>;
70
71
  }
71
- /** Image en lecture seule (port de ImageEntry) : seules une adresse du site (`/…`) ou une adresse http(s) sont affichées. */
72
+ /** Read-only image: only same-site (`/…`) or http(s) URLs are rendered. */
72
73
  export declare class ImageEntry extends Entry {
73
74
  private _height;
74
75
  private _alt;
75
76
  private _circular;
76
77
  static make(name: string): ImageEntry;
77
- /** Hauteur CSS de l'image (`8rem` par défaut, comme Filament). */
78
+ /** CSS height of the image (`8rem` by default). */
78
79
  imageHeight(height: string): this;
79
80
  alt(text: string | null): this;
80
81
  circular(condition?: boolean): this;
81
82
  toHtml(): Promise<string>;
82
83
  }
83
- /** Icône selon la valeur ; `boolean()` affiche une coche verte ou une croix rouge (port partiel de IconEntry). */
84
+ /** Icon based on the value; `boolean()` shows a green check or a red cross. */
84
85
  export declare class IconEntry extends Entry {
85
86
  private _boolean;
86
87
  static make(name: string): IconEntry;
87
88
  boolean(condition?: boolean): this;
88
89
  toHtml(): Promise<string>;
89
90
  }
90
- /** Paires clé / valeur en table (port de KeyValueEntry) : l'état est un objet (ou sa forme JSON) ; une valeur non scalaire est montrée en JSON. */
91
+ /** Key/value pairs as a table: the state is an object (or its JSON form); a non-scalar value is shown as JSON. */
91
92
  export declare class KeyValueEntry extends Entry {
92
93
  private _keyLabel;
93
94
  private _valueLabel;
@@ -95,15 +96,15 @@ export declare class KeyValueEntry extends Entry {
95
96
  static make(name: string): KeyValueEntry;
96
97
  keyLabel(label: string | null): this;
97
98
  valueLabel(label: string | null): this;
98
- /** Texte affiché sans aucune paire (« No entries » par défaut). */
99
+ /** Text shown when there are no pairs ("No entries" by default). */
99
100
  placeholder(text: string | null): this;
100
- /** @deprecated Utilisez `placeholder()`. */
101
+ /** @deprecated Use `placeholder()`. */
101
102
  emptyMessage(text: string | null): this;
102
103
  toHtml(): Promise<string>;
103
104
  }
104
105
  /**
105
- * Code en lecture seule (port de CodeEntry). Même balisage `<pre class="phiki …">` que Filament, mais SANS coloration syntaxique :
106
- * le surligneur Phiki n'est pas porté, chaque ligne est un seul jeton de texte échappé. Un tableau ou un objet est montré en JSON indenté.
106
+ * Read-only code, rendered as `<pre class="phiki …">` but WITHOUT syntax highlighting:
107
+ * each line is a single escaped text token. An array or object is shown as indented JSON.
107
108
  */
108
109
  export declare class CodeEntry extends Entry {
109
110
  private _grammar;
@@ -114,43 +115,43 @@ export declare class CodeEntry extends Entry {
114
115
  private _copyMessage;
115
116
  private _copyMessageDuration;
116
117
  static make(name: string): CodeEntry;
117
- /** Langage (`php`, `json`, `javascript`…) : nom donné à la classe `language-…` ; `html` par défaut, `json` pour un tableau. */
118
+ /** Language (`php`, `json`, `javascript`…) used in the `language-…` class; `html` by default, `json` for an array. */
118
119
  grammar(grammar: string | null): this;
119
- /** Thèmes (noms de classes seulement : sans Phiki, les couleurs restent celles des thèmes par défaut). */
120
+ /** Theme class names only: without a highlighter, colors stay those of the default themes. */
120
121
  lightTheme(theme: string | null): this;
121
122
  darkTheme(theme: string | null): this;
122
123
  placeholder(text: string | null): this;
123
- /** Un clic copie le code dans le presse-papiers. */
124
+ /** Clicking copies the code to the clipboard. */
124
125
  copyable(condition?: boolean): this;
125
126
  copyMessage(message: string | null): this;
126
127
  copyMessageDuration(milliseconds: number): this;
127
128
  toHtml(): Promise<string>;
128
129
  }
129
130
  /**
130
- * Entrée au rendu libre (port de ViewEntry) : la fonction fournie reçoit l'état, l'enregistrement et le contexte, et rend le contenu
131
- * placé dans l'enveloppe d'entrée. Ce HTML est inséré TEL QUEL : échappez toute valeur venue d'un utilisateur ou de la base avec `e()`.
131
+ * Custom-rendered entry: the given function receives the state, record and context, and returns the content
132
+ * placed in the entry wrapper. This HTML is inserted AS IS: escape any user- or database-supplied value with `e()`.
132
133
  */
133
134
  export declare class ViewEntry extends Entry {
134
135
  private _view;
135
136
  private _wrapped;
136
137
  static make(name: string): ViewEntry;
137
- /** Fonction de rendu du contenu (équivalent de la vue Blade de Filament). */
138
+ /** Content render function. */
138
139
  view(renderer: ((ctx: ComponentContext & {
139
140
  state: unknown;
140
141
  entry: ViewEntry;
141
142
  }) => string | Promise<string>) | null): this;
142
- /** `false` : la fonction rend toute l'entrée, sans l'enveloppe (libellé) habituelle. */
143
+ /** `false`: the function renders the whole entry, without the usual wrapper (label). */
143
144
  wrapped(condition?: boolean): this;
144
145
  toHtml(): Promise<string>;
145
146
  }
146
- /** En-tête de colonne d'une entrée répétée en table (même forme que `TableColumn` de @navette-js/forms). */
147
+ /** Column header of a table repeatable entry (same shape as `TableColumn` from @navette-js/forms). */
147
148
  export type RepeatableColumn = {
148
149
  label: string;
149
150
  getWidth(): string | null;
150
151
  };
151
152
  /**
152
- * Liste d'éléments en lecture seule (port partiel de RepeatableEntry, en mode table) : une ligne par élément du tableau lu dans
153
- * l'enregistrement, une cellule par entrée du schéma.
153
+ * Read-only list of items, as a table: one row per item of the array read from
154
+ * the record, one cell per schema entry.
154
155
  */
155
156
  export declare class RepeatableEntry extends Entry {
156
157
  private _columns;
package/lib/index.js CHANGED
@@ -1,13 +1,13 @@
1
- // @navette-js/infolists — port de filament/infolists : entrées en lecture seule (TextEntry, IconEntry).
1
+ // @navette-js/infolists — read-only entries (TextEntry, IconEntry…).
2
2
  import { Component, Schema } from '@navette-js/schemas';
3
3
  import { Attributes, e, icon, js, configured } from '@navette-js/support';
4
4
  import { t } from '@navette-js/support/i18n';
5
5
  const headline = (s) => s.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/[_-]+/g, ' ').toLowerCase().replace(/^./, (c) => c.toUpperCase());
6
- /** Libellé par défaut : pour `department.name`, c'est « Department » (comme Filament). */
6
+ /** Default label: "Department" for `department.name`. */
7
7
  const defaultLabel = (name) => headline(name.includes('.') ? name.split('.').slice(-2, -1)[0] : name);
8
8
  let colorManager = null;
9
9
  export const useColorManager = (manager) => { colorManager = manager; };
10
- /** Base des entrées : libellé, valeur lue dans l'enregistrement (chemin pointé accepté) ou calculée. */
10
+ /** Base entry: a label and a value read from the record (dotted paths allowed) or computed. */
11
11
  class Entry extends Component {
12
12
  _label = null;
13
13
  _state = null;
@@ -15,32 +15,32 @@ class Entry extends Component {
15
15
  hasStatePath() { return true; }
16
16
  label(label) { this._label = label; return this; }
17
17
  _hiddenLabel = false;
18
- /** Libellé gardé pour les lecteurs d'écran seulement. */
18
+ /** Keeps the label for screen readers only. */
19
19
  hiddenLabel(condition = true) { this._hiddenLabel = condition; return this; }
20
- /** Valeur calculée ; sans cela, l'attribut du même nom de l'enregistrement est affiché. */
20
+ /** Computed value; without it, the record attribute of the same name is shown. */
21
21
  state(resolver) { this._state = resolver; return this; }
22
22
  async getState() {
23
23
  const ctx = this.context();
24
24
  if (this._state)
25
25
  return this._state(ctx);
26
26
  const record = ctx.record;
27
- // `department.name` : attribut à plat `department_name` (colonne calculée par l'adaptateur), sinon chemin dans l'objet.
27
+ // `department.name`: flat `department_name` attribute (column computed by the adapter), otherwise a path into the object.
28
28
  if (record && this._name.includes('.'))
29
29
  return record[this._name.replace(/\./g, '_')] ?? this._name.split('.').reduce((node, key) => node?.[key], record);
30
30
  return record?.[this._name];
31
31
  }
32
- /** Balisage de Entry::wrapEmbeddedHtml(). */
32
+ /** Wraps the content with the entry label. */
33
33
  wrap(content) {
34
34
  if (this._hiddenLabel)
35
35
  return `<div class="fi-in-entry"><div class="fi-in-entry-label fi-sr-only" role="term">${e(this._label ?? defaultLabel(this._name))}</div><div class="fi-in-entry-content-col"><div class="fi-in-entry-content-ctn" role="definition"><div class="fi-in-entry-content">${content}</div></div></div></div>`;
36
36
  return `<div class="fi-in-entry"><div class="fi-in-entry-label-col"><div class="fi-in-entry-label-ctn"><div class="fi-in-entry-label" role="term">${e(this._label ?? defaultLabel(this._name))}</div></div></div><div class="fi-in-entry-content-col"><div class="fi-in-entry-content-ctn" role="definition"><div class="fi-in-entry-content">${content}</div></div></div></div>`;
37
37
  }
38
38
  }
39
- /** Texte en lecture seule (port partiel de TextEntry) : pastille, couleur, icône, montant, date, texte de remplacement. */
40
39
  let htmlSanitizer = null;
41
40
  const TIGHT_ITEM = new RegExp('<li><p>((?:(?!</?p>|</?li>)[^])*)</p></li>', 'g');
42
- /** Filtre appliqué au HTML qu'une entrée affiche tel quel (`markdown()`). Sans filtre, le contenu est échappé. */
41
+ /** Sanitizer applied to HTML an entry renders as is (`markdown()`). Without one, the content is escaped. */
43
42
  export const useHtmlSanitizer = (sanitizer) => { htmlSanitizer = sanitizer; };
43
+ /** Read-only text: badge, color, icon, money, date, placeholder. */
44
44
  export class TextEntry extends Entry {
45
45
  _badge = false;
46
46
  _color = null;
@@ -65,18 +65,18 @@ export class TextEntry extends Entry {
65
65
  suffix(text) { this._suffix = text; return this; }
66
66
  _prose = false;
67
67
  _markdown = false;
68
- /** Mise en forme typographique du contenu (`fi-prose`). */
68
+ /** Typographic styling of the content (`fi-prose`). */
69
69
  prose(condition = true) { this._prose = condition; return this; }
70
70
  /**
71
- * Le contenu est du texte enrichi : il est rendu en HTML après nettoyage par le filtre posé avec `useHtmlSanitizer()`.
72
- * Port partiel de `markdown()` : le HTML déjà présent est conservé (nettoyé), le texte brut devient des paragraphes ; la syntaxe Markdown n'est pas convertie.
71
+ * Rich text content, rendered as HTML after the sanitizer set with `useHtmlSanitizer()`.
72
+ * Existing HTML is kept (sanitized) and plain text becomes paragraphs; Markdown syntax itself is not converted.
73
73
  */
74
74
  markdown(condition = true) { this._markdown = condition; return this; }
75
75
  _copyable = false;
76
76
  _numeric = false;
77
- /** Un clic copie la valeur dans le presse-papiers. */
77
+ /** Clicking copies the value to the clipboard. */
78
78
  copyable(condition = true) { this._copyable = condition; return this; }
79
- /** Nombre formaté (séparateurs de milliers), aligné comme un nombre. */
79
+ /** Formatted number (thousands separators), aligned as a number. */
80
80
  numeric(condition = true) { this._numeric = condition; return this; }
81
81
  async toHtml() {
82
82
  const record = this.context().record;
@@ -84,7 +84,7 @@ export class TextEntry extends Entry {
84
84
  const empty = state === null || state === undefined || state === '';
85
85
  if (empty && this._placeholder !== null)
86
86
  return this.wrap(`<div class="fi-in-text"><p class="fi-in-placeholder">${e(this._placeholder)}</p></div>`);
87
- // Valeur vide : conteneur nu (avec `fi-numeric` pour un montant), comme Filament.
87
+ // Empty value: bare container (with `fi-numeric` for money).
88
88
  if (empty)
89
89
  return this._state || (!this._money && !this._date && !this._badge) ? this.wrap('<div class="fi-in-text-item fi-size-sm fi-in-text fi-wrapped"></div>') : this.wrap(`<div class="${['fi-in-text', this._money || this._numeric ? 'fi-numeric' : null].filter(Boolean).join(' ')}"></div>`);
90
90
  let shown = String(state);
@@ -94,10 +94,10 @@ export class TextEntry extends Entry {
94
94
  shown = new Intl.NumberFormat('en-US', { style: 'currency', currency: this._money }).format(Number(state));
95
95
  if (this._date)
96
96
  shown = new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' }).format(new Date(`${shown.replace(' ', 'T').slice(0, 19)}Z`));
97
- // `dateTime()` : « M j, Y H:i:s », le format par défaut de Filament.
97
+ // `dateTime()`: "Jan 5, 2024 13:45:00" (`M j, Y H:i:s`).
98
98
  if (this._dateTime)
99
99
  shown = `${new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' }).format(new Date(`${shown.replace(' ', 'T').slice(0, 19)}Z`))} ${shown.slice(11, 19)}`;
100
- // `time('H:i')` ou `time()` (« H:i:s ») : l'heure d'une valeur `H:i:s` ou `Y-m-d H:i:s`.
100
+ // `time('H:i')` or `time()` (`H:i:s`): the time part of an `H:i:s` or `Y-m-d H:i:s` value.
101
101
  if (this._time)
102
102
  shown = (shown.length > 8 ? shown.slice(11, 19) : shown).slice(0, this._time === 'H:i' ? 5 : 8);
103
103
  if (this._format)
@@ -107,13 +107,13 @@ export class TextEntry extends Entry {
107
107
  if (this._markdown) {
108
108
  const source = String(state);
109
109
  const html = /<[a-z][^>]*>/i.test(source)
110
- // Listes « serrées », comme le rendu Markdown de Filament : le paragraphe unique d'une puce est retiré.
110
+ // Tight lists: a list item's single paragraph is unwrapped.
111
111
  ? (htmlSanitizer ? htmlSanitizer(source).replace(TIGHT_ITEM, '<li>$1</li>') : e(source))
112
112
  : source.split(/\n{2,}/).map((paragraph) => `<p>${e(paragraph.trim()).replace(/\n/g, '<br />')}</p>`).join('');
113
113
  return this.wrap(`<div class="${['fi-in-text-item', this._prose ? 'fi-prose' : null, 'fi-size-sm', 'fi-in-text', 'fi-wrapped'].filter(Boolean).join(' ')}">${html}</div>`);
114
114
  }
115
115
  if (this._badge && Array.isArray(state)) {
116
- // Plusieurs valeurs : une liste de pastilles (étiquettes, par exemple).
116
+ // Several values: a list of badges (tags, for instance).
117
117
  const color = typeof this._color === 'function' ? this._color(record) : (this._color ?? 'primary');
118
118
  const classes = ['fi-badge', 'fi-size-sm', ...(color !== 'gray' ? (colorManager?.getComponentClasses('badge', color) ?? []) : [])].join(' ');
119
119
  return this.wrap(`<ul class="fi-in-text fi-in-text-has-badges fi-wrapped">${state.map((item) => `<li class="fi-in-text-item"><span class="${classes}">${e(String(item))}</span></li>`).join('')}</ul>`);
@@ -139,7 +139,7 @@ export class TextEntry extends Entry {
139
139
  return this.wrap(`<div class="${['fi-in-text-item', 'fi-size-sm', 'fi-in-text', this._money || this._numeric ? 'fi-numeric' : null, 'fi-wrapped'].filter(Boolean).join(' ')}">${content}</div>`);
140
140
  }
141
141
  }
142
- /** Pastille de couleur (port de ColorEntry) : seules les couleurs hexadécimales ou fonctionnelles simples sont injectées dans le style. */
142
+ /** Color swatch: only hex or simple functional colors are injected into the style attribute. */
143
143
  export class ColorEntry extends Entry {
144
144
  _placeholder = null;
145
145
  static make(name) { return configured(new ColorEntry(name)); }
@@ -152,13 +152,13 @@ export class ColorEntry extends Entry {
152
152
  return this.wrap(`<div class="fi-in-color fi-wrapped"><div role="img" aria-label="${e(color)}" class="fi-in-color-item" style="background-color: ${color};"></div></div>`);
153
153
  }
154
154
  }
155
- /** Image en lecture seule (port de ImageEntry) : seules une adresse du site (`/…`) ou une adresse http(s) sont affichées. */
155
+ /** Read-only image: only same-site (`/…`) or http(s) URLs are rendered. */
156
156
  export class ImageEntry extends Entry {
157
157
  _height = '8rem';
158
158
  _alt = null;
159
159
  _circular = false;
160
160
  static make(name) { return configured(new ImageEntry(name)); }
161
- /** Hauteur CSS de l'image (`8rem` par défaut, comme Filament). */
161
+ /** CSS height of the image (`8rem` by default). */
162
162
  imageHeight(height) { if (/^[0-9.]+(rem|px|em)$/.test(height))
163
163
  this._height = height; return this; }
164
164
  alt(text) { this._alt = text; return this; }
@@ -169,7 +169,7 @@ export class ImageEntry extends Entry {
169
169
  return this.wrap(`<div class="fi-in-image fi-wrapped">${sources.map((src) => `<img alt="${e(this._alt ?? '')}" src="${e(src)}" style="height: ${this._height};"${this._circular ? ' class="fi-circular"' : ''} />`).join('')}</div>`);
170
170
  }
171
171
  }
172
- /** Icône selon la valeur ; `boolean()` affiche une coche verte ou une croix rouge (port partiel de IconEntry). */
172
+ /** Icon based on the value; `boolean()` shows a green check or a red cross. */
173
173
  export class IconEntry extends Entry {
174
174
  _boolean = false;
175
175
  static make(name) { return configured(new IconEntry(name)); }
@@ -183,7 +183,7 @@ export class IconEntry extends Entry {
183
183
  return this.wrap(`<div class="fi-in-icon fi-wrapped">${icon(on ? 'o-check-circle' : 'o-x-circle', { size: 'lg', attributes: new Attributes().class(classes) })}<span class="fi-sr-only">${e(t(on ? 'filament-tables::table.columns.icon.boolean.true' : 'filament-tables::table.columns.icon.boolean.false'))}</span></div>`);
184
184
  }
185
185
  }
186
- /** Paires clé / valeur en table (port de KeyValueEntry) : l'état est un objet (ou sa forme JSON) ; une valeur non scalaire est montrée en JSON. */
186
+ /** Key/value pairs as a table: the state is an object (or its JSON form); a non-scalar value is shown as JSON. */
187
187
  export class KeyValueEntry extends Entry {
188
188
  _keyLabel = null;
189
189
  _valueLabel = null;
@@ -191,9 +191,9 @@ export class KeyValueEntry extends Entry {
191
191
  static make(name) { return configured(new KeyValueEntry(name)); }
192
192
  keyLabel(label) { this._keyLabel = label; return this; }
193
193
  valueLabel(label) { this._valueLabel = label; return this; }
194
- /** Texte affiché sans aucune paire (« No entries » par défaut). */
194
+ /** Text shown when there are no pairs ("No entries" by default). */
195
195
  placeholder(text) { this._placeholder = text; return this; }
196
- /** @deprecated Utilisez `placeholder()`. */
196
+ /** @deprecated Use `placeholder()`. */
197
197
  emptyMessage(text) { return this.placeholder(text); }
198
198
  async toHtml() {
199
199
  let state = await this.getState();
@@ -213,13 +213,13 @@ export class KeyValueEntry extends Entry {
213
213
  return this.wrap(`<table class="fi-in-key-value"><thead><tr><th scope="col">${e(this._keyLabel ?? tr('columns.key.label'))}</th><th scope="col">${e(this._valueLabel ?? tr('columns.value.label'))}</th></tr></thead><tbody>${rows}${empty}</tbody></table>`);
214
214
  }
215
215
  }
216
- /** `json_encode($value, JSON_PRETTY_PRINT)` de PHP : quatre espaces, `/` et caractères non ASCII échappés. */
216
+ /** Pretty-printed JSON: four-space indent, with `/` and non-ASCII characters escaped. */
217
217
  const phpPrettyJson = (value) => (JSON.stringify(value, null, 4) ?? 'null').replace(/\//g, '\\/').replace(/[\u0080-￿]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`);
218
- /** Couleurs de fond et de texte des thèmes par défaut de Phiki (github-light / github-dark-high-contrast). */
218
+ /** Background and text colors of the default themes (github-light / github-dark-high-contrast). */
219
219
  const PHIKI_DEFAULT_STYLE = 'background-color: #fff;color: #24292e;--phiki-dark-background-color: #0a0c10;--phiki-dark-color: #f0f3f6;';
220
220
  /**
221
- * Code en lecture seule (port de CodeEntry). Même balisage `<pre class="phiki …">` que Filament, mais SANS coloration syntaxique :
222
- * le surligneur Phiki n'est pas porté, chaque ligne est un seul jeton de texte échappé. Un tableau ou un objet est montré en JSON indenté.
221
+ * Read-only code, rendered as `<pre class="phiki …">` but WITHOUT syntax highlighting:
222
+ * each line is a single escaped text token. An array or object is shown as indented JSON.
223
223
  */
224
224
  export class CodeEntry extends Entry {
225
225
  _grammar = null;
@@ -230,13 +230,13 @@ export class CodeEntry extends Entry {
230
230
  _copyMessage = null;
231
231
  _copyMessageDuration = 2000;
232
232
  static make(name) { return configured(new CodeEntry(name)); }
233
- /** Langage (`php`, `json`, `javascript`…) : nom donné à la classe `language-…` ; `html` par défaut, `json` pour un tableau. */
233
+ /** Language (`php`, `json`, `javascript`…) used in the `language-…` class; `html` by default, `json` for an array. */
234
234
  grammar(grammar) { this._grammar = grammar; return this; }
235
- /** Thèmes (noms de classes seulement : sans Phiki, les couleurs restent celles des thèmes par défaut). */
235
+ /** Theme class names only: without a highlighter, colors stay those of the default themes. */
236
236
  lightTheme(theme) { this._lightTheme = theme; return this; }
237
237
  darkTheme(theme) { this._darkTheme = theme; return this; }
238
238
  placeholder(text) { this._placeholder = text; return this; }
239
- /** Un clic copie le code dans le presse-papiers. */
239
+ /** Clicking copies the code to the clipboard. */
240
240
  copyable(condition = true) { this._copyable = condition; return this; }
241
241
  copyMessage(message) { this._copyMessage = message; return this; }
242
242
  copyMessageDuration(milliseconds) { this._copyMessageDuration = milliseconds; return this; }
@@ -254,7 +254,7 @@ export class CodeEntry extends Entry {
254
254
  const language = (grammar ?? 'html').replace(/[^a-zA-Z0-9_+#-]/g, '');
255
255
  const light = (this._lightTheme ?? 'github-light').replace(/[^a-zA-Z0-9_-]/g, ''), dark = (this._darkTheme ?? 'github-dark-high-contrast').replace(/[^a-zA-Z0-9_-]/g, '');
256
256
  const style = this._lightTheme === null && this._darkTheme === null ? ` style="${PHIKI_DEFAULT_STYLE}"` : '';
257
- // Phiki : une ligne par `<span class="line">`, le saut de ligne étant le dernier jeton de chaque ligne.
257
+ // One `<span class="line">` per line, the newline being the last token of each line.
258
258
  const lines = code.split('\n');
259
259
  const linesHtml = lines.map((line, index) => `<span class="line">${line === '' ? '' : `<span class="token">${e(line)}</span>`}${index < lines.length - 1 ? '<span class="token">\n</span>' : ''}</span>`).filter((html, index) => !(index === lines.length - 1 && lines[index] === '' && lines.length > 1)).join('');
260
260
  const pre = `<pre class="phiki language-${language} ${light} phiki-themes ${dark}" data-language="${language}"${style}><code>${linesHtml}</code></pre>`;
@@ -272,27 +272,27 @@ export class CodeEntry extends Entry {
272
272
  }
273
273
  }
274
274
  /**
275
- * Entrée au rendu libre (port de ViewEntry) : la fonction fournie reçoit l'état, l'enregistrement et le contexte, et rend le contenu
276
- * placé dans l'enveloppe d'entrée. Ce HTML est inséré TEL QUEL : échappez toute valeur venue d'un utilisateur ou de la base avec `e()`.
275
+ * Custom-rendered entry: the given function receives the state, record and context, and returns the content
276
+ * placed in the entry wrapper. This HTML is inserted AS IS: escape any user- or database-supplied value with `e()`.
277
277
  */
278
278
  export class ViewEntry extends Entry {
279
279
  _view = null;
280
280
  _wrapped = true;
281
281
  static make(name) { return configured(new ViewEntry(name)); }
282
- /** Fonction de rendu du contenu (équivalent de la vue Blade de Filament). */
282
+ /** Content render function. */
283
283
  view(renderer) { this._view = renderer; return this; }
284
- /** `false` : la fonction rend toute l'entrée, sans l'enveloppe (libellé) habituelle. */
284
+ /** `false`: the function renders the whole entry, without the usual wrapper (label). */
285
285
  wrapped(condition = true) { this._wrapped = condition; return this; }
286
286
  async toHtml() {
287
287
  if (!this._view)
288
- throw new Error(`ViewEntry [${this._name}] : view() est requis.`);
288
+ throw new Error(`ViewEntry [${this._name}]: view() is required.`);
289
289
  const html = await this._view({ ...this.context(), state: await this.getState(), entry: this });
290
290
  return this._wrapped ? this.wrap(html) : html;
291
291
  }
292
292
  }
293
293
  /**
294
- * Liste d'éléments en lecture seule (port partiel de RepeatableEntry, en mode table) : une ligne par élément du tableau lu dans
295
- * l'enregistrement, une cellule par entrée du schéma.
294
+ * Read-only list of items, as a table: one row per item of the array read from
295
+ * the record, one cell per schema entry.
296
296
  */
297
297
  export class RepeatableEntry extends Entry {
298
298
  _columns = [];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@navette-js/infolists",
3
- "version": "1.0.0-beta.1",
4
- "description": "Infolist entries for Navette, the Node.js port of Filament.",
3
+ "version": "1.0.0-beta.3",
4
+ "description": "Read-only infolist entries for Navette admin panels.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "exports": {
@@ -30,7 +30,6 @@
30
30
  },
31
31
  "keywords": [
32
32
  "navette",
33
- "filament",
34
33
  "admin",
35
34
  "panel",
36
35
  "livewire",
@@ -38,8 +37,8 @@
38
37
  "node"
39
38
  ],
40
39
  "dependencies": {
41
- "@navette-js/support": "1.0.0-beta.1",
42
- "@navette-js/schemas": "1.0.0-beta.1"
40
+ "@navette-js/support": "1.0.0-beta.3",
41
+ "@navette-js/schemas": "1.0.0-beta.3"
43
42
  },
44
43
  "publishConfig": {
45
44
  "access": "public",