@tekkare/romulus 0.1.149 → 0.1.151

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.148",
7
+ "version": "0.1.150",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -396,6 +396,10 @@ function submit() {
396
396
  </script>
397
397
 
398
398
  <template>
399
+ <!-- Chaque section porte un `data-field` : les sections partagent la meme
400
+ classe, et une visite guidee (cf. RmTour) doit pouvoir designer
401
+ « Declencheurs » sans compter les fieldsets, dont le nombre change
402
+ avec le genre d'alerte. -->
399
403
  <form class="rm-alert-form" @submit.prevent="submit">
400
404
  <RmInput
401
405
  :model-value="title"
@@ -407,7 +411,7 @@ function submit() {
407
411
  "
408
412
  />
409
413
 
410
- <fieldset v-if="!editing && canWatchView" class="rm-alert-form__group">
414
+ <fieldset v-if="!editing && canWatchView" class="rm-alert-form__group" data-field="kind">
411
415
  <legend class="rm-alert-form__label">{{ T.kind }}</legend>
412
416
  <p class="rm-alert-form__hint">
413
417
  {{
@@ -427,7 +431,7 @@ function submit() {
427
431
  />
428
432
  </fieldset>
429
433
 
430
- <fieldset v-if="watchesProduct" class="rm-alert-form__group">
434
+ <fieldset v-if="watchesProduct" class="rm-alert-form__group" data-field="brands">
431
435
  <legend class="rm-alert-form__label">{{ T.brands }}</legend>
432
436
  <p class="rm-alert-form__hint">{{ T.brandsHint }}</p>
433
437
  <!-- L'intitule du filtre dit l'action, pas le titre de la section :
@@ -457,7 +461,7 @@ function submit() {
457
461
  </p>
458
462
  </fieldset>
459
463
 
460
- <div v-else-if="!editing" class="rm-alert-form__view">
464
+ <div v-else-if="!editing" class="rm-alert-form__view" data-field="view">
461
465
  <p class="rm-alert-form__label">{{ T.view }}</p>
462
466
  <p class="rm-alert-form__hint">
463
467
  <!-- L'avertissement sur l'application ne vaut que pour une vue deja
@@ -491,7 +495,7 @@ function submit() {
491
495
  {{ watchesProduct ? `${T.brands} : ${brands.join(', ')}` : `${T.view} : ${viewTitle}` }}
492
496
  </p>
493
497
 
494
- <fieldset class="rm-alert-form__group">
498
+ <fieldset class="rm-alert-form__group" data-field="triggers">
495
499
  <legend class="rm-alert-form__label">{{ T.triggers }}</legend>
496
500
  <p class="rm-alert-form__hint">{{ T.triggersHint }}</p>
497
501
  <RmCheckbox
@@ -512,7 +516,7 @@ function submit() {
512
516
  {{ T.productFields }}
513
517
  </p>
514
518
 
515
- <fieldset v-if="watchesChanges && !watchesProduct" class="rm-alert-form__group">
519
+ <fieldset v-if="watchesChanges && !watchesProduct" class="rm-alert-form__group" data-field="fields">
516
520
  <legend class="rm-alert-form__label">{{ T.fields }}</legend>
517
521
  <p class="rm-alert-form__hint">{{ anyField ? T.anyFieldHint : T.fieldsHint }}</p>
518
522
  <template v-if="fields.length">
@@ -530,7 +534,7 @@ function submit() {
530
534
  <p v-else class="rm-alert-form__hint">{{ T.noFields }}</p>
531
535
  </fieldset>
532
536
 
533
- <fieldset class="rm-alert-form__group">
537
+ <fieldset class="rm-alert-form__group" data-field="channels">
534
538
  <legend class="rm-alert-form__label">{{ T.channels }}</legend>
535
539
  <RmCheckbox :model-value="true" :label="T.inApp" disabled />
536
540
 
@@ -82,13 +82,28 @@ const GAP = 14
82
82
  /** Marge minimale entre la bulle et le bord de la fenetre. */
83
83
  const EDGE = 16
84
84
 
85
+ /**
86
+ * Le noeud vise. Une etape peut donner plusieurs selecteurs separes par une
87
+ * virgule : ils sont essayes DANS L'ORDRE ECRIT, du plus precis au repli.
88
+ * `document.querySelector` avec la meme liste rendrait le premier noeud dans
89
+ * l'ordre du document, donc souvent le repli — un panneau plutot que la liste
90
+ * qu'il contient.
91
+ */
92
+ function findTarget(selector: string): HTMLElement | null {
93
+ for (const part of selector.split(',')) {
94
+ const node = document.querySelector(part.trim()) as HTMLElement | null
95
+ if (node) return node
96
+ }
97
+ return null
98
+ }
99
+
85
100
  function measure() {
86
101
  const selector = current.value?.target
87
102
  if (!selector) {
88
103
  spot.value = null
89
104
  return
90
105
  }
91
- const node = document.querySelector(selector) as HTMLElement | null
106
+ const node = findTarget(selector)
92
107
  if (!node) {
93
108
  spot.value = null
94
109
  return
@@ -137,15 +152,44 @@ function stop() {
137
152
  frame = null
138
153
  }
139
154
 
140
- /** La cible est amenee a l'ecran au changement d'etape, jamais apres. */
155
+ /**
156
+ * La cible est amenee a l'ecran au changement d'etape, jamais apres.
157
+ *
158
+ * Le defilement est VERTICAL seulement, et fait a la main plutot que par
159
+ * `scrollIntoView`. Une cible du cadre (un panneau qui glisse encore, une
160
+ * tuile du rail) est brievement hors champ a droite, et `scrollIntoView` y
161
+ * repond en poussant la page de cote : le cadre se decalait, et le detourage
162
+ * pointait a cote pour tout le reste de la visite.
163
+ */
141
164
  function reveal() {
142
165
  const selector = current.value?.target
143
166
  if (!selector) return
144
- const node = document.querySelector(selector) as HTMLElement | null
167
+ const node = findTarget(selector)
145
168
  if (!node) return
169
+
170
+ const rect = node.getBoundingClientRect()
171
+ const vh = window.innerHeight
172
+ // Deja lisible : ne rien bouger. Une cible plus haute que la fenetre compte
173
+ // comme lue des que son debut est visible.
174
+ if (rect.top >= 0 && (rect.bottom <= vh || rect.height >= vh)) return
175
+
146
176
  const still = document.body.classList.contains('reduce-motion')
147
177
  || window.matchMedia('(prefers-reduced-motion: reduce)').matches
148
- node.scrollIntoView({ block: 'center', inline: 'nearest', behavior: still ? 'auto' : 'smooth' })
178
+ const delta = rect.top - Math.max((vh - rect.height) / 2, 0)
179
+
180
+ // Le conteneur qui defile : le corps d'un panneau ou d'une modale, sinon la
181
+ // fenetre. Sans cette recherche, une etape qui designe le bas d'un
182
+ // formulaire long ferait defiler la page derriere la modale.
183
+ let parent: HTMLElement | null = node.parentElement
184
+ while (parent) {
185
+ const style = getComputedStyle(parent)
186
+ const scrolls = /(auto|scroll)/.test(style.overflowY)
187
+ if (scrolls && parent.scrollHeight > parent.clientHeight + 1) break
188
+ parent = parent.parentElement
189
+ }
190
+
191
+ if (parent) parent.scrollBy({ top: delta, behavior: still ? 'auto' : 'smooth' })
192
+ else window.scrollBy({ top: delta, behavior: still ? 'auto' : 'smooth' })
149
193
  }
150
194
 
151
195
  function onKeydown(event: KeyboardEvent) {
@@ -323,14 +367,14 @@ function rich(text: string) {
323
367
  <template>
324
368
  <Teleport to="body">
325
369
  <div v-if="open && current" class="rm-tour" role="dialog" aria-modal="true" :aria-label="title">
326
- <!-- Le voile prend les clics : pendant une visite, la page est ce qu'on
327
- regarde, pas ce qu'on manipule. Un clic dessus ne referme rien —
328
- quitter est une decision, elle passe par « Quitter » ou Echap.
329
-
330
- Il est decoupe en quatre bandes autour de la cible, qui reste donc
331
- visible et nette dans le trou, sans qu'on ait a deplacer son noeud
332
- ni a lui poser un z-index. Une seule bande plein ecran quand
333
- l'etape ne vise rien. -->
370
+ <!-- Le voile est decoupe en quatre bandes autour de la cible, qui reste
371
+ donc visible et nette dans le trou, sans qu'on ait a deplacer son
372
+ noeud ni a lui poser un z-index. Une seule bande plein ecran quand
373
+ l'etape ne vise rien.
374
+
375
+ Il laisse passer les clics : la visite explique des gestes, elle ne
376
+ doit pas empecher de les faire. Quitter passe par « Quitter » ou
377
+ Echap, pas par un clic dans le vide. -->
334
378
  <div
335
379
  v-for="(part, index) in veil"
336
380
  :key="index"
@@ -394,5 +438,5 @@ function rich(text: string) {
394
438
  </template>
395
439
 
396
440
  <style scoped>
397
- .rm-tour{inset:0;position:fixed;z-index:900}.rm-tour__veil{background:rgba(15,23,42,.55);position:fixed}.rm-tour__spot{border:2px solid var(--color-secondary);border-radius:var(--radius-lg);pointer-events:none;position:fixed;transition:all var(--duration-fast) var(--ease-out)}.rm-tour__card{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-2xl);box-shadow:var(--shadow-lg);flex-direction:column;max-width:calc(100vw - 32px);outline:none;padding:var(--space-16);position:fixed;width:340px}.rm-tour__card,.rm-tour__head{display:flex;gap:var(--space-8)}.rm-tour__head{align-items:center;justify-content:space-between}.rm-tour__eyebrow{color:var(--color-secondary);font-size:var(--font-10);font-weight:var(--weight-semibold);letter-spacing:.08em;text-transform:uppercase}.rm-tour__close{align-items:center;background:none;border:none;border-radius:var(--radius-md);color:var(--text-muted);cursor:pointer;display:flex;height:24px;justify-content:center;margin-left:auto;padding:0;width:24px}.rm-tour__close:hover{background:var(--bg-surface-3);color:var(--text-primary)}.rm-tour__close svg{height:14px;width:14px}.rm-tour__title{color:var(--text-primary);font-size:var(--font-16);font-weight:var(--weight-semibold);line-height:1.3}.rm-tour__text{color:var(--text-secondary);font-size:var(--font-13);line-height:1.55}.rm-tour__list{color:var(--text-secondary);display:flex;flex-direction:column;font-size:var(--font-12);gap:var(--space-4);line-height:1.5;margin:0;padding-left:var(--space-16)}.rm-tour__foot{align-items:center;border-top:1px solid var(--border-default);display:flex;gap:var(--space-8);margin-top:var(--space-4);padding-top:var(--space-12)}.rm-tour__count{color:var(--text-muted);font-size:var(--font-11);font-variant-numeric:tabular-nums}.rm-tour__dots{display:flex;gap:4px}.rm-tour__dot{background:var(--border-default);border-radius:var(--radius-pill);height:5px;width:5px}.rm-tour__dot--done{background:var(--color-violet-pale)}.rm-tour__dot--on{background:var(--color-secondary);width:14px}.rm-tour__actions{display:flex;gap:var(--space-6);margin-left:auto}.rm-tour__btn{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md);color:var(--text-secondary);cursor:pointer;font-family:inherit;font-size:var(--font-12);padding:5px var(--space-12)}.rm-tour__btn:hover{background:var(--bg-surface-3)}.rm-tour__btn--primary{background:var(--color-secondary);border-color:var(--color-secondary);color:#fff;font-weight:var(--weight-semibold)}.rm-tour__btn--primary:hover{background:var(--color-secondary);filter:brightness(1.05)}
441
+ .rm-tour{inset:0;position:fixed;z-index:900}.rm-tour__veil{backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);background:rgba(15,23,42,.45)}.rm-tour__spot,.rm-tour__veil{pointer-events:none;position:fixed}.rm-tour__spot{border:2px solid var(--color-secondary);border-radius:var(--radius-lg);transition:all var(--duration-fast) var(--ease-out)}.rm-tour__card{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-2xl);box-shadow:var(--shadow-lg);flex-direction:column;max-width:calc(100vw - 32px);outline:none;padding:var(--space-16);position:fixed;width:340px}.rm-tour__card,.rm-tour__head{display:flex;gap:var(--space-8)}.rm-tour__head{align-items:center;justify-content:space-between}.rm-tour__eyebrow{color:var(--color-secondary);font-size:var(--font-10);font-weight:var(--weight-semibold);letter-spacing:.08em;text-transform:uppercase}.rm-tour__close{align-items:center;background:none;border:none;border-radius:var(--radius-md);color:var(--text-muted);cursor:pointer;display:flex;height:24px;justify-content:center;margin-left:auto;padding:0;width:24px}.rm-tour__close:hover{background:var(--bg-surface-3);color:var(--text-primary)}.rm-tour__close svg{height:14px;width:14px}.rm-tour__title{color:var(--text-primary);font-size:var(--font-16);font-weight:var(--weight-semibold);line-height:1.3}.rm-tour__text{color:var(--text-secondary);font-size:var(--font-13);line-height:1.55}.rm-tour__list{color:var(--text-secondary);display:flex;flex-direction:column;font-size:var(--font-12);gap:var(--space-4);line-height:1.5;margin:0;padding-left:var(--space-16)}.rm-tour__foot{align-items:center;border-top:1px solid var(--border-default);display:flex;gap:var(--space-8);margin-top:var(--space-4);padding-top:var(--space-12)}.rm-tour__count{color:var(--text-muted);font-size:var(--font-11);font-variant-numeric:tabular-nums}.rm-tour__dots{display:flex;gap:4px}.rm-tour__dot{background:var(--border-default);border-radius:var(--radius-pill);height:5px;width:5px}.rm-tour__dot--done{background:var(--color-violet-pale)}.rm-tour__dot--on{background:var(--color-secondary);width:14px}.rm-tour__actions{display:flex;gap:var(--space-6);margin-left:auto}.rm-tour__btn{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md);color:var(--text-secondary);cursor:pointer;font-family:inherit;font-size:var(--font-12);padding:5px var(--space-12)}.rm-tour__btn:hover{background:var(--bg-surface-3)}.rm-tour__btn--primary{background:var(--color-secondary);border-color:var(--color-secondary);color:#fff;font-weight:var(--weight-semibold)}.rm-tour__btn--primary:hover{background:var(--color-secondary);filter:brightness(1.05)}
398
442
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.149",
3
+ "version": "0.1.151",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",