@tekkare/romulus 0.1.246 → 0.1.247

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.16.0"
6
6
  },
7
- "version": "0.1.245",
7
+ "version": "0.1.246",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -26,6 +26,7 @@ import type {
26
26
  RmAlertKind,
27
27
  RmAlertDraft,
28
28
  RmAlertField,
29
+ RmAlertProductTexts,
29
30
  RmAlertTrigger,
30
31
  RmAlertViewOption,
31
32
  } from '../utils/alerts'
@@ -66,6 +67,12 @@ const props = withDefaults(
66
67
  * disparait, il ne reste que l'alerte de vue.
67
68
  */
68
69
  canWatchProduct?: boolean
70
+ /**
71
+ * Textes du second genre d'alerte, quand l'app y suit autre chose que des
72
+ * marques (Case Atlas : des maladies rares). Les cles absentes gardent le
73
+ * texte des marques. `{max}` est remplace par `maxBrands`.
74
+ */
75
+ productTexts?: Partial<RmAlertProductTexts>
69
76
  /** Nombre d'elements que la vue tient a l'ecran, s'il est connu. */
70
77
  scopeCount?: number | null
71
78
  submitting?: boolean
@@ -92,6 +99,7 @@ const props = withDefaults(
92
99
  maxBrands: 20,
93
100
  canWatchView: true,
94
101
  canWatchProduct: true,
102
+ productTexts: () => ({}),
95
103
  fields: () => [],
96
104
  scopeCount: null,
97
105
  submitting: false,
@@ -134,6 +142,7 @@ const FR = {
134
142
  brandsRequired: 'Choisissez au moins une marque.',
135
143
  brandsTooMany: 'Une alerte suit {max} marques au maximum.',
136
144
  productFields: 'Rien à choisir : l’alerte signale tout changement sur ces marques (nouvel élément, changement de valeur, sortie du périmètre).',
145
+ productTitle: 'Veille',
137
146
  screenOption: 'Écran actuel (une vue sera créée)',
138
147
  viewSelect: 'Vue à surveiller',
139
148
  viewApplied: 'La vue choisie est appliquée à l’écran : c’est lui qui fournit la requête surveillée.',
@@ -143,15 +152,17 @@ const FR = {
143
152
  viewHint: 'L’alerte se déclenche sur le périmètre de cette vue, tel qu’il est à l’écran.',
144
153
  viewFields: 'Rien à choisir : l’alerte signale tout changement dans la vue (nouvel élément, changement de valeur, sortie du périmètre).',
145
154
  channels: 'Canaux',
146
- inApp: 'Dans l’app (pastille sur la tuile Alertes)',
147
- email: 'Email',
155
+ inApp: 'L’alerte apparaît toujours dans l’app (pastille sur la tuile Alertes).',
156
+ email: 'Recevoir aussi un email',
148
157
  cadence: 'Fréquence d’envoi du mail',
149
- daily: 'Quotidienne (un envoi par jour au plus)',
150
- weekly: 'Hebdomadaire (un envoi par semaine au plus)',
151
- monthly: 'Mensuelle (un envoi par mois au plus)',
152
- test5: 'Toutes les 5 minutes (mode essai)',
153
- cadenceHint:
154
- 'Un plafond, pas un rythme : rien ne part quand rien n’a bougé, et la détection suit celle des données.',
158
+ checkEvery: 'Vérifier une fois par',
159
+ daily: 'jour',
160
+ weekly: 'semaine',
161
+ monthly: 'mois',
162
+ checkTest: 'Vérifier toutes les',
163
+ test5: '5 minutes',
164
+ onlyOnChange: 'et envoyer un email uniquement si un changement est détecté',
165
+ testMode: 'mode essai',
155
166
  submit: 'Créer l’alerte',
156
167
  save: 'Enregistrer',
157
168
  reseed: 'Cette alerte suit désormais tout changement. L’enregistrer la fait repartir d’une nouvelle référence : vous ne serez prévenu que de ce qui bouge ensuite.',
@@ -173,6 +184,7 @@ const EN = {
173
184
  brandsRequired: 'Pick at least one brand.',
174
185
  brandsTooMany: 'An alert watches at most {max} brands.',
175
186
  productFields: 'Nothing to choose: the alert reports any change on these brands (new item, value change, left the scope).',
187
+ productTitle: 'Watch',
176
188
  screenOption: 'Current screen (a view will be created)',
177
189
  viewSelect: 'View to watch',
178
190
  viewApplied: 'The chosen view is applied to the screen: the screen is what provides the watched query.',
@@ -182,15 +194,17 @@ const EN = {
182
194
  viewHint: 'The alert fires on this view’s scope, as it stands on screen.',
183
195
  viewFields: 'Nothing to choose: the alert reports any change in the view (new item, value change, left the scope).',
184
196
  channels: 'Channels',
185
- inApp: 'In app (badge on the Alerts tile)',
186
- email: 'Email',
197
+ inApp: 'The alert always shows in the app (badge on the Alerts tile).',
198
+ email: 'Also send me an email',
187
199
  cadence: 'Mail frequency',
188
- daily: 'Daily (at most one a day)',
189
- weekly: 'Weekly (at most one a week)',
190
- monthly: 'Monthly (at most one a month)',
191
- test5: 'Every 5 minutes (test mode)',
192
- cadenceHint:
193
- 'A cap, not a rhythm: nothing goes out when nothing moved, and detection follows the data.',
200
+ checkEvery: 'Check once a',
201
+ daily: 'day',
202
+ weekly: 'week',
203
+ monthly: 'month',
204
+ checkTest: 'Check every',
205
+ test5: '5 minutes',
206
+ onlyOnChange: 'and send an email only if a change is detected',
207
+ testMode: 'test mode',
194
208
  submit: 'Create the alert',
195
209
  save: 'Save',
196
210
  reseed: 'This alert now watches every change. Saving it restarts from a new baseline: you are only told about what moves afterwards.',
@@ -200,7 +214,7 @@ const EN = {
200
214
  scope: 'items in the view right now',
201
215
  }
202
216
 
203
- const T = computed(() => (props.lang === 'en' ? EN : FR))
217
+ const T = computed(() => ({ ...(props.lang === 'en' ? EN : FR), ...props.productTexts }))
204
218
 
205
219
  /**
206
220
  * Vue retenue. `null` designe l'ecran actuel, qui deviendra une vue au moment
@@ -299,7 +313,7 @@ watch(
299
313
  watch([kind, brands], () => {
300
314
  if (titleTouched.value) return
301
315
  if (watchesProduct.value) {
302
- title.value = brands.value.length ? `Veille ${brands.value.join(', ')}` : ''
316
+ title.value = brands.value.length ? `${T.value.productTitle} ${brands.value.join(', ')}` : ''
303
317
  return
304
318
  }
305
319
  const view = props.views.find((entry) => entry.id === selectedViewId.value)
@@ -313,20 +327,20 @@ watch(selectedViewId, (id) => {
313
327
  if (id) emit('select-view', id)
314
328
  })
315
329
 
316
- /** Options de cadence, dans l'ordre du plus espace au plus frequent. */
330
+ /**
331
+ * Options de cadence, du plus frequent au plus espace. L'unite se lit en gras
332
+ * dans la phrase (« Verifier une fois par **semaine** ») : c'est elle qui
333
+ * distingue les options, le reste de la ligne est commun.
334
+ */
317
335
  const cadenceOptions = computed(() => [
318
- { value: 'monthly' as const, label: T.value.monthly },
319
- { value: 'weekly' as const, label: T.value.weekly },
320
- { value: 'daily' as const, label: T.value.daily },
321
336
  ...(props.testCadence
322
- ? [{ value: 'test5' as const, label: T.value.test5 }]
337
+ ? [{ value: 'test5' as const, lead: T.value.checkTest, unit: T.value.test5, test: true }]
323
338
  : []),
339
+ { value: 'daily' as const, lead: T.value.checkEvery, unit: T.value.daily, test: false },
340
+ { value: 'weekly' as const, lead: T.value.checkEvery, unit: T.value.weekly, test: false },
341
+ { value: 'monthly' as const, lead: T.value.checkEvery, unit: T.value.monthly, test: false },
324
342
  ])
325
343
 
326
- const cadenceLabel = computed(
327
- () => cadenceOptions.value.find((option) => option.value === cadence.value)?.label ?? '',
328
- )
329
-
330
344
  const brandsInvalid = computed(
331
345
  () =>
332
346
  watchesProduct.value &&
@@ -464,26 +478,44 @@ function submit() {
464
478
 
465
479
  <fieldset class="rm-alert-form__group" data-field="channels">
466
480
  <legend class="rm-alert-form__label">{{ T.channels }}</legend>
467
- <RmCheckbox :model-value="true" :label="T.inApp" disabled />
468
-
469
- <!-- La case et sa frequence sur une meme rangee : le choix du canal et
470
- celui de son rythme se lisent ensemble, et la frequence n'a aucun
471
- sens sans la case. -->
472
- <div class="rm-alert-form__channel">
473
- <RmCheckbox v-model="emailOn" :label="T.email" />
474
- <RmDropdownSelect
475
- v-if="emailOn"
476
- label=""
481
+ <!-- L'app n'est pas un choix : une case grisee laissait croire qu'on
482
+ pouvait la decocher. Seul le mail se choisit. -->
483
+ <p class="rm-alert-form__in-app">
484
+ <RmIcon name="Bell" :size="14" />
485
+ {{ T.inApp }}
486
+ </p>
487
+
488
+ <RmCheckbox v-model="emailOn" :label="T.email" />
489
+
490
+ <!-- Une liste plutot qu'un menu : chaque option tient sur deux lignes,
491
+ l'unite en gras et la condition d'envoi en dessous, en grise. -->
492
+ <div
493
+ v-if="emailOn"
494
+ class="rm-alert-form__cadences"
495
+ role="radiogroup"
496
+ :aria-label="T.cadence"
497
+ >
498
+ <label
499
+ v-for="option in cadenceOptions"
500
+ :key="option.value"
477
501
  class="rm-alert-form__cadence"
478
- :options="cadenceOptions.map((option) => option.label)"
479
- :model-value="cadenceLabel"
480
- @update:model-value="
481
- cadence =
482
- cadenceOptions.find((option) => option.label === $event)?.value ?? 'weekly'
483
- "
484
- />
502
+ :class="{ 'rm-alert-form__cadence--on': cadence === option.value }"
503
+ >
504
+ <input
505
+ v-model="cadence"
506
+ type="radio"
507
+ name="rm-alert-cadence"
508
+ :value="option.value"
509
+ >
510
+ <span class="rm-alert-form__cadence-text">
511
+ <span class="rm-alert-form__cadence-main">
512
+ {{ option.lead }} <strong>{{ option.unit }}</strong>
513
+ <template v-if="option.test"> ({{ T.testMode }})</template>
514
+ </span>
515
+ <span class="rm-alert-form__cadence-sub">{{ T.onlyOnChange }}</span>
516
+ </span>
517
+ </label>
485
518
  </div>
486
- <p v-if="emailOn" class="rm-alert-form__hint">{{ T.cadenceHint }}</p>
487
519
  </fieldset>
488
520
 
489
521
  <p
@@ -509,5 +541,5 @@ function submit() {
509
541
  </template>
510
542
 
511
543
  <style scoped>
512
- .rm-alert-form{display:flex;flex-direction:column;gap:18px}.rm-alert-form__label{color:var(--text-primary);font-size:13px;font-weight:600;margin:0 0 2px;padding:0}.rm-alert-form__group{border:0;display:flex;flex-direction:column;gap:8px;margin:0;padding:0}.rm-alert-form__view-name{align-items:center;color:var(--text-primary);display:flex;flex-wrap:wrap;font-size:14px;gap:6px;margin:0}.rm-alert-form__count{color:var(--text-muted);font-size:11.5px}.rm-alert-form__hint{color:var(--text-muted);font-size:12px;line-height:1.45;margin:0 0 4px}.rm-alert-form__error{color:var(--color-danger);font-size:12px;margin:0}.rm-alert-form__channel{align-items:center;display:flex;flex-wrap:wrap;gap:12px}.rm-alert-form__cadence{flex:1 1 240px;min-width:240px}.rm-alert-form__nested{display:flex;flex-direction:column;gap:6px;padding-left:26px}.rm-alert-form__baseline{align-items:flex-start;background:var(--bg-surface-3);border-radius:8px;color:var(--text-secondary);display:flex;font-size:12.5px;gap:8px;line-height:1.45;margin:0;padding:10px 12px}.rm-alert-form__actions{display:flex;gap:8px;justify-content:flex-end}
544
+ .rm-alert-form{display:flex;flex-direction:column;gap:18px}.rm-alert-form__label{color:var(--text-primary);font-size:13px;font-weight:600;margin:0 0 2px;padding:0}.rm-alert-form__group{border:0;display:flex;flex-direction:column;gap:8px;margin:0;padding:0}.rm-alert-form__view-name{align-items:center;color:var(--text-primary);display:flex;flex-wrap:wrap;font-size:14px;gap:6px;margin:0}.rm-alert-form__count{color:var(--text-muted);font-size:11.5px}.rm-alert-form__hint{color:var(--text-muted);font-size:12px;line-height:1.45;margin:0 0 4px}.rm-alert-form__error{color:var(--color-danger);font-size:12px;margin:0}.rm-alert-form__in-app{align-items:center;color:var(--text-secondary);display:flex;font-size:13px;gap:8px;margin:0}.rm-alert-form__cadences{display:flex;flex-direction:column;gap:6px;padding-left:26px}.rm-alert-form__cadence{align-items:flex-start;border:1px solid var(--border-soft);border-radius:var(--radius-md);cursor:pointer;display:flex;gap:10px;padding:8px 12px}.rm-alert-form__cadence:hover{background:var(--bg-hover)}.rm-alert-form__cadence--on{background:rgba(var(--brand-rgb),.04);border-color:var(--color-secondary)}.rm-alert-form__cadence input{accent-color:var(--color-primary);margin:3px 0 0}.rm-alert-form__cadence-text{display:flex;flex-direction:column;gap:2px}.rm-alert-form__cadence-main{color:var(--text-primary);font-size:13px}.rm-alert-form__cadence-sub{color:var(--text-muted);font-size:12px}.rm-alert-form__nested{display:flex;flex-direction:column;gap:6px;padding-left:26px}.rm-alert-form__baseline{align-items:flex-start;background:var(--bg-surface-3);border-radius:8px;color:var(--text-secondary);display:flex;font-size:12.5px;gap:8px;line-height:1.45;margin:0;padding:10px 12px}.rm-alert-form__actions{display:flex;gap:8px;justify-content:flex-end}
513
545
  </style>
@@ -114,5 +114,5 @@ const modalStyle = computed(() => {
114
114
  </template>
115
115
 
116
116
  <style scoped>
117
- .rm-modal-overlay{align-items:center;backdrop-filter:blur(2px);background:rgba(0,0,0,.4);display:flex;font-family:var(--r-font-family);inset:0;justify-content:center;position:fixed;z-index:100}.rm-modal{background:var(--bg-surface);border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,.15);display:flex;flex-direction:column;max-height:90vh;max-width:95vw}.rm-modal__header{align-items:flex-start;display:flex;gap:var(--space-16);justify-content:space-between;padding:var(--space-20) var(--space-24) 0}.rm-modal__header-content{flex:1;min-width:0}.rm-modal__close{align-items:center;background:none;border:none;border-radius:var(--r-radius-md);color:var(--text-muted);cursor:pointer;display:flex;flex-shrink:0;height:32px;justify-content:center;transition:all var(--r-transition-fast);width:32px}.rm-modal__close:hover{background:var(--bg-surface-3);color:var(--text-primary)}.rm-modal__tabs{padding:0}.rm-modal__body{flex:1;overflow-y:auto;padding:var(--space-24)}.rm-modal__footer{border-top:1px solid var(--border-soft);padding:var(--space-16) var(--space-24)}.rm-modal-enter-active,.rm-modal-leave-active{transition:opacity .2s ease}.rm-modal-enter-active .rm-modal,.rm-modal-leave-active .rm-modal{transition:transform .2s ease}.rm-modal-enter-from,.rm-modal-leave-to{opacity:0}.rm-modal-enter-from .rm-modal,.rm-modal-leave-to .rm-modal{transform:scale(.95)}.rm-modal-leave-active,.rm-modal-leave-to{pointer-events:none}
117
+ .rm-modal-overlay{align-items:center;backdrop-filter:blur(2px);background:rgba(0,0,0,.4);display:flex;font-family:var(--r-font-family);inset:0;justify-content:center;position:fixed;z-index:100}.rm-modal{background:var(--bg-surface);border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,.15);display:flex;flex-direction:column;max-height:90vh;max-width:95vw}.rm-modal__header{align-items:flex-start;display:flex;gap:var(--space-16);justify-content:space-between;padding:var(--space-20) var(--space-24) 0}.rm-modal__header-content{flex:1;min-width:0}.rm-modal__close{align-items:center;background:none;border:none;border-radius:var(--r-radius-md);color:var(--text-muted);cursor:pointer;display:flex;flex-shrink:0;height:32px;justify-content:center;transition:all var(--r-transition-fast);width:32px}.rm-modal__close:hover{background:var(--bg-surface-3);color:var(--text-primary)}.rm-modal__tabs{padding:0}.rm-modal__body{flex:1;overflow-y:auto;padding:var(--space-24)}.rm-modal__footer{align-items:center;border-top:1px solid var(--border-soft);display:flex;flex-wrap:wrap;gap:var(--space-8);justify-content:flex-end;padding:var(--space-16) var(--space-24)}.rm-modal-enter-active,.rm-modal-leave-active{transition:opacity .2s ease}.rm-modal-enter-active .rm-modal,.rm-modal-leave-active .rm-modal{transition:transform .2s ease}.rm-modal-enter-from,.rm-modal-leave-to{opacity:0}.rm-modal-enter-from .rm-modal,.rm-modal-leave-to .rm-modal{transform:scale(.95)}.rm-modal-leave-active,.rm-modal-leave-to{pointer-events:none}
118
118
  </style>
@@ -221,7 +221,7 @@ const NoteLink = Node.create({
221
221
  title: node.attrs.label,
222
222
  class: 'rm-rte-pagelink',
223
223
  }),
224
- `${node.attrs.icon ? `${node.attrs.icon} ` : ''}${node.attrs.label || 'Page sans titre'}`,
224
+ `${node.attrs.icon ? `${node.attrs.icon} ` : ''}${node.attrs.label || 'Note sans titre'}`,
225
225
  ]
226
226
  },
227
227
  renderText({ node }) {
@@ -581,8 +581,8 @@ defineExpose({ editor })
581
581
  const GROUP_LABEL: Record<RmRichTextMentionItem['kind'], string> = {
582
582
  user: 'Personnes',
583
583
  resource: 'Ressources',
584
- page: 'Pages',
585
- 'create-page': 'Nouvelle page',
584
+ page: 'Notes',
585
+ 'create-page': 'Nouvelle note',
586
586
  }
587
587
  const MENTION_ICON: Record<RmRichTextMentionItem['kind'], string> = {
588
588
  user: 'User',
@@ -86,6 +86,11 @@ export interface RmSavedView {
86
86
  isTemplate?: boolean
87
87
  /** Nombre d'espaces vises. `0` vaut « tous ». */
88
88
  templateTargets?: number
89
+ /**
90
+ * Modele publie pour l'espace du lecteur nommement, et non pour tout le
91
+ * monde : pastille « Cree pour vous » au degrade de marque.
92
+ */
93
+ forYou?: boolean
89
94
  /**
90
95
  * Menu propre a cette vue, a la place de celui de la liste. Une liste ou
91
96
  * cohabitent mes vues et celles des autres n'a pas un seul menu : le
@@ -155,6 +160,7 @@ const T = computed(() => ({
155
160
  ? 'Default view, applied on opening'
156
161
  : "Vue par défaut, s'active à l'ouverture",
157
162
  shared: en.value ? 'Shared' : 'Partagée',
163
+ forYou: en.value ? 'Made for you' : 'Créé pour vous',
158
164
  sharedBy: (name: string) =>
159
165
  en.value ? `Shared by ${name}` : `Partagée par ${name}`,
160
166
  template: (n: number) => {
@@ -267,9 +273,13 @@ function onCardClick() {
267
273
  @click="onCardClick"
268
274
  >
269
275
  <div
270
- v-if="view.isDefault || view.sharedBy || view.shared || view.isTemplate"
276
+ v-if="view.isDefault || view.sharedBy || view.shared || view.isTemplate || view.forYou"
271
277
  class="rm-view__strip"
272
278
  >
279
+ <span v-if="view.forYou" class="rm-view__for-you">
280
+ <RmIcon name="Sparkle" :size="12" weight="fill" />
281
+ {{ T.forYou }}
282
+ </span>
273
283
  <span v-if="view.isDefault" class="rm-view__strip-mark">
274
284
  <RmIcon name="PushPin" :size="14" weight="fill" />
275
285
  {{ T.isDefault }}
@@ -427,5 +437,5 @@ function onCardClick() {
427
437
  </template>
428
438
 
429
439
  <style scoped>
430
- .rm-view{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-lg);color:var(--text-primary);cursor:pointer;font-family:var(--font-sans);padding:14px 18px;transition:border-color var(--duration-fast) var(--ease-out),box-shadow var(--duration-fast) var(--ease-out),background var(--duration-fast) var(--ease-out)}.rm-view:not(.rm-view--active):hover{background:var(--bg-hover);border-color:var(--color-accent-soft)}.rm-view--active{border-color:var(--color-secondary);box-shadow:0 0 0 3px rgba(var(--brand-rgb),.08);cursor:default}.rm-view__strip{align-items:center;background:var(--bg-hover);border-bottom:1px solid var(--border-soft);border-radius:7px 7px 0 0;color:var(--color-secondary);display:flex;flex-wrap:wrap;font-size:var(--font-11);font-weight:var(--weight-bold);gap:var(--space-8);letter-spacing:.06em;margin:-14px -18px var(--space-12);padding:var(--space-8) var(--space-16);text-transform:uppercase}.rm-view__strip-mark{align-items:center;display:inline-flex;gap:var(--space-6);white-space:nowrap}.rm-view__head{align-items:center;display:flex;gap:var(--space-10)}.rm-view__icon{align-items:center;border-radius:var(--radius-md);display:inline-flex;flex:0 0 auto;height:34px;justify-content:center;width:34px}.rm-view__icon--primary{background:var(--bg-hover);color:var(--color-secondary)}.rm-view__icon--success{background:var(--color-success-bg);color:var(--color-success)}.rm-view__icon--warning{background:var(--color-warning-bg);color:var(--color-warning)}.rm-view__icon--danger{background:var(--color-danger-bg);color:var(--color-danger)}.rm-view__icon--info{background:var(--color-info-bg);color:var(--color-info)}.rm-view__star{align-items:center;background:transparent;border:1px solid transparent;border-radius:var(--radius-md);color:var(--text-muted);cursor:pointer;display:inline-flex;flex:0 0 auto;height:28px;justify-content:center;padding:0;width:28px}.rm-view__star:hover{background:var(--bg-hover)}.rm-view__star--on,.rm-view__star:hover{color:var(--color-warning)}.rm-view__title,.rm-view__title-edit{flex:1;font-size:var(--font-16);font-weight:var(--weight-bold);min-width:0}.rm-view__title-edit{background:var(--bg-surface);border:1px solid var(--color-secondary);border-radius:var(--radius-md);color:var(--text-primary);font-family:inherit;outline:none;padding:var(--space-2) var(--space-8)}.rm-view__title-edit:focus{box-shadow:0 0 0 3px rgba(var(--brand-rgb),.12)}.rm-view__actions{align-items:center;display:flex;gap:var(--space-8)}.rm-view__badge{align-items:center;background:var(--color-success-bg);border-radius:var(--radius-pill);color:var(--color-success);display:inline-flex;font-size:var(--font-10);font-weight:var(--weight-bold);gap:var(--space-4);letter-spacing:.06em;padding:3px var(--space-8);text-transform:uppercase}.rm-view__menu-wrap{position:relative}.rm-view__more{align-items:center;background:transparent;border:none;border-radius:var(--radius-md);color:var(--text-muted);cursor:pointer;display:inline-flex;height:28px;justify-content:center;padding:0;width:28px}.rm-view__more:hover{background:var(--bg-hover);color:var(--text-primary)}.rm-view__menu-shade{inset:0;position:fixed;z-index:60}.rm-view__menu{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-lg);box-shadow:var(--shadow-lg);display:flex;flex-direction:column;min-width:200px;padding:var(--space-4);position:absolute;right:0;top:calc(100% + 6px);z-index:61}.rm-view__menu-item{align-items:center;background:transparent;border:none;border-radius:var(--radius-md);color:var(--text-primary);cursor:pointer;display:flex;font-family:inherit;font-size:var(--font-13);gap:var(--space-8);padding:var(--space-8) var(--space-12);text-align:left;width:100%}.rm-view__menu-item:hover{background:var(--bg-hover);color:var(--color-secondary)}.rm-view__menu-item--danger{color:var(--color-danger)}.rm-view__menu-item--danger:hover{background:var(--color-danger-bg);color:var(--color-danger)}.rm-view__desc{color:var(--text-secondary);font-size:var(--font-13);margin:var(--space-8) 0 var(--space-12)}.rm-view__config{background:var(--bg-surface-2);border:1px solid var(--border-soft);border-radius:var(--radius-md);display:flex;flex-direction:column;gap:var(--space-6);margin:10px 0 var(--space-12);padding:var(--space-10) 14px}.rm-view__row{align-items:flex-start;display:flex;flex-wrap:wrap;font-size:var(--font-12);gap:14px}.rm-view__row-key{color:var(--text-muted);display:inline-flex;flex-shrink:0;font-size:var(--font-10);font-weight:var(--weight-semibold);letter-spacing:.04em;min-width:96px;padding-top:var(--space-4);text-transform:uppercase}.rm-view__row-key,.rm-view__row-val{align-items:center;gap:var(--space-6)}.rm-view__row-val{color:var(--text-primary);display:flex;flex:1 1 200px;flex-wrap:wrap;padding:var(--space-2) 0}.rm-view__pill{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md);display:inline-flex;font-size:var(--font-12);gap:var(--space-4);padding:5px var(--space-10)}.rm-view__pill-k{color:var(--color-primary);font-weight:var(--weight-semibold)}.rm-view__pill-tip{min-width:0}.rm-view__pill-v{display:-webkit-box;-webkit-box-orient:vertical;color:var(--text-primary);font-weight:var(--weight-medium);-webkit-line-clamp:3;overflow:hidden}.rm-view__foot{border-top:1px solid var(--border-soft);color:var(--text-muted);display:flex;flex-wrap:wrap;font-size:var(--font-11);gap:var(--space-16);padding-top:var(--space-10)}.rm-view__foot span{align-items:center;display:inline-flex;gap:var(--space-4)}.rm-view__confirm{align-items:center;background:var(--color-danger-bg);border:1px solid var(--color-danger);border-radius:var(--radius-md);color:var(--color-danger);display:flex;flex-wrap:wrap;font-size:var(--font-13);gap:var(--space-12);margin-top:var(--space-12);padding:var(--space-8) var(--space-12)}.rm-view__confirm>span{align-items:center;display:inline-flex;flex:1 1 220px;gap:var(--space-8)}.rm-view__confirm-actions{display:flex;flex-shrink:0;gap:var(--space-8)}
440
+ .rm-view{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-lg);color:var(--text-primary);cursor:pointer;font-family:var(--font-sans);padding:14px 18px;transition:border-color var(--duration-fast) var(--ease-out),box-shadow var(--duration-fast) var(--ease-out),background var(--duration-fast) var(--ease-out)}.rm-view:not(.rm-view--active):hover{background:var(--bg-hover);border-color:var(--color-accent-soft)}.rm-view--active{border-color:var(--color-secondary);box-shadow:0 0 0 3px rgba(var(--brand-rgb),.08);cursor:default}.rm-view__strip{align-items:center;background:var(--bg-hover);border-bottom:1px solid var(--border-soft);border-radius:7px 7px 0 0;color:var(--color-secondary);display:flex;flex-wrap:wrap;font-size:var(--font-11);font-weight:var(--weight-bold);gap:var(--space-8);letter-spacing:.06em;margin:-14px -18px var(--space-12);padding:var(--space-8) var(--space-16);text-transform:uppercase}.rm-view__strip-mark{align-items:center;display:inline-flex;gap:var(--space-6);white-space:nowrap}.rm-view__head{align-items:center;display:flex;gap:var(--space-10)}.rm-view__icon{align-items:center;border-radius:var(--radius-md);display:inline-flex;flex:0 0 auto;height:34px;justify-content:center;width:34px}.rm-view__icon--primary{background:var(--bg-hover);color:var(--color-secondary)}.rm-view__icon--success{background:var(--color-success-bg);color:var(--color-success)}.rm-view__icon--warning{background:var(--color-warning-bg);color:var(--color-warning)}.rm-view__icon--danger{background:var(--color-danger-bg);color:var(--color-danger)}.rm-view__icon--info{background:var(--color-info-bg);color:var(--color-info)}.rm-view__star{align-items:center;background:transparent;border:1px solid transparent;border-radius:var(--radius-md);color:var(--text-muted);cursor:pointer;display:inline-flex;flex:0 0 auto;height:28px;justify-content:center;padding:0;width:28px}.rm-view__star:hover{background:var(--bg-hover)}.rm-view__star--on,.rm-view__star:hover{color:var(--color-warning)}.rm-view__title{overflow-wrap:anywhere}.rm-view__title,.rm-view__title-edit{flex:1;font-size:var(--font-16);font-weight:var(--weight-bold);min-width:0}.rm-view__title-edit{background:var(--bg-surface);border:1px solid var(--color-secondary);border-radius:var(--radius-md);color:var(--text-primary);font-family:inherit;outline:none;padding:var(--space-2) var(--space-8)}.rm-view__title-edit:focus{box-shadow:0 0 0 3px rgba(var(--brand-rgb),.12)}.rm-view__actions{align-items:center;display:flex;gap:var(--space-8)}.rm-view__for-you{background:linear-gradient(90deg,#4dccf7,#a06bf5 50%,#ff5cf0);box-shadow:0 1px 4px rgba(160,107,245,.35);color:#fff;text-shadow:0 1px 1px rgba(33,0,173,.35)}.rm-view__badge,.rm-view__for-you{align-items:center;border-radius:var(--radius-pill);display:inline-flex;font-size:var(--font-10);font-weight:var(--weight-bold);gap:var(--space-4);letter-spacing:.06em;padding:3px var(--space-8);text-transform:uppercase}.rm-view__badge{background:var(--color-success-bg);color:var(--color-success)}.rm-view__menu-wrap{position:relative}.rm-view__more{align-items:center;background:transparent;border:none;border-radius:var(--radius-md);color:var(--text-muted);cursor:pointer;display:inline-flex;height:28px;justify-content:center;padding:0;width:28px}.rm-view__more:hover{background:var(--bg-hover);color:var(--text-primary)}.rm-view__menu-shade{inset:0;position:fixed;z-index:60}.rm-view__menu{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-lg);box-shadow:var(--shadow-lg);display:flex;flex-direction:column;min-width:200px;padding:var(--space-4);position:absolute;right:0;top:calc(100% + 6px);z-index:61}.rm-view__menu-item{align-items:center;background:transparent;border:none;border-radius:var(--radius-md);color:var(--text-primary);cursor:pointer;display:flex;font-family:inherit;font-size:var(--font-13);gap:var(--space-8);padding:var(--space-8) var(--space-12);text-align:left;width:100%}.rm-view__menu-item:hover{background:var(--bg-hover);color:var(--color-secondary)}.rm-view__menu-item--danger{color:var(--color-danger)}.rm-view__menu-item--danger:hover{background:var(--color-danger-bg);color:var(--color-danger)}.rm-view__desc{color:var(--text-secondary);font-size:var(--font-13);margin:var(--space-8) 0 var(--space-12)}.rm-view__config{background:var(--bg-surface-2);border:1px solid var(--border-soft);border-radius:var(--radius-md);display:flex;flex-direction:column;gap:var(--space-6);margin:10px 0 var(--space-12);padding:var(--space-10) 14px}.rm-view__row{align-items:flex-start;display:flex;flex-wrap:wrap;font-size:var(--font-12);gap:14px}.rm-view__row-key{color:var(--text-muted);display:inline-flex;flex-shrink:0;font-size:var(--font-10);font-weight:var(--weight-semibold);letter-spacing:.04em;min-width:96px;padding-top:var(--space-4);text-transform:uppercase}.rm-view__row-key,.rm-view__row-val{align-items:center;gap:var(--space-6)}.rm-view__row-val{color:var(--text-primary);display:flex;flex:1 1 200px;flex-wrap:wrap;padding:var(--space-2) 0}.rm-view__pill{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md);display:inline-flex;font-size:var(--font-12);gap:var(--space-4);padding:5px var(--space-10)}.rm-view__pill-k{color:var(--color-primary);font-weight:var(--weight-semibold)}.rm-view__pill-tip{min-width:0}.rm-view__pill-v{display:-webkit-box;-webkit-box-orient:vertical;color:var(--text-primary);font-weight:var(--weight-medium);-webkit-line-clamp:3;overflow:hidden}.rm-view__foot{border-top:1px solid var(--border-soft);color:var(--text-muted);display:flex;flex-wrap:wrap;font-size:var(--font-11);gap:var(--space-16);padding-top:var(--space-10)}.rm-view__foot span{align-items:center;display:inline-flex;gap:var(--space-4)}.rm-view__confirm{align-items:center;background:var(--color-danger-bg);border:1px solid var(--color-danger);border-radius:var(--radius-md);color:var(--color-danger);display:flex;flex-wrap:wrap;font-size:var(--font-13);gap:var(--space-12);margin-top:var(--space-12);padding:var(--space-8) var(--space-12)}.rm-view__confirm>span{align-items:center;display:inline-flex;flex:1 1 220px;gap:var(--space-8)}.rm-view__confirm-actions{display:flex;flex-shrink:0;gap:var(--space-8)}
431
441
  </style>
@@ -49,6 +49,12 @@ const props = withDefaults(
49
49
  viewsLabel?: string
50
50
  /** L'ecran ne correspond plus a la vue active. */
51
51
  dirty?: boolean
52
+ /**
53
+ * La vue n'est pas enregistree (posee par un lien, un template, une
54
+ * selection). « modifiee » laissait croire a une vue sauvegardee qu'on
55
+ * aurait retouchee : la pastille dit alors « non sauvegardee ».
56
+ */
57
+ temporary?: boolean
52
58
  /**
53
59
  * Actions de bas de menu. La page y met ce qu'elle sait faire d'une vue :
54
60
  * les ouvrir toutes, en enregistrer une nouvelle, modifier son perimetre.
@@ -61,6 +67,7 @@ const props = withDefaults(
61
67
  views: () => [],
62
68
  viewsLabel: undefined,
63
69
  dirty: false,
70
+ temporary: false,
64
71
  actions: () => [],
65
72
  lang: 'fr',
66
73
  },
@@ -83,6 +90,7 @@ const WORDS = {
83
90
  actions: 'Actions',
84
91
  unsaved: 'Modifications non enregistrées',
85
92
  modified: 'modifiée',
93
+ temporary: 'non sauvegardée',
86
94
  applied: 'Vue appliquée',
87
95
  save: 'Sauvegarder',
88
96
  aria: 'Vue affichée',
@@ -94,6 +102,7 @@ const WORDS = {
94
102
  actions: 'Actions',
95
103
  unsaved: 'Unsaved changes',
96
104
  modified: 'edited',
105
+ temporary: 'unsaved',
97
106
  applied: 'View applied',
98
107
  save: 'Save',
99
108
  aria: 'Current view',
@@ -108,7 +117,8 @@ const t = computed(() => WORDS[props.lang])
108
117
  */
109
118
  const label = computed(() => {
110
119
  if (!props.viewName) return t.value.none
111
- return props.dirty ? `${props.viewName} (${t.value.modified})` : props.viewName
120
+ if (!props.dirty) return props.viewName
121
+ return `${props.viewName} (${props.temporary ? t.value.temporary : t.value.modified})`
112
122
  })
113
123
 
114
124
  const open = ref(false)
@@ -11,9 +11,12 @@ const FOCUSABLE = [
11
11
  function focusables(root) {
12
12
  return Array.from(root.querySelectorAll(FOCUSABLE)).filter((el) => el.offsetWidth > 0 || el.offsetHeight > 0 || el === document.activeElement);
13
13
  }
14
+ const stack = [];
14
15
  export function useRmDialog({ open, surface, close }) {
15
16
  let opener = null;
17
+ const token = Symbol("rm-dialog");
16
18
  function onKeydown(event) {
19
+ if (stack[stack.length - 1] !== token) return;
17
20
  if (event.key === "Escape") {
18
21
  event.stopPropagation();
19
22
  if (rmPanelsDismiss()) return;
@@ -41,9 +44,12 @@ export function useRmDialog({ open, surface, close }) {
41
44
  }
42
45
  }
43
46
  function bind() {
47
+ if (!stack.includes(token)) stack.push(token);
44
48
  document.addEventListener("keydown", onKeydown, true);
45
49
  }
46
50
  function unbind() {
51
+ const at = stack.indexOf(token);
52
+ if (at >= 0) stack.splice(at, 1);
47
53
  document.removeEventListener("keydown", onKeydown, true);
48
54
  }
49
55
  watch(open, async (isOpen) => {
@@ -133,6 +133,27 @@ export interface RmAlertViewOption {
133
133
  * nomme des marques et vaut pour tout le hub, sans dependre d'aucune page.
134
134
  */
135
135
  export type RmAlertKind = 'view' | 'product';
136
+ /**
137
+ * Textes du genre `product` de RmAlertForm, quand l'app y suit autre chose que
138
+ * des marques. `{max}` est remplace par le plafond d'elements.
139
+ */
140
+ export interface RmAlertProductTexts {
141
+ /** Libelle du choix dans le selecteur de genre. */
142
+ kindProduct: string;
143
+ /** Explication sous le selecteur de genre. */
144
+ kindProductHint: string;
145
+ /** Legende de la section de choix, et prefixe en modification. */
146
+ brands: string;
147
+ /** Intitule du champ de recherche. */
148
+ brandsSearch: string;
149
+ brandsHint: string;
150
+ brandsRequired: string;
151
+ brandsTooMany: string;
152
+ /** Ce que l'alerte signale, sous la section. */
153
+ productFields: string;
154
+ /** Debut du titre propose (« Veille Keytruda »). */
155
+ productTitle: string;
156
+ }
136
157
  /** Ce que le formulaire de creation rend a l'appelant. */
137
158
  export interface RmAlertDraft {
138
159
  title: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.246",
3
+ "version": "0.1.247",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",