srcdev-nuxt-components 9.1.41 → 9.1.42

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.
@@ -0,0 +1,139 @@
1
+ ---
2
+ name: SamaritanPromptMixed
3
+ description: SamaritanPromptMixed animated text prompt — props, MessageConfig API, typewriter/word-pulse effects, accessibility (aria-live), CSS tokens, consumer styling
4
+ type: reference
5
+ ---
6
+
7
+ # SamaritanPromptMixed
8
+
9
+ ## Overview
10
+
11
+ `SamaritanPromptMixed` cycles through a list of messages using animated text effects. Each message can independently use either a `typewriter` effect (characters typed and deleted one-by-one) or a `word-pulse` effect (full text fades in, holds, then fades out). Effects and timing can be set globally via props and overridden per-message via `MessageConfig`.
12
+
13
+ The component uses `useCancellableTimer` for all async timing — the loop exits cleanly on `onUnmounted` without dangling promises.
14
+
15
+ ### Accessibility
16
+
17
+ - The visual animated content (`.samaritan-prompt__content`) carries `aria-hidden="true"` — screen readers never hear incremental characters.
18
+ - A visually hidden `aria-live="polite"` `aria-atomic="true"` span announces the complete message at the moment it is fully displayed:
19
+ - **Typewriter**: announced after all characters are typed (before the hold/delete phase).
20
+ - **Word-pulse**: announced when `textOpacity` reaches `1` (fade-in complete).
21
+ - `announcedText` is cleared to `""` before each deletion/fade-out, so the same message announced again in the next cycle fires a new announcement (value changes `"" → text`).
22
+ - `prefers-reduced-motion: reduce` disables the content fade transition and the cursor pulse animation via a CSS media query.
23
+
24
+ ## Props
25
+
26
+ | Prop | Type | Default | Description |
27
+ |------|------|---------|-------------|
28
+ | `messageConfigs` | `MessageConfig[]` | — | Array of message objects. **Required.** |
29
+ | `effect` | `"typewriter" \| "word-pulse"` | `"typewriter"` | Default effect for all messages. |
30
+ | `typeSpeed` | `number` | `80` | ms per character typed. |
31
+ | `deleteSpeed` | `number` | `40` | ms per character deleted. |
32
+ | `holdDuration` | `number` | `7000` | ms to hold the fully typed text before deleting. |
33
+ | `pauseDuration` | `number` | `1000` | ms pause after a message completes, before the next begins. |
34
+ | `wordDuration` | `number` | `1200` | ms the word-pulse text stays fully visible. |
35
+ | `fadeDuration` | `number` | `400` | ms for the word-pulse fade-in / fade-out CSS transition. |
36
+ | `introDelay` | `number` | `2000` | ms delay before the first message starts on mount. Set to `0` to start immediately. |
37
+ | `hideCursorInCycle` | `boolean` | `true` | Hide the cursor during text animation; show it during pause/hold. Per-message override available. |
38
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
39
+
40
+ ## MessageConfig
41
+
42
+ Each entry in `messageConfigs` accepts:
43
+
44
+ ```typescript
45
+ interface MessageConfig {
46
+ text: string;
47
+ effect?: "typewriter" | "word-pulse"; // overrides global prop
48
+ typeSpeed?: number;
49
+ deleteSpeed?: number;
50
+ holdDuration?: number;
51
+ pauseDuration?: number;
52
+ wordDuration?: number;
53
+ fadeDuration?: number;
54
+ hideCursorInCycle?: boolean;
55
+ }
56
+ ```
57
+
58
+ Any field omitted falls back to the corresponding component prop.
59
+
60
+ ## Basic usage
61
+
62
+ ```vue
63
+ <SamaritanPromptMixed
64
+ :message-configs="[
65
+ { text: 'INITIALISING SYSTEM' },
66
+ { text: 'LOADING PROFILE' },
67
+ { text: 'ACCESS GRANTED' },
68
+ ]"
69
+ />
70
+ ```
71
+
72
+ ## Mixed effects
73
+
74
+ ```vue
75
+ <SamaritanPromptMixed
76
+ :message-configs="[
77
+ { text: 'INITIALISING SYSTEM', effect: 'typewriter', hold-duration: 3000 },
78
+ { text: 'STAND BY', effect: 'word-pulse', word-duration: 2000 },
79
+ { text: 'CONNECTED', effect: 'typewriter' },
80
+ ]"
81
+ />
82
+ ```
83
+
84
+ ## Fast intro, no delay
85
+
86
+ ```vue
87
+ <SamaritanPromptMixed
88
+ :intro-delay="0"
89
+ :type-speed="50"
90
+ :message-configs="[{ text: 'READY' }]"
91
+ />
92
+ ```
93
+
94
+ ## CSS custom properties
95
+
96
+ | Token | Default | Description |
97
+ |-------|---------|-------------|
98
+ | `--samaritan-font-size` | `2rem` | Text size. |
99
+ | `--samaritan-color-text` | `#ffffff` | Text colour. |
100
+ | `--samaritan-color-underline` | `#ffffff` | Underline bar colour. |
101
+ | `--samaritan-color-cursor` | `#cc0000` | Cursor colour (peak of pulse). |
102
+ | `--samaritan-color-cursor-off` | `transparent` | Cursor colour (trough of pulse). |
103
+ | `--samaritan-font-family` | `"Mono MMM 5", "Nova Mono", "Courier New", monospace` | Font stack. |
104
+ | `--samaritan-letter-spacing` | `0.08em` | Letter spacing. |
105
+
106
+ The custom font `Mono MMM 5` is loaded via `@font-face` inside the component's unscoped `<style>` block from `/fonts/monoMMM_5.ttf`.
107
+
108
+ ## Consumer styling
109
+
110
+ Use an unscoped style block scoped by a page or section wrapper. No `:deep()` needed.
111
+
112
+ ```vue
113
+ <style>
114
+ .my-page .samaritan-prompt {
115
+ --samaritan-font-size: 3.2rem;
116
+ --samaritan-color-text: #00ff88;
117
+ --samaritan-color-cursor: #ff4400;
118
+ }
119
+ </style>
120
+ ```
121
+
122
+ ## CSS classes
123
+
124
+ | Class | Notes |
125
+ |-------|-------|
126
+ | `.samaritan-prompt` | Root element. |
127
+ | `.samaritan-prompt__content` | Wraps text + underline. `aria-hidden="true"`. Opacity transitions on word-pulse effect. |
128
+ | `.samaritan-prompt__stage` | Centers the text span; min-height prevents layout shift. |
129
+ | `.samaritan-prompt__text` | The visible animated text. `text-transform: uppercase`. |
130
+ | `.samaritan-prompt__underline` | Horizontal rule below text. |
131
+ | `.samaritan-prompt__cursor` | `▲` glyph; pulses via `samaritan-pulse` keyframe. `aria-hidden="true"`. |
132
+ | `.samaritan-prompt__sr-text` | Visually hidden `aria-live` region for screen reader announcements. |
133
+
134
+ ## Notes
135
+
136
+ - The `introDelay` only applies at the start of the loop, not between messages. Between messages, `pauseDuration` applies.
137
+ - The cursor pulse animation uses the `samaritan-pulse` keyframe (defined in the component's global style). The cursor hides/shows during cycles via `cursorVisible` + opacity transition, not `display`.
138
+ - `prefers-reduced-motion: reduce` suppresses the opacity transition on `.samaritan-prompt__content` and the `animation` on `.samaritan-prompt__cursor`. The `aria-live` announcements continue to fire regardless.
139
+ - The `MessageConfig` type is exported from the component: `import type { MessageConfig } from 'srcdev-nuxt-components/components/02.molecules/samaritan-prompt/SamaritanPromptMixed.vue'`.
@@ -77,7 +77,8 @@ Each skill is a single markdown file named `<area>-<task>.md`.
77
77
  ├── capture-qr-code.md — CaptureQrCode: live camera scanner, error state, visibility/route/KeepAlive lifecycle, media stream cleanup
78
78
  ├── decode-qr-code.md — DecodeQrCode: file picker + drag-and-drop image decoder, shared results list, CSS override points
79
79
  ├── data-grid.md — DataGrid: auto-fit responsive grid, $slots iteration, --data-grid-columns/gap tokens, semantic tag + aria
80
- └── carousel-flip.md — CarouselFlip: FLIP-animated carousel, carouselDataIds slot API, buttonLayout variants (sides/controls-flanking/controls-grouped-right/overlay), CSS tokens
80
+ ├── carousel-flip.md — CarouselFlip: FLIP-animated carousel, carouselDataIds slot API, buttonLayout variants (sides/controls-flanking/controls-grouped-right/overlay), CSS tokens
81
+ └── samaritan-prompt-mixed.md — SamaritanPromptMixed: animated text prompt, typewriter/word-pulse effects, MessageConfig API, aria-live accessibility, CSS tokens
81
82
  ```
82
83
 
83
84
  ## Skill file template
@@ -1,12 +1,13 @@
1
1
  <template>
2
2
  <div :class="['samaritan-prompt', elementClasses]">
3
- <div class="samaritan-prompt__content" :style="{ opacity: textOpacity }">
3
+ <div class="samaritan-prompt__content" :style="{ opacity: textOpacity }" aria-hidden="true">
4
4
  <div class="samaritan-prompt__stage">
5
5
  <span class="samaritan-prompt__text">{{ displayText }}</span>
6
6
  </div>
7
7
  <div class="samaritan-prompt__underline"></div>
8
8
  </div>
9
9
  <span class="samaritan-prompt__cursor" :style="{ opacity: cursorOpacity }" aria-hidden="true">▲</span>
10
+ <span class="samaritan-prompt__sr-text" aria-live="polite" aria-atomic="true">{{ announcedText }}</span>
10
11
  </div>
11
12
  </template>
12
13
 
@@ -53,6 +54,7 @@ const props = withDefaults(defineProps<Props>(), {
53
54
  const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
54
55
 
55
56
  const displayText = ref("");
57
+ const announcedText = ref("");
56
58
  const textOpacity = ref(1);
57
59
  const cursorVisible = ref(true);
58
60
  const activeFadeDuration = ref(props.fadeDuration);
@@ -85,7 +87,9 @@ const runTypewriter = async (config: ResolvedConfig) => {
85
87
  await wait(typeSpeed);
86
88
  }
87
89
 
90
+ announcedText.value = text;
88
91
  await wait(holdDuration);
92
+ announcedText.value = "";
89
93
 
90
94
  while (displayText.value.length > 0) {
91
95
  displayText.value = displayText.value.slice(0, -1);
@@ -112,8 +116,10 @@ const runWordPulse = async (config: ResolvedConfig) => {
112
116
  await wait(120);
113
117
 
114
118
  textOpacity.value = 1;
119
+ announcedText.value = text;
115
120
  await wait(wordDuration);
116
121
 
122
+ announcedText.value = "";
117
123
  textOpacity.value = 0;
118
124
  await wait(fadeDuration);
119
125
 
@@ -221,6 +227,30 @@ onUnmounted(stop);
221
227
  animation: samaritan-pulse 2.5s ease-in-out infinite;
222
228
  transition: opacity 400ms ease;
223
229
  }
230
+
231
+ .samaritan-prompt__sr-text {
232
+ position: absolute;
233
+ width: 1px;
234
+ height: 1px;
235
+ padding: 0;
236
+ margin: -1px;
237
+ overflow: hidden;
238
+ clip: rect(0, 0, 0, 0);
239
+ white-space: nowrap;
240
+ border: 0;
241
+ }
242
+ }
243
+
244
+ @media (prefers-reduced-motion: reduce) {
245
+ .samaritan-prompt {
246
+ .samaritan-prompt__content {
247
+ transition: none;
248
+ }
249
+
250
+ .samaritan-prompt__cursor {
251
+ animation: none;
252
+ }
253
+ }
224
254
  }
225
255
 
226
256
  @keyframes samaritan-pulse {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "9.1.41",
4
+ "version": "9.1.42",
5
5
  "main": "nuxt.config.ts",
6
6
  "types": "types.d.ts",
7
7
  "license": "MIT",