@navette-js/actions 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/actions
2
2
 
3
- Actions and modals for Navette, the Node.js port of Filament.
3
+ Actions and modals 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,6 +1,6 @@
1
1
  import { type ColorManager, type Lazy } from '@navette-js/support';
2
2
  import type { ComponentContext, Renderable } from '@navette-js/schemas';
3
- /** Emplacements de render hook d'une modale d'action — mêmes noms que `Filament\Actions\View\ActionsRenderHook`. Donnée reçue : `data.action`. */
3
+ /** Render hook slots of an action modal. Hooks receive the action as `data.action`. */
4
4
  export declare const ActionsRenderHook: {
5
5
  readonly MODAL_CUSTOM_CONTENT_AFTER: "actions::modal.custom-content.after";
6
6
  readonly MODAL_CUSTOM_CONTENT_BEFORE: "actions::modal.custom-content.before";
@@ -10,7 +10,7 @@ export declare const ActionsRenderHook: {
10
10
  readonly MODAL_SCHEMA_BEFORE: "actions::modal.schema.before";
11
11
  };
12
12
  export declare const useColorManager: (manager: ColorManager) => void;
13
- /** Contexte d'exécution d'une action montée. */
13
+ /** Execution context of a mounted action. */
14
14
  export type ActionCallContext = {
15
15
  livewire: any;
16
16
  record: any;
@@ -18,7 +18,7 @@ export type ActionCallContext = {
18
18
  data: Record<string, unknown>;
19
19
  };
20
20
  export declare class Action implements Renderable {
21
- /** Réglage par défaut de toutes les instances créées ensuite par `make()` : `TextInput.configureUsing((input) => input.maxLength(255))`. */
21
+ /** Default setup for every instance created afterwards by `make()`, e.g. `Action.configureUsing((action) => action.size('sm'))`. */
22
22
  static configureUsing<T>(this: abstract new (...args: any[]) => T, configure: (instance: T) => void): void;
23
23
  private name;
24
24
  private _label;
@@ -52,23 +52,23 @@ export declare class Action implements Renderable {
52
52
  constructor(name: string);
53
53
  static make(name: string): Action;
54
54
  label(label: Lazy<string | null, ComponentContext>): this;
55
- /** Une fonction rend la couleur dépendante de l'enregistrement (actions de ligne). */
55
+ /** Pass a function to make the color depend on the record (row actions). */
56
56
  color(color: string | ((ctx: ComponentContext) => string | Promise<string>)): this;
57
57
  size(size: string): this;
58
- /** Bouton `type="submit"` qui déclenche la méthode de soumission du formulaire (`->submit('authenticate')`). */
58
+ /** Renders a `type="submit"` button that triggers the form's submit method, e.g. `.submit('authenticate')`. */
59
59
  submit(form: string | null): this;
60
60
  hidden(condition?: Lazy<boolean, ComponentContext>): this;
61
- /** Action de navigation : rendue en `<a href wire:navigate>`. */
61
+ /** Navigation action, rendered as `<a href wire:navigate>`. */
62
62
  url(url: string | null | ((ctx: ComponentContext) => string | null)): this;
63
- /** Une chaîne : méthode du composant hôte appelée au clic (`wire:click`). Une fonction : traitement de l'action, monté par `mountAction()` (avec modale si elle en a une). */
63
+ /** A string: host component method called on click (`wire:click`). A function: the action handler, mounted through `mountAction()` (with its modal, if any). */
64
64
  action(action: string | ((ctx: ActionCallContext) => unknown | Promise<unknown>) | null): this;
65
65
  icon(name: string | null | ((ctx: ComponentContext) => string | null | Promise<string | null>)): this;
66
- /** Icône quand l'action est un lien de ligne de table. */
66
+ /** Icon used when the action is a table row link. */
67
67
  tableIcon(name: string | null): this;
68
68
  private _iconButton;
69
- /** Action de ligne réduite à son icône ; le libellé devient l'infobulle et le nom accessible (port de `iconButton()`). */
69
+ /** Icon-only row action; the label becomes the tooltip and accessible name. */
70
70
  iconButton(condition?: boolean): this;
71
- /** Icône quand l'action est un élément de menu (ActionGroup). */
71
+ /** Icon used when the action is a menu item (ActionGroup). */
72
72
  groupedIcon(name: string | null): this;
73
73
  requiresConfirmation(condition?: boolean): this;
74
74
  modalHeading(heading: Lazy<string | null, ActionCallContext>): this;
@@ -77,56 +77,56 @@ export declare class Action implements Renderable {
77
77
  modalIcon(name: string | null): this;
78
78
  private _badge;
79
79
  private _badgeColor;
80
- /** Pastille affichée sur le bouton (compteur). */
80
+ /** Badge (counter) shown on the button. */
81
81
  badge(badge: string | number | null | (() => string | number | null | Promise<string | number | null>)): this;
82
82
  badgeColor(color: string): this;
83
83
  private _slideOver;
84
84
  private _hasSubmit;
85
- /** Modale en panneau latéral. */
85
+ /** Opens the modal as a slide-over panel. */
86
86
  slideOver(condition?: boolean): this;
87
- /** `false` : modale de consultation, sans bouton de validation. */
87
+ /** `false`: read-only modal, without a submit button. */
88
88
  modalSubmitAction(condition: boolean): this;
89
89
  private _cancelLabel;
90
- /** Libellé du bouton de fermeture de la modale (« Cancel » par défaut). */
90
+ /** Label of the modal's close button ("Cancel" by default). */
91
91
  modalCancelActionLabel(label: string | null): this;
92
92
  private _tooltip;
93
93
  private _newTab;
94
- /** Infobulle affichée au survol. */
94
+ /** Tooltip shown on hover. */
95
95
  tooltip(text: Lazy<string | null, ComponentContext>): this;
96
- /** Ouvre l'adresse de l'action dans un nouvel onglet. */
96
+ /** Opens the action's URL in a new tab. */
97
97
  openUrlInNewTab(condition?: boolean): this;
98
98
  private tooltipAttribute;
99
99
  private _modalIconColor;
100
- /** Couleur de l'icône de la modale ; par défaut celle de l'action. */
100
+ /** Color of the modal icon; defaults to the action's color. */
101
101
  modalIconColor(color: string | null): this;
102
102
  getColor(): string;
103
103
  getLabel(ctx: ComponentContext): Promise<string>;
104
104
  getIcon(ctx: ComponentContext): Promise<string | null>;
105
105
  private _sort;
106
- /** Ordre dans un menu (menu utilisateur, menu du locataire) : négatif, avant le choix du thème ou la liste des locataires. */
106
+ /** Position in a menu (user menu, tenant menu); negative values go before the theme switcher or the tenant list. */
107
107
  sort(sort: number): this;
108
108
  getSort(): number;
109
109
  /**
110
- * Élément d'un menu du panel (menu utilisateur, menu du locataire) — port de l'action « grouped » à adresse.
111
- * Seules les actions à adresse y ont leur place : le menu n'est pas un composant qui monte des actions.
110
+ * Item of a panel menu (user menu, tenant menu). Only URL actions are rendered: these menus are not components
111
+ * that can mount actions.
112
112
  */
113
113
  toMenuItemHtml(ctx: ComponentContext, options?: {
114
114
  navigate?: boolean;
115
115
  }): Promise<string>;
116
- /** Une adresse calculée ne se connaît qu'avec l'enregistrement : l'hôte la revérifie au montage (`resolveUrl`). */
116
+ /** A computed URL depends on the record, so the host checks it again on mount (`resolveUrl`). */
117
117
  isMountable(): boolean;
118
- /** Adresse de l'action dans ce contexte ; une action qui en a une est un lien, jamais une action montée. */
118
+ /** URL of the action in this context; an action with a URL is a link, never a mounted action. */
119
119
  resolveUrl(ctx: ComponentContext): string | null;
120
120
  hasModal(): boolean;
121
- /** Formulaire affiché dans la modale. Une fonction : les composants sont reconstruits à chaque requête. */
121
+ /** Form shown in the modal. With a function, the components are rebuilt on every request. */
122
122
  schema(components: any[] | ((schema: any) => any), columns?: Record<string, number> | number | null): this;
123
- /** Applique le formulaire de l'action au schéma fourni par l'hôte : la fonction rend soit des composants, soit le schéma configuré. */
123
+ /** Applies the action's form to the host's schema; the function returns either components or the configured schema. */
124
124
  applySchema(schema: any): any | null;
125
- /** Boutons ajoutés au pied de la modale, entre la validation et l'annulation. */
125
+ /** Extra buttons in the modal footer, after the submit button. */
126
126
  extraModalFooterActions(actions: Action[]): this;
127
- /** État initial du formulaire (ex. à partir de l'enregistrement). */
127
+ /** Initial form state (e.g. from the record). */
128
128
  fillForm(resolver: (ctx: ActionCallContext) => Record<string, unknown> | Promise<Record<string, unknown>>): this;
129
- /** Largeur de la modale (`xs` … `7xl`). Par défaut `md` pour une confirmation, `4xl` pour un formulaire. */
129
+ /** Modal width (`xs` … `7xl`). Defaults to `md` for a confirmation and `4xl` for a form. */
130
130
  modalWidth(width: string | null): this;
131
131
  hasSchema(): boolean;
132
132
  getFormFill(ctx: ActionCallContext): Promise<Record<string, unknown> | null>;
@@ -134,50 +134,50 @@ export declare class Action implements Renderable {
134
134
  private _mutateData;
135
135
  private _before;
136
136
  private _after;
137
- /** Transforme les données validées du formulaire avant le traitement (ajouter l'auteur, normaliser une valeur…). */
137
+ /** Transforms the validated form data before the handler runs (add the author, normalize a value…). */
138
138
  mutateDataUsing(fn: (data: Record<string, unknown>, ctx: ActionCallContext) => Record<string, unknown> | Promise<Record<string, unknown>>): this;
139
- /** Exécuté juste avant le traitement ; lever une erreur l'annule. */
139
+ /** Runs right before the handler; throwing cancels the action. */
140
140
  before(fn: (ctx: ActionCallContext) => unknown | Promise<unknown>): this;
141
- /** Exécuté après un traitement réussi (journal, notification supplémentaire…). */
141
+ /** Runs after the handler succeeds (logging, extra notification…). */
142
142
  after(fn: (ctx: ActionCallContext) => unknown | Promise<unknown>): this;
143
143
  call(ctx: ActionCallContext): Promise<unknown>;
144
- /** Élément de menu d'une action groupée de table — port de l'action « grouped » (`fi-ac-grouped-action`). Le clic passe par `filamentTable.mountAction()`, qui envoie la sélection. */
144
+ /** Menu item of a table bulk action. The click goes through the table's Alpine `mountAction()`, which sends the selection. */
145
145
  toGroupedHtml(ctx: ComponentContext, context: Record<string, unknown>): Promise<string>;
146
146
  /**
147
- * Élément de menu d'une action de ligne (dans un ActionGroup) — port de l'action « grouped ».
148
- * Filament a deux balisages : un chemin court, et la vue Blade dès qu'un raccourci clavier est posé (ordre des classes différent).
147
+ * Menu item of a row action inside an ActionGroup. Disabled-capable and key-bound items use a different class order
148
+ * than plain items; front-end CSS and tests rely on the exact markup.
149
149
  */
150
150
  toRecordGroupedHtml(ctx: ComponentContext, context: Record<string, unknown>, wireKey: string): Promise<string>;
151
- /** Lien d'une action de ligne de table (`fi-ac-link-action`) — port de l'action « link » en petite taille. */
151
+ /** Small link (or icon button) for a table row action. */
152
152
  toLinkHtml(ctx: ComponentContext, context: Record<string, unknown>, wireKey: string): Promise<string>;
153
- /** Fenêtre modale d'une action montée — port de filament-actions::action-modal et filament::components.modal (confirmation seulement). */
153
+ /** Modal of a mounted action; `content` is the rendered form, if any. */
154
154
  toModalHtml(ctx: ActionCallContext, index: number, content?: string | null): Promise<string>;
155
155
  alpineClickHandler(handler: string | null): this;
156
156
  private _livewireTarget;
157
157
  private _iconAfter;
158
- /** Cible de l'indicateur de chargement (`wire:target`), quand le clic est géré par du JavaScript. */
158
+ /** Loading indicator target (`wire:target`) when the click is handled in JavaScript. */
159
159
  livewireTarget(target: string | null): this;
160
- /** Icône (et indicateur de chargement) après le libellé : `'after'`. */
160
+ /** `'after'` puts the icon (and loading indicator) after the label. */
161
161
  iconPosition(position: 'before' | 'after'): this;
162
162
  keyBindings(bindings: string[]): this;
163
163
  getName(): string;
164
164
  private _authorize;
165
- /** Condition d'autorisation, distincte de `visible` / `hidden` : non remplie, l'action est masquée et refusée par le serveur. */
165
+ /** Authorization check, separate from `visible` / `hidden`: when it fails, the action is hidden and the server refuses it. */
166
166
  authorize(condition: Lazy<boolean, ComponentContext>): this;
167
167
  isVisible(ctx: ComponentContext): Promise<boolean>;
168
168
  visible(condition?: Lazy<boolean, ComponentContext>): this;
169
- /** Action affichée mais inutilisable ; le serveur refuse aussi de la monter. */
169
+ /** Shown but unusable; the server also refuses to mount it. */
170
170
  disabled(condition?: Lazy<boolean, ComponentContext>): this;
171
171
  isDisabled(ctx: ComponentContext): Promise<boolean>;
172
- /** Bouton plein — port de la vue `filament::components.button` pour une action de soumission. */
172
+ /** Renders the action as a button (or as a link when it has a URL). */
173
173
  toHtml(ctx: ComponentContext, explicitMountContext?: Record<string, unknown> | null): Promise<string>;
174
174
  }
175
- /** Groupe d'actions replié dans un menu déroulant (port de ActionGroup, bouton-icône « ⋮ »). */
175
+ /** Group of actions collapsed into a dropdown behind a "⋮" icon button. */
176
176
  export declare class ActionGroup {
177
177
  private actions;
178
178
  constructor(actions: Action[]);
179
179
  static make(actions: Action[]): ActionGroup;
180
180
  getActions(): Action[];
181
- /** `keyOf` donne la clé `wire:key` de chaque action (elle dépend de l'enregistrement). */
181
+ /** `keyOf` returns each action's `wire:key` (it depends on the record). */
182
182
  toHtml(ctx: ComponentContext, context: Record<string, unknown>, keyOf: (action: Action) => string): Promise<string>;
183
183
  }
package/lib/index.js CHANGED
@@ -1,9 +1,9 @@
1
- // @navette-js/actions — port de filament/actions : Action (bouton plein, soumission de formulaire) pour commencer.
1
+ // @navette-js/actions — actions: buttons, links, menu items and modal actions.
2
2
  import { Attributes, e, evaluate, icon, js, loadingIndicator, renderHook, views, configured, configureUsing } from '@navette-js/support';
3
3
  import { t } from '@navette-js/support/i18n';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  views.namespace('filament-actions', fileURLToPath(new URL('../views', import.meta.url)));
6
- /** Emplacements de render hook d'une modale d'action — mêmes noms que `Filament\Actions\View\ActionsRenderHook`. Donnée reçue : `data.action`. */
6
+ /** Render hook slots of an action modal. Hooks receive the action as `data.action`. */
7
7
  export const ActionsRenderHook = {
8
8
  MODAL_CUSTOM_CONTENT_AFTER: 'actions::modal.custom-content.after',
9
9
  MODAL_CUSTOM_CONTENT_BEFORE: 'actions::modal.custom-content.before',
@@ -15,7 +15,7 @@ export const ActionsRenderHook = {
15
15
  let colorManager = null;
16
16
  export const useColorManager = (manager) => { colorManager = manager; };
17
17
  export class Action {
18
- /** Réglage par défaut de toutes les instances créées ensuite par `make()` : `TextInput.configureUsing((input) => input.maxLength(255))`. */
18
+ /** Default setup for every instance created afterwards by `make()`, e.g. `Action.configureUsing((action) => action.size('sm'))`. */
19
19
  static configureUsing(configure) { configureUsing(this, configure); }
20
20
  name;
21
21
  _label = null;
@@ -49,7 +49,7 @@ export class Action {
49
49
  constructor(name) { this.name = name; }
50
50
  static make(name) { return configured(new Action(name)); }
51
51
  label(label) { this._label = label; return this; }
52
- /** Une fonction rend la couleur dépendante de l'enregistrement (actions de ligne). */
52
+ /** Pass a function to make the color depend on the record (row actions). */
53
53
  color(color) { if (typeof color === 'function')
54
54
  this._lazyColor = color;
55
55
  else {
@@ -57,17 +57,17 @@ export class Action {
57
57
  this._lazyColor = null;
58
58
  } return this; }
59
59
  size(size) { this._size = size; return this; }
60
- /** Bouton `type="submit"` qui déclenche la méthode de soumission du formulaire (`->submit('authenticate')`). */
60
+ /** Renders a `type="submit"` button that triggers the form's submit method, e.g. `.submit('authenticate')`. */
61
61
  submit(form) { this._submit = form; return this; }
62
62
  hidden(condition = true) { this._hidden = condition; return this; }
63
- /** Action de navigation : rendue en `<a href wire:navigate>`. */
63
+ /** Navigation action, rendered as `<a href wire:navigate>`. */
64
64
  url(url) { if (typeof url === 'function')
65
65
  this._lazyUrl = url;
66
66
  else {
67
67
  this._url = url;
68
68
  this._lazyUrl = null;
69
69
  } return this; }
70
- /** Une chaîne : méthode du composant hôte appelée au clic (`wire:click`). Une fonction : traitement de l'action, monté par `mountAction()` (avec modale si elle en a une). */
70
+ /** A string: host component method called on click (`wire:click`). A function: the action handler, mounted through `mountAction()` (with its modal, if any). */
71
71
  action(action) {
72
72
  if (typeof action === 'function') {
73
73
  this._handler = action;
@@ -85,12 +85,12 @@ export class Action {
85
85
  this._icon = name;
86
86
  this._lazyIcon = null;
87
87
  } return this; }
88
- /** Icône quand l'action est un lien de ligne de table. */
88
+ /** Icon used when the action is a table row link. */
89
89
  tableIcon(name) { this._tableIcon = name; return this; }
90
90
  _iconButton = false;
91
- /** Action de ligne réduite à son icône ; le libellé devient l'infobulle et le nom accessible (port de `iconButton()`). */
91
+ /** Icon-only row action; the label becomes the tooltip and accessible name. */
92
92
  iconButton(condition = true) { this._iconButton = condition; return this; }
93
- /** Icône quand l'action est un élément de menu (ActionGroup). */
93
+ /** Icon used when the action is a menu item (ActionGroup). */
94
94
  groupedIcon(name) { this._groupedIcon = name; return this; }
95
95
  requiresConfirmation(condition = true) { this._requiresConfirmation = condition; return this; }
96
96
  modalHeading(heading) { this._modalHeading = heading; return this; }
@@ -99,23 +99,23 @@ export class Action {
99
99
  modalIcon(name) { this._modalIcon = name; return this; }
100
100
  _badge = null;
101
101
  _badgeColor = 'primary';
102
- /** Pastille affichée sur le bouton (compteur). */
102
+ /** Badge (counter) shown on the button. */
103
103
  badge(badge) { this._badge = badge; return this; }
104
104
  badgeColor(color) { this._badgeColor = color; return this; }
105
105
  _slideOver = false;
106
106
  _hasSubmit = true;
107
- /** Modale en panneau latéral. */
107
+ /** Opens the modal as a slide-over panel. */
108
108
  slideOver(condition = true) { this._slideOver = condition; return this; }
109
- /** `false` : modale de consultation, sans bouton de validation. */
109
+ /** `false`: read-only modal, without a submit button. */
110
110
  modalSubmitAction(condition) { this._hasSubmit = condition; return this; }
111
111
  _cancelLabel = null;
112
- /** Libellé du bouton de fermeture de la modale (« Cancel » par défaut). */
112
+ /** Label of the modal's close button ("Cancel" by default). */
113
113
  modalCancelActionLabel(label) { this._cancelLabel = label; return this; }
114
114
  _tooltip = null;
115
115
  _newTab = false;
116
- /** Infobulle affichée au survol. */
116
+ /** Tooltip shown on hover. */
117
117
  tooltip(text) { this._tooltip = text; return this; }
118
- /** Ouvre l'adresse de l'action dans un nouvel onglet. */
118
+ /** Opens the action's URL in a new tab. */
119
119
  openUrlInNewTab(condition = true) { this._newTab = condition; return this; }
120
120
  async tooltipAttribute(ctx) {
121
121
  const text = await evaluate(this._tooltip, ctx);
@@ -126,22 +126,22 @@ export class Action {
126
126
  }`)}" ` : '';
127
127
  }
128
128
  _modalIconColor = null;
129
- /** Couleur de l'icône de la modale ; par défaut celle de l'action. */
129
+ /** Color of the modal icon; defaults to the action's color. */
130
130
  modalIconColor(color) { this._modalIconColor = color; return this; }
131
131
  getColor() { return this._color; }
132
132
  async getLabel(ctx) { return (await evaluate(this._label, ctx)) ?? this.getDefaultLabel(); }
133
133
  async getIcon(ctx) { return this._lazyIcon ? this._lazyIcon(ctx) : this._icon; }
134
134
  _sort = 0;
135
- /** Ordre dans un menu (menu utilisateur, menu du locataire) : négatif, avant le choix du thème ou la liste des locataires. */
135
+ /** Position in a menu (user menu, tenant menu); negative values go before the theme switcher or the tenant list. */
136
136
  sort(sort) { this._sort = sort; return this; }
137
137
  getSort() { return this._sort; }
138
138
  /**
139
- * Élément d'un menu du panel (menu utilisateur, menu du locataire) — port de l'action « grouped » à adresse.
140
- * Seules les actions à adresse y ont leur place : le menu n'est pas un composant qui monte des actions.
139
+ * Item of a panel menu (user menu, tenant menu). Only URL actions are rendered: these menus are not components
140
+ * that can mount actions.
141
141
  */
142
142
  async toMenuItemHtml(ctx, options = {}) {
143
143
  if (!colorManager)
144
- throw new Error('Aucun gestionnaire de couleurs : appelez useColorManager().');
144
+ throw new Error('No color manager: call useColorManager() first.');
145
145
  const href = this.resolveUrl(ctx);
146
146
  if (!href || !/^(\/(?!\/)|https?:\/\/)/.test(href))
147
147
  return '';
@@ -150,25 +150,25 @@ export class Action {
150
150
  const glyph = iconName ? icon(iconName, { attributes: new Attributes().class(colors.length ? ['fi-color', `fi-color-${this._color}`] : []) }) : '';
151
151
  return `<a href="${e(href)}"${this._newTab ? ' target="_blank"' : options.navigate ? ' wire:navigate' : ''} class="${['fi-dropdown-list-item', 'fi-ac-grouped-action', ...colors].join(' ')} " role="menuitem" tabindex="-1">${glyph}<span class="fi-dropdown-list-item-label">${e(await this.getLabel(ctx))}</span></a>`;
152
152
  }
153
- /** Une adresse calculée ne se connaît qu'avec l'enregistrement : l'hôte la revérifie au montage (`resolveUrl`). */
153
+ /** A computed URL depends on the record, so the host checks it again on mount (`resolveUrl`). */
154
154
  isMountable() { return (this._handler !== null || this._schema !== null) && this._url === null; }
155
- /** Adresse de l'action dans ce contexte ; une action qui en a une est un lien, jamais une action montée. */
155
+ /** URL of the action in this context; an action with a URL is a link, never a mounted action. */
156
156
  resolveUrl(ctx) { return this._lazyUrl ? this._lazyUrl(ctx) : this._url; }
157
157
  hasModal() { return this._requiresConfirmation || this._schema !== null; }
158
- /** Formulaire affiché dans la modale. Une fonction : les composants sont reconstruits à chaque requête. */
158
+ /** Form shown in the modal. With a function, the components are rebuilt on every request. */
159
159
  schema(components, columns = {}) { this._schema = typeof components === 'function' ? components : () => components; this._schemaColumns = columns; return this; }
160
- /** Applique le formulaire de l'action au schéma fourni par l'hôte : la fonction rend soit des composants, soit le schéma configuré. */
160
+ /** Applies the action's form to the host's schema; the function returns either components or the configured schema. */
161
161
  applySchema(schema) {
162
162
  if (!this._schema)
163
163
  return null;
164
164
  const result = this._schema(schema);
165
165
  return Array.isArray(result) ? schema.columns(this._schemaColumns).components(result) : result;
166
166
  }
167
- /** Boutons ajoutés au pied de la modale, entre la validation et l'annulation. */
167
+ /** Extra buttons in the modal footer, after the submit button. */
168
168
  extraModalFooterActions(actions) { this._extraModalFooterActions = actions; return this; }
169
- /** État initial du formulaire (ex. à partir de l'enregistrement). */
169
+ /** Initial form state (e.g. from the record). */
170
170
  fillForm(resolver) { this._fillForm = resolver; return this; }
171
- /** Largeur de la modale (`xs` … `7xl`). Par défaut `md` pour une confirmation, `4xl` pour un formulaire. */
171
+ /** Modal width (`xs` … `7xl`). Defaults to `md` for a confirmation and `4xl` for a form. */
172
172
  modalWidth(width) { this._modalWidth = width; return this; }
173
173
  hasSchema() { return this._schema !== null; }
174
174
  async getFormFill(ctx) { return this._fillForm ? this._fillForm(ctx) : null; }
@@ -176,11 +176,11 @@ export class Action {
176
176
  _mutateData = null;
177
177
  _before = null;
178
178
  _after = null;
179
- /** Transforme les données validées du formulaire avant le traitement (ajouter l'auteur, normaliser une valeur…). */
179
+ /** Transforms the validated form data before the handler runs (add the author, normalize a value…). */
180
180
  mutateDataUsing(fn) { this._mutateData = fn; return this; }
181
- /** Exécuté juste avant le traitement ; lever une erreur l'annule. */
181
+ /** Runs right before the handler; throwing cancels the action. */
182
182
  before(fn) { this._before = fn; return this; }
183
- /** Exécuté après un traitement réussi (journal, notification supplémentaire…). */
183
+ /** Runs after the handler succeeds (logging, extra notification…). */
184
184
  after(fn) { this._after = fn; return this; }
185
185
  async call(ctx) {
186
186
  if (!this._handler)
@@ -192,10 +192,10 @@ export class Action {
192
192
  await this._after?.(ctx);
193
193
  return result;
194
194
  }
195
- /** Élément de menu d'une action groupée de table — port de l'action « grouped » (`fi-ac-grouped-action`). Le clic passe par `filamentTable.mountAction()`, qui envoie la sélection. */
195
+ /** Menu item of a table bulk action. The click goes through the table's Alpine `mountAction()`, which sends the selection. */
196
196
  async toGroupedHtml(ctx, context) {
197
197
  if (!colorManager)
198
- throw new Error('Aucun gestionnaire de couleurs : appelez useColorManager().');
198
+ throw new Error('No color manager: call useColorManager() first.');
199
199
  const expression = `mountAction(${js(this.name)}, {}, ${js(context)})`;
200
200
  const attributes = new Attributes()
201
201
  .merge({ role: 'menuitem', tabindex: '-1', type: 'button', 'wire:loading.attr': 'aria-disabled', 'wire:loading.class': 'fi-disabled', 'wire:target': expression })
@@ -205,12 +205,12 @@ export class Action {
205
205
  return `<button ${attributes.toHtml()}>${iconHtml}${loadingIndicator(new Attributes({ 'wire:loading.delay.default': '', 'wire:target': expression }))}<span class="fi-dropdown-list-item-label">${e((await evaluate(this._label, ctx)) ?? this.getDefaultLabel())}</span></button>`;
206
206
  }
207
207
  /**
208
- * Élément de menu d'une action de ligne (dans un ActionGroup) — port de l'action « grouped ».
209
- * Filament a deux balisages : un chemin court, et la vue Blade dès qu'un raccourci clavier est posé (ordre des classes différent).
208
+ * Menu item of a row action inside an ActionGroup. Disabled-capable and key-bound items use a different class order
209
+ * than plain items; front-end CSS and tests rely on the exact markup.
210
210
  */
211
211
  async toRecordGroupedHtml(ctx, context, wireKey) {
212
212
  if (!colorManager)
213
- throw new Error('Aucun gestionnaire de couleurs : appelez useColorManager().');
213
+ throw new Error('No color manager: call useColorManager() first.');
214
214
  const color = this._lazyColor ? await this._lazyColor(ctx) : this._color;
215
215
  const iconName = this._lazyIcon ? await this._lazyIcon(ctx) : (this._groupedIcon ?? this._icon);
216
216
  const expression = e(`mountAction(${js(this.name)}, {}, ${js(context)})`);
@@ -218,17 +218,17 @@ export class Action {
218
218
  const label = (await evaluate(this._label, ctx)) ?? this.getDefaultLabel();
219
219
  const iconHtml = iconName ? icon(iconName, { attributes: new Attributes({ 'wire:loading.remove.delay.default': '1', 'wire:target': `mountAction(${js(this.name)}, {}, ${js(context)})` }).class(color !== 'gray' ? ['fi-color', `fi-color-${color}`] : []) }) : '';
220
220
  const inner = `${iconHtml}${loadingIndicator(new Attributes({ 'wire:loading.delay.default': '', 'wire:target': `mountAction(${js(this.name)}, {}, ${js(context)})` }))}<span class="fi-dropdown-list-item-label">${e(label)}</span>`;
221
- // Action de navigation : un lien, sans montage côté serveur.
221
+ // Navigation action: a plain link, nothing is mounted on the server.
222
222
  const href = this._lazyUrl ? this._lazyUrl(ctx) : this._url;
223
223
  if (href) {
224
224
  const plainIcon = iconName ? icon(iconName, { attributes: new Attributes().class(color !== 'gray' ? ['fi-color', `fi-color-${color}`] : []) }) : '';
225
- // Lien externe (nouvel onglet) : pas de navigation Livewire, classes dans l'ordre de Filament.
225
+ // New-tab link: no `wire:navigate`.
226
226
  if (this._newTab)
227
227
  return `<a href="${e(href)}" target="_blank" role="menuitem" tabindex="-1" wire:key="${e(wireKey)}" class="${['fi-ac-grouped-action', 'fi-dropdown-list-item', ...colors].join(' ')}">${plainIcon}<span class="fi-dropdown-list-item-label">${e(label)}</span></a>`;
228
228
  return `<a href="${e(href)}" wire:navigate wire:key="${e(wireKey)}" class="${['fi-dropdown-list-item', 'fi-ac-grouped-action', ...colors].join(' ')}" role="menuitem" tabindex="-1">${plainIcon}<span class="fi-dropdown-list-item-label">${e(label)}</span></a>`;
229
229
  }
230
230
  if (this._disabled !== false) {
231
- // Désactivation conditionnelle : Filament passe alors par sa vue Blade, que l'action soit active ou non (ordre des classes différent).
231
+ // Conditionally disabled: this markup is used whether or not the action ends up disabled.
232
232
  const disabled = await this.isDisabled(ctx);
233
233
  return `<button ${disabled ? 'aria-disabled="true" ' : ''}role="menuitem" tabindex="-1" type="button" wire:loading.attr="aria-disabled" wire:loading.class="fi-disabled" class="${['fi-ac-grouped-action', 'fi-dropdown-list-item', disabled ? 'fi-disabled' : null, ...colors].filter(Boolean).join(' ')}" wire:click="${expression}" wire:key="${e(wireKey)}">${inner}</button>`;
234
234
  }
@@ -237,10 +237,10 @@ export class Action {
237
237
  }
238
238
  return `<button type="button" wire:loading.attr="aria-disabled" wire:loading.class="fi-disabled" wire:click="${expression}" wire:key="${e(wireKey)}" class="${['fi-dropdown-list-item', 'fi-ac-grouped-action', ...colors].join(' ')}" role="menuitem" tabindex="-1">${inner}</button>`;
239
239
  }
240
- /** Lien d'une action de ligne de table (`fi-ac-link-action`) — port de l'action « link » en petite taille. */
240
+ /** Small link (or icon button) for a table row action. */
241
241
  async toLinkHtml(ctx, context, wireKey) {
242
242
  if (!colorManager)
243
- throw new Error('Aucun gestionnaire de couleurs : appelez useColorManager().');
243
+ throw new Error('No color manager: call useColorManager() first.');
244
244
  const color = this._lazyColor ? await this._lazyColor(ctx) : this._color;
245
245
  const iconName = this._lazyIcon ? await this._lazyIcon(ctx) : (this._tableIcon ?? this._icon);
246
246
  const raw = `mountAction(${js(this.name)}, {}, ${js(context)})`;
@@ -253,27 +253,26 @@ export class Action {
253
253
  return `<button title="${e(label)}" aria-label="${e(label)}" ${disabled ? 'disabled ' : ''}type="button" wire:loading.attr="aria-disabled" wire:loading.class="fi-disabled" class="${buttonClasses}"${disabled ? '' : ` wire:click="${e(raw)}"`} wire:key="${e(wireKey)}">${glyph}${loadingIndicator(new Attributes({ 'wire:loading.delay.default': '', 'wire:target': raw }))}</button>`;
254
254
  }
255
255
  const iconHtml = iconName ? icon(iconName, { size: 'sm', attributes: new Attributes({ 'wire:loading.remove.delay.default': '1', 'wire:target': raw }) }) : '';
256
- // Action de navigation : un lien vers la page, sans montage côté serveur.
257
256
  const href = this._lazyUrl ? this._lazyUrl(ctx) : this._url;
258
257
  const tooltip = await this.tooltipAttribute(ctx);
259
- // Seules les adresses locales ou http(s) deviennent des liens.
258
+ // Only local or http(s) URLs become links (no `javascript:` or protocol-relative URLs).
260
259
  if (href && /^(\/(?!\/)|https?:\/\/)/.test(href))
261
260
  return `<a href="${e(href)}" ${this._newTab ? 'target="_blank" ' : ''}${tooltip}${this._newTab ? '' : 'wire:navigate '}wire:key="${e(wireKey)}" class="${classes}">${iconName ? icon(iconName, { size: 'sm' }) : ''}<span class="fi-link-label">${e(label)}</span></a>`;
262
261
  const keys = this._keyBindings.length ? `x-bind:id="$id('key-bindings')" x-mousetrap.global.${this._keyBindings.map((k) => k.replace(/\+/g, '-')).join('.')}="document.getElementById($el.id)?.click()" ` : '';
263
262
  return `<button ${keys}${tooltip}type="button" wire:loading.attr="aria-disabled" wire:loading.class="fi-disabled" class="${classes}" wire:click="${e(raw)}" wire:key="${e(wireKey)}">${iconHtml}${loadingIndicator(new Attributes({ 'wire:loading.delay.default': '', 'wire:target': raw }), 'sm')}<span class="fi-link-label">${e(label)}</span></button>`;
264
263
  }
265
- /** Fenêtre modale d'une action montée — port de filament-actions::action-modal et filament::components.modal (confirmation seulement). */
264
+ /** Modal of a mounted action; `content` is the rendered form, if any. */
266
265
  async toModalHtml(ctx, index, content = null) {
267
266
  if (!colorManager)
268
- throw new Error('Aucun gestionnaire de couleurs : appelez useColorManager().');
267
+ throw new Error('No color manager: call useColorManager() first.');
269
268
  const schemaCtx = { livewire: ctx.livewire, component: null, operation: null, record: ctx.record, get: () => undefined, state: undefined };
270
269
  const label = (await evaluate(this._label, schemaCtx)) ?? this.getDefaultLabel();
271
270
  const color = this._lazyColor ? await this._lazyColor(schemaCtx) : this._color;
272
271
  const confirmation = this._requiresConfirmation;
273
- // Comme Filament : une confirmation est centrée (md) ; un formulaire est aligné au début (4xl), sauf en petite largeur.
272
+ // A confirmation is centered (md); a form is start-aligned (4xl), except at small widths.
274
273
  const width = this._modalWidth ?? (confirmation ? 'md' : '4xl');
275
274
  const submitLabel = this._modalSubmitActionLabel ?? t(confirmation ? 'filament-actions::modal.actions.confirm.label' : 'filament-actions::modal.actions.submit.label');
276
- // Même ordre que filament-actions::action-modal : contenu libre, schéma, pied de contenu, chacun entre ses deux emplacements.
275
+ // Hook order: custom content, schema, content footer, each between its before/after slots.
277
276
  const scopes = ctx.livewire.getRenderHookScopes?.() ?? [];
278
277
  const hook = (name) => renderHook(name, scopes, { action: this });
279
278
  const hooked = hook(ActionsRenderHook.MODAL_CUSTOM_CONTENT_BEFORE) + hook(ActionsRenderHook.MODAL_CUSTOM_CONTENT_AFTER)
@@ -288,31 +287,31 @@ export class Action {
288
287
  description: (await evaluate(this._modalDescription, ctx)) ?? (this._requiresConfirmation ? t('filament-actions::modal.confirmation') : null),
289
288
  iconName: this._modalIcon ?? (this._requiresConfirmation ? 'o-exclamation-triangle' : null), color,
290
289
  cancel: await Action.make('cancel').label(this._cancelLabel ?? t('filament-actions::modal.actions.cancel.label')).color('gray').alpineClickHandler('close()').toHtml(schemaCtx),
291
- // Comme Filament : le bouton de validation d'une action grise est en couleur primaire.
290
+ // The submit button of a gray action uses the primary color.
292
291
  submit: !this._hasSubmit ? '' : (await Action.make('submit').label(submitLabel).color(color === 'gray' ? 'primary' : color).submit('callMountedAction').toHtml(schemaCtx)) + (await Promise.all(this._extraModalFooterActions.map((a) => a.toHtml(schemaCtx)))).join(''),
293
292
  });
294
293
  }
295
294
  alpineClickHandler(handler) { this._alpineClick = handler; return this; }
296
295
  _livewireTarget = null;
297
296
  _iconAfter = false;
298
- /** Cible de l'indicateur de chargement (`wire:target`), quand le clic est géré par du JavaScript. */
297
+ /** Loading indicator target (`wire:target`) when the click is handled in JavaScript. */
299
298
  livewireTarget(target) { this._livewireTarget = target; return this; }
300
- /** Icône (et indicateur de chargement) après le libellé : `'after'`. */
299
+ /** `'after'` puts the icon (and loading indicator) after the label. */
301
300
  iconPosition(position) { this._iconAfter = position === 'after'; return this; }
302
301
  keyBindings(bindings) { this._keyBindings = bindings; return this; }
303
302
  getName() { return this.name; }
304
303
  _authorize = true;
305
- /** Condition d'autorisation, distincte de `visible` / `hidden` : non remplie, l'action est masquée et refusée par le serveur. */
304
+ /** Authorization check, separate from `visible` / `hidden`: when it fails, the action is hidden and the server refuses it. */
306
305
  authorize(condition) { this._authorize = condition; return this; }
307
306
  async isVisible(ctx) { return !(await evaluate(this._hidden, ctx)) && (await evaluate(this._visible, ctx)) && Boolean(await evaluate(this._authorize, ctx)); }
308
307
  visible(condition = true) { this._visible = condition; return this; }
309
- /** Action affichée mais inutilisable ; le serveur refuse aussi de la monter. */
308
+ /** Shown but unusable; the server also refuses to mount it. */
310
309
  disabled(condition = true) { this._disabled = condition; return this; }
311
310
  async isDisabled(ctx) { return evaluate(this._disabled, ctx); }
312
- /** Bouton plein — port de la vue `filament::components.button` pour une action de soumission. */
311
+ /** Renders the action as a button (or as a link when it has a URL). */
313
312
  async toHtml(ctx, explicitMountContext = null) {
314
313
  if (!colorManager)
315
- throw new Error('Aucun gestionnaire de couleurs : appelez useColorManager().');
314
+ throw new Error('No color manager: call useColorManager() first.');
316
315
  const linkUrl = this._lazyUrl ? this._lazyUrl(ctx) : this._url;
317
316
  if (linkUrl !== null && /^(\/(?!\/)|https?:\/\/)/.test(linkUrl)) {
318
317
  const link = new Attributes().class(['fi-ac-btn-action', 'fi-btn', `fi-size-${this._size}`, ...(this._color !== 'gray' ? colorManager.getComponentClasses('button', this._color) : [])]);
@@ -322,9 +321,9 @@ export class Action {
322
321
  return `<a href="${e(linkUrl)}" ${this._newTab ? 'target="_blank" ' : ''}${await this.tooltipAttribute(ctx)}${this._newTab ? '' : 'wire:navigate '}${link.toHtml()}>${linkIcon}${e((await evaluate(this._label, ctx)) ?? this.getDefaultLabel())}${badgeHtml}</a>`;
323
322
  }
324
323
  const isSubmit = this._submit !== null;
325
- // Action à traitement : le clic monte l'action côté serveur, avec le contexte fourni par l'hôte (ex. l'enregistrement).
324
+ // Handler action: the click mounts the action on the server, with the host's context (e.g. the record).
326
325
  const mountContext = explicitMountContext ?? ctx.livewire?.getActionMountContext?.() ?? {};
327
- // Sans contexte, Filament émet la forme courte `mountAction('nom')`.
326
+ // Without context, the short form `mountAction('name')` is emitted.
328
327
  const mountExpression = this._handler ? (Object.keys(mountContext).length ? `mountAction(${js(this.name)}, {}, ${js(mountContext)})` : `mountAction(${js(this.name)})`) : null;
329
328
  const attributes = new Attributes()
330
329
  .merge({
@@ -349,16 +348,16 @@ export class Action {
349
348
  });
350
349
  }
351
350
  }
352
- /** Groupe d'actions replié dans un menu déroulant (port de ActionGroup, bouton-icône « ⋮ »). */
351
+ /** Group of actions collapsed into a dropdown behind a "⋮" icon button. */
353
352
  export class ActionGroup {
354
353
  actions;
355
354
  constructor(actions) { this.actions = actions; }
356
355
  static make(actions) { return configured(new ActionGroup(actions)); }
357
356
  getActions() { return this.actions; }
358
- /** `keyOf` donne la clé `wire:key` de chaque action (elle dépend de l'enregistrement). */
357
+ /** `keyOf` returns each action's `wire:key` (it depends on the record). */
359
358
  async toHtml(ctx, context, keyOf) {
360
359
  if (!colorManager)
361
- throw new Error('Aucun gestionnaire de couleurs : appelez useColorManager().');
360
+ throw new Error('No color manager: call useColorManager() first.');
362
361
  const items = [];
363
362
  for (const action of this.actions)
364
363
  if (await action.isVisible(ctx))
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@navette-js/actions",
3
- "version": "1.0.0-beta.1",
4
- "description": "Actions and modals for Navette, the Node.js port of Filament.",
3
+ "version": "1.0.0-beta.3",
4
+ "description": "Actions and modals for Navette admin panels.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "exports": {
@@ -31,7 +31,6 @@
31
31
  },
32
32
  "keywords": [
33
33
  "navette",
34
- "filament",
35
34
  "admin",
36
35
  "panel",
37
36
  "livewire",
@@ -39,8 +38,8 @@
39
38
  "node"
40
39
  ],
41
40
  "dependencies": {
42
- "@navette-js/support": "1.0.0-beta.1",
43
- "@navette-js/schemas": "1.0.0-beta.1"
41
+ "@navette-js/support": "1.0.0-beta.3",
42
+ "@navette-js/schemas": "1.0.0-beta.3"
44
43
  },
45
44
  "publishConfig": {
46
45
  "access": "public",
package/views/button.ejs CHANGED
@@ -1,4 +1,4 @@
1
- <%# Bouton d'action — port de la vue Blade filament::components.button %>
1
+ <%# Action button; submit buttons show a processing message while the form is submitting. %>
2
2
  <button <%- keyBindings %><% if (isSubmit) { %>x-data="filamentFormButton" x-bind:class="{ 'fi-processing': isProcessing }" <% } %><%- attributes %>>
3
3
  <% if (!iconAfter) { %><%- iconHtml %><% if (hasWireLoader) { %><%- wireLoader %><% } %><% if (isSubmit) { %><%- formLoader %><% } %><% } %>
4
4
  <% if (isSubmit) { %>
package/views/modal.ejs CHANGED
@@ -1,4 +1,4 @@
1
- <%# Modale d'une action montée — port de filament-actions::action-modal et filament::components.modal (modale de confirmation, centrée, largeur md) %>
1
+ <%# Modal of a mounted action (confirmation or form). Closing it unmounts the action. %>
2
2
  <div wire:partial="action-modals.<%- index %>"<% if (description) { %> aria-describedby="<%= id %>.description"<% } %> aria-labelledby="<%= id %>.heading" aria-modal="true" id="<%= id %>" role="<%- confirmation ? 'alertdialog' : 'dialog' %>" tabindex="-1" x-data="filamentModal({
3
3
  id: <%- js(id) %>,
4
4
  isScrollLocked: true,
@@ -37,7 +37,7 @@
37
37
  </div>
38
38
  </div>
39
39
 
40
- <%# Les six emplacements de ActionsRenderHook sont assemblés dans `content` par Action.toModalHtml(). %>
40
+ <%# The six ActionsRenderHook slots are already assembled into `content` by Action.toModalHtml(). %>
41
41
  <% if (content !== null) { %>
42
42
  <div wire:key="<%= livewireId %>.modal.<%= id %>.content" class="fi-modal-content">
43
43
  <%- content %>
@@ -46,7 +46,7 @@
46
46
 
47
47
  <div wire:key="<%= livewireId %>.modal.<%= id %>.footer" class="fi-modal-footer fi-align-<%- confirmation ? 'center' : 'start' %>">
48
48
  <div class="fi-modal-footer-actions">
49
- <%# Confirmation : annuler puis confirmer ; formulaire : valider puis annuler (comme Filament) %>
49
+ <%# Confirmation: cancel then confirm; form: submit then cancel. %>
50
50
  <%- confirmation ? cancel + submit : submit + cancel %>
51
51
  </div>
52
52
  </div>