@muxima-ui/core 1.3.0 → 1.4.1
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 +1 -0
- package/fesm2022/muxima-ui-core-tour.mjs +536 -0
- package/fesm2022/muxima-ui-core-tour.mjs.map +1 -0
- package/fesm2022/muxima-ui-core.mjs +1 -1
- package/fesm2022/muxima-ui-core.mjs.map +1 -1
- package/package.json +6 -2
- package/styles/base.css +1 -0
- package/styles/components/data-grid.css +1 -0
- package/styles/components/tour.css +155 -0
- package/styles/muxima.css +1 -0
- package/types/muxima-ui-core-tour.d.ts +163 -0
- package/types/muxima-ui-core.d.ts +1 -1
package/README.md
CHANGED
|
@@ -63,6 +63,7 @@ Cada componente tem o seu ponto de entrada, para só importares o que usas:
|
|
|
63
63
|
| `@muxima-ui/core/menu` | `MX_MENU` |
|
|
64
64
|
| `@muxima-ui/core/popover` | `MX_POPOVER` |
|
|
65
65
|
| `@muxima-ui/core/tooltip` | `MxTooltip` |
|
|
66
|
+
| `@muxima-ui/core/tour` | `MxTour`, `MxTourAnchor` (visita guiada) |
|
|
66
67
|
| `@muxima-ui/core/alert` | `MX_ALERT` |
|
|
67
68
|
| `@muxima-ui/core/progress` | `MxProgress`, `MxSpinner` |
|
|
68
69
|
| `@muxima-ui/core/skeleton` | `MxSkeleton` |
|
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { signal, InjectionToken, inject, viewChild, NgZone, ChangeDetectorRef, DestroyRef, effect, untracked, afterNextRender, ViewEncapsulation, ChangeDetectionStrategy, Component, Injector, computed, ElementRef, Injectable, input, Directive } from '@angular/core';
|
|
3
|
+
import { NgTemplateOutlet } from '@angular/common';
|
|
4
|
+
import { CdkTrapFocus } from '@angular/cdk/a11y';
|
|
5
|
+
import { createOverlayRef, createNoopScrollStrategy, createGlobalPositionStrategy } from '@angular/cdk/overlay';
|
|
6
|
+
import { ComponentPortal } from '@angular/cdk/portal';
|
|
7
|
+
import { MxButton } from '@muxima-ui/core/button';
|
|
8
|
+
import { mxUniqueId } from '@muxima-ui/core/form-field';
|
|
9
|
+
|
|
10
|
+
const MX_TOUR_LABELS = {
|
|
11
|
+
next: 'Seguinte',
|
|
12
|
+
previous: 'Anterior',
|
|
13
|
+
done: 'Concluir',
|
|
14
|
+
skip: 'Saltar',
|
|
15
|
+
close: 'Fechar a visita guiada',
|
|
16
|
+
progress: (current, total) => `Passo ${current} de ${total}`,
|
|
17
|
+
};
|
|
18
|
+
/** Referência a uma visita em curso. */
|
|
19
|
+
class MxTourRef {
|
|
20
|
+
steps;
|
|
21
|
+
onClose;
|
|
22
|
+
/** Passo actual (base 0). */
|
|
23
|
+
index = signal(0, ...(ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []));
|
|
24
|
+
/** Resolve quando a visita fecha. */
|
|
25
|
+
afterClosed;
|
|
26
|
+
labels;
|
|
27
|
+
dismissible;
|
|
28
|
+
showDots;
|
|
29
|
+
resolve;
|
|
30
|
+
done = false;
|
|
31
|
+
constructor(steps, options, onClose) {
|
|
32
|
+
this.steps = steps;
|
|
33
|
+
this.onClose = onClose;
|
|
34
|
+
this.labels = { ...MX_TOUR_LABELS, ...options.labels };
|
|
35
|
+
this.dismissible = options.dismissible ?? true;
|
|
36
|
+
this.showDots = options.showDots ?? true;
|
|
37
|
+
this.index.set(Math.min(Math.max(options.startAt ?? 0, 0), Math.max(steps.length - 1, 0)));
|
|
38
|
+
this.afterClosed = new Promise((r) => (this.resolve = r));
|
|
39
|
+
}
|
|
40
|
+
get isOpen() {
|
|
41
|
+
return !this.done;
|
|
42
|
+
}
|
|
43
|
+
get isLast() {
|
|
44
|
+
return this.index() === this.steps.length - 1;
|
|
45
|
+
}
|
|
46
|
+
next() {
|
|
47
|
+
if (this.isLast)
|
|
48
|
+
this.close(true);
|
|
49
|
+
else
|
|
50
|
+
this.goTo(this.index() + 1);
|
|
51
|
+
}
|
|
52
|
+
previous() {
|
|
53
|
+
this.goTo(this.index() - 1);
|
|
54
|
+
}
|
|
55
|
+
goTo(index) {
|
|
56
|
+
if (this.done || index < 0 || index >= this.steps.length)
|
|
57
|
+
return;
|
|
58
|
+
this.index.set(index);
|
|
59
|
+
}
|
|
60
|
+
/** Fecha a visita; `completed` diz se foi até ao fim. */
|
|
61
|
+
close(completed = false) {
|
|
62
|
+
if (this.done)
|
|
63
|
+
return;
|
|
64
|
+
this.done = true;
|
|
65
|
+
const result = { completed, index: this.index() };
|
|
66
|
+
this.onClose(result);
|
|
67
|
+
this.resolve(result);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const MX_TOUR_REF = new InjectionToken('MX_TOUR_REF');
|
|
71
|
+
/** requestAnimationFrame, ou um setTimeout onde não exista (testes, SSR). */
|
|
72
|
+
const nextFrame = (cb) => typeof requestAnimationFrame === 'function' ? requestAnimationFrame(cb) : setTimeout(cb, 16);
|
|
73
|
+
const cancelFrame = (id) => typeof cancelAnimationFrame === 'function' ? cancelAnimationFrame(id) : clearTimeout(id);
|
|
74
|
+
const GAP = 12;
|
|
75
|
+
const MARGIN = 12;
|
|
76
|
+
/** Painel da visita: o destaque, o fundo escurecido e o cartão do passo. Uso interno. */
|
|
77
|
+
class MxTourPanel {
|
|
78
|
+
ref = inject(MX_TOUR_REF);
|
|
79
|
+
tour = inject(MxTour);
|
|
80
|
+
titleId = mxUniqueId('mx-tour-title');
|
|
81
|
+
bodyId = mxUniqueId('mx-tour-body');
|
|
82
|
+
progressId = mxUniqueId('mx-tour-progress');
|
|
83
|
+
card = viewChild('card', ...(ngDevMode ? [{ debugName: "card" }] : /* istanbul ignore next */ []));
|
|
84
|
+
zone = inject(NgZone);
|
|
85
|
+
cdr = inject(ChangeDetectorRef);
|
|
86
|
+
/** Passo que está desenhado (muda depois de beforeShow). */
|
|
87
|
+
step = signal(null, ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
|
|
88
|
+
target = signal(null, ...(ngDevMode ? [{ debugName: "target" }] : /* istanbul ignore next */ []));
|
|
89
|
+
spot = signal(null, ...(ngDevMode ? [{ debugName: "spot" }] : /* istanbul ignore next */ []));
|
|
90
|
+
pos = signal({ top: 0, left: 0 }, ...(ngDevMode ? [{ debugName: "pos" }] : /* istanbul ignore next */ []));
|
|
91
|
+
placement = signal('bottom', ...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
|
|
92
|
+
ready = signal(false, ...(ngDevMode ? [{ debugName: "ready" }] : /* istanbul ignore next */ []));
|
|
93
|
+
frame = 0;
|
|
94
|
+
resizeObserver;
|
|
95
|
+
showToken = 0;
|
|
96
|
+
constructor() {
|
|
97
|
+
const destroy = inject(DestroyRef);
|
|
98
|
+
// mostra o passo sempre que o índice muda
|
|
99
|
+
effect(() => {
|
|
100
|
+
const i = this.ref.index();
|
|
101
|
+
untracked(() => void this.show(i));
|
|
102
|
+
});
|
|
103
|
+
afterNextRender(() => {
|
|
104
|
+
const schedule = () => this.schedule();
|
|
105
|
+
this.zone.runOutsideAngular(() => {
|
|
106
|
+
window.addEventListener('scroll', schedule, { capture: true, passive: true });
|
|
107
|
+
window.addEventListener('resize', schedule, { passive: true });
|
|
108
|
+
});
|
|
109
|
+
if (typeof ResizeObserver !== 'undefined')
|
|
110
|
+
this.resizeObserver = new ResizeObserver(schedule);
|
|
111
|
+
destroy.onDestroy(() => {
|
|
112
|
+
window.removeEventListener('scroll', schedule, { capture: true });
|
|
113
|
+
window.removeEventListener('resize', schedule);
|
|
114
|
+
this.resizeObserver?.disconnect();
|
|
115
|
+
cancelFrame(this.frame);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async show(index) {
|
|
120
|
+
const token = ++this.showToken;
|
|
121
|
+
const st = this.ref.steps[index];
|
|
122
|
+
if (!st)
|
|
123
|
+
return;
|
|
124
|
+
// só se esconde antes do primeiro passo (para o posicionar); depois, esconder
|
|
125
|
+
// o cartão tirava-lhe o foco a meio da visita
|
|
126
|
+
if (!this.step())
|
|
127
|
+
this.ready.set(false);
|
|
128
|
+
try {
|
|
129
|
+
await st.beforeShow?.();
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// um beforeShow que falha não deve prender a visita
|
|
133
|
+
}
|
|
134
|
+
// espera um frame para o DOM reflectir o que beforeShow mudou
|
|
135
|
+
await new Promise((r) => nextFrame(r));
|
|
136
|
+
if (token !== this.showToken || !this.ref.isOpen)
|
|
137
|
+
return;
|
|
138
|
+
// o alvo pode só aparecer depois de beforeShow (ex.: conteúdo de um separador)
|
|
139
|
+
let el = this.tour.resolve(st.target);
|
|
140
|
+
for (let i = 0; !el && st.target && i < 5; i++) {
|
|
141
|
+
await new Promise((r) => nextFrame(r));
|
|
142
|
+
if (token !== this.showToken || !this.ref.isOpen)
|
|
143
|
+
return;
|
|
144
|
+
el = this.tour.resolve(st.target);
|
|
145
|
+
}
|
|
146
|
+
this.resizeObserver?.disconnect();
|
|
147
|
+
this.target.set(el);
|
|
148
|
+
if (el) {
|
|
149
|
+
this.resizeObserver?.observe(el);
|
|
150
|
+
const r = el.getBoundingClientRect();
|
|
151
|
+
const fora = r.top < 0 || r.bottom > window.innerHeight || r.left < 0 || r.right > window.innerWidth;
|
|
152
|
+
if (fora) {
|
|
153
|
+
const reduzir = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
154
|
+
el.scrollIntoView?.({ block: 'center', inline: 'nearest', behavior: reduzir ? 'auto' : 'smooth' });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
this.step.set(st);
|
|
158
|
+
// desenha já o cartão para o medir, posicionar e dar-lhe o foco
|
|
159
|
+
this.cdr.detectChanges();
|
|
160
|
+
const card = this.card()?.nativeElement;
|
|
161
|
+
if (card)
|
|
162
|
+
this.resizeObserver?.observe(card);
|
|
163
|
+
this.update();
|
|
164
|
+
this.ready.set(true);
|
|
165
|
+
this.cdr.detectChanges();
|
|
166
|
+
card?.focus({ preventScroll: true });
|
|
167
|
+
}
|
|
168
|
+
schedule() {
|
|
169
|
+
cancelFrame(this.frame);
|
|
170
|
+
this.frame = nextFrame(() => this.zone.run(() => this.update()));
|
|
171
|
+
}
|
|
172
|
+
update() {
|
|
173
|
+
const st = this.step();
|
|
174
|
+
const el = this.target();
|
|
175
|
+
const r = el?.getBoundingClientRect();
|
|
176
|
+
if (!st || !r || (r.width === 0 && r.height === 0)) {
|
|
177
|
+
this.spot.set(null);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const pad = st.padding ?? 6;
|
|
181
|
+
const spot = { top: r.top - pad, left: r.left - pad, width: r.width + pad * 2, height: r.height + pad * 2 };
|
|
182
|
+
this.spot.set(spot);
|
|
183
|
+
const card = this.card()?.nativeElement;
|
|
184
|
+
const cw = card?.offsetWidth ?? 320;
|
|
185
|
+
const ch = card?.offsetHeight ?? 160;
|
|
186
|
+
const vw = window.innerWidth;
|
|
187
|
+
const vh = window.innerHeight;
|
|
188
|
+
const rtl = getComputedStyle(document.documentElement).direction === 'rtl';
|
|
189
|
+
const side = (p) => rtl && (p === 'start' || p === 'end') ? (p === 'start' ? 'end' : 'start') : p;
|
|
190
|
+
const fits = {
|
|
191
|
+
bottom: spot.top + spot.height + GAP + ch <= vh - MARGIN,
|
|
192
|
+
top: spot.top - GAP - ch >= MARGIN,
|
|
193
|
+
end: spot.left + spot.width + GAP + cw <= vw - MARGIN,
|
|
194
|
+
start: spot.left - GAP - cw >= MARGIN,
|
|
195
|
+
};
|
|
196
|
+
const wanted = st.placement && st.placement !== 'auto' ? side(st.placement) : null;
|
|
197
|
+
const order = wanted
|
|
198
|
+
? [wanted, 'bottom', 'top', 'end', 'start']
|
|
199
|
+
: ['bottom', 'top', 'end', 'start'];
|
|
200
|
+
const p = order.find((o) => fits[o]) ?? 'bottom';
|
|
201
|
+
let top;
|
|
202
|
+
let left;
|
|
203
|
+
if (p === 'bottom' || p === 'top') {
|
|
204
|
+
top = p === 'bottom' ? spot.top + spot.height + GAP : spot.top - GAP - ch;
|
|
205
|
+
left = spot.left + spot.width / 2 - cw / 2;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
left = p === 'end' ? spot.left + spot.width + GAP : spot.left - GAP - cw;
|
|
209
|
+
top = spot.top + spot.height / 2 - ch / 2;
|
|
210
|
+
}
|
|
211
|
+
const clamp = (v, min, max) => Math.min(Math.max(v, min), Math.max(min, max));
|
|
212
|
+
this.placement.set(rtl && (p === 'start' || p === 'end') ? (p === 'start' ? 'end' : 'start') : p);
|
|
213
|
+
this.pos.set({ top: clamp(top, MARGIN, vh - ch - MARGIN), left: clamp(left, MARGIN, vw - cw - MARGIN) });
|
|
214
|
+
}
|
|
215
|
+
onKeydown(event) {
|
|
216
|
+
const t = event.target;
|
|
217
|
+
const editavel = t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName);
|
|
218
|
+
if (event.key === 'Escape' && this.ref.dismissible) {
|
|
219
|
+
event.preventDefault();
|
|
220
|
+
event.stopPropagation();
|
|
221
|
+
this.ref.close(false);
|
|
222
|
+
}
|
|
223
|
+
else if (!editavel && event.key === 'ArrowRight' && !this.ref.isLast) {
|
|
224
|
+
event.preventDefault();
|
|
225
|
+
this.ref.next();
|
|
226
|
+
}
|
|
227
|
+
else if (!editavel && event.key === 'ArrowLeft' && this.ref.index() > 0) {
|
|
228
|
+
event.preventDefault();
|
|
229
|
+
this.ref.previous();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxTourPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
233
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: MxTourPanel, isStandalone: true, selector: "mx-tour-panel", host: { classAttribute: "mx-tour-layer" }, viewQueries: [{ propertyName: "card", first: true, predicate: ["card"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
234
|
+
@if (spot(); as s) {
|
|
235
|
+
<div
|
|
236
|
+
class="mx-tour-spotlight"
|
|
237
|
+
aria-hidden="true"
|
|
238
|
+
[style.top.px]="s.top"
|
|
239
|
+
[style.left.px]="s.left"
|
|
240
|
+
[style.width.px]="s.width"
|
|
241
|
+
[style.height.px]="s.height"
|
|
242
|
+
></div>
|
|
243
|
+
} @else {
|
|
244
|
+
<div class="mx-tour-dim" aria-hidden="true"></div>
|
|
245
|
+
}
|
|
246
|
+
@if (step(); as st) {
|
|
247
|
+
<div
|
|
248
|
+
#card
|
|
249
|
+
class="mx-tour-card"
|
|
250
|
+
role="dialog"
|
|
251
|
+
aria-modal="true"
|
|
252
|
+
tabindex="-1"
|
|
253
|
+
cdkTrapFocus
|
|
254
|
+
[attr.aria-labelledby]="titleId"
|
|
255
|
+
[attr.aria-describedby]="progressId + ' ' + bodyId"
|
|
256
|
+
[attr.data-placement]="spot() ? placement() : 'center'"
|
|
257
|
+
[style.top.px]="spot() ? pos().top : null"
|
|
258
|
+
[style.left.px]="spot() ? pos().left : null"
|
|
259
|
+
[style.visibility]="ready() ? null : 'hidden'"
|
|
260
|
+
(keydown)="onKeydown($event)"
|
|
261
|
+
>
|
|
262
|
+
<div class="mx-tour-head">
|
|
263
|
+
<p class="mx-tour-progress" [id]="progressId">
|
|
264
|
+
{{ ref.labels.progress(ref.index() + 1, ref.steps.length) }}
|
|
265
|
+
</p>
|
|
266
|
+
@if (ref.dismissible) {
|
|
267
|
+
<button
|
|
268
|
+
type="button"
|
|
269
|
+
class="mx-tour-close"
|
|
270
|
+
[attr.aria-label]="ref.labels.close"
|
|
271
|
+
(click)="ref.close(false)"
|
|
272
|
+
>
|
|
273
|
+
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
274
|
+
<path
|
|
275
|
+
d="M4 4l8 8M12 4l-8 8"
|
|
276
|
+
stroke="currentColor"
|
|
277
|
+
stroke-width="1.5"
|
|
278
|
+
stroke-linecap="round"
|
|
279
|
+
/>
|
|
280
|
+
</svg>
|
|
281
|
+
</button>
|
|
282
|
+
}
|
|
283
|
+
</div>
|
|
284
|
+
<h2 class="mx-tour-title" [id]="titleId">{{ st.title }}</h2>
|
|
285
|
+
<div class="mx-tour-body" [id]="bodyId">
|
|
286
|
+
@if (st.template) {
|
|
287
|
+
<ng-container
|
|
288
|
+
*ngTemplateOutlet="st.template; context: { $implicit: st, index: ref.index(), tour: ref }"
|
|
289
|
+
/>
|
|
290
|
+
} @else if (st.content) {
|
|
291
|
+
<p>{{ st.content }}</p>
|
|
292
|
+
}
|
|
293
|
+
</div>
|
|
294
|
+
<div class="mx-tour-foot">
|
|
295
|
+
@if (ref.showDots && ref.steps.length > 1) {
|
|
296
|
+
<span class="mx-tour-dots" aria-hidden="true">
|
|
297
|
+
@for (s of ref.steps; track $index) {
|
|
298
|
+
<span class="mx-tour-dot" [attr.data-active]="$index === ref.index() ? '' : null"></span>
|
|
299
|
+
}
|
|
300
|
+
</span>
|
|
301
|
+
}
|
|
302
|
+
<span class="mx-tour-actions">
|
|
303
|
+
@if (ref.dismissible && !ref.isLast) {
|
|
304
|
+
<button mxButton variant="ghost" size="sm" type="button" (click)="ref.close(false)">
|
|
305
|
+
{{ ref.labels.skip }}
|
|
306
|
+
</button>
|
|
307
|
+
}
|
|
308
|
+
@if (ref.index() > 0) {
|
|
309
|
+
<button mxButton variant="outline" size="sm" type="button" (click)="ref.previous()">
|
|
310
|
+
{{ ref.labels.previous }}
|
|
311
|
+
</button>
|
|
312
|
+
}
|
|
313
|
+
<button mxButton size="sm" type="button" (click)="ref.next()">
|
|
314
|
+
{{ ref.isLast ? ref.labels.done : ref.labels.next }}
|
|
315
|
+
</button>
|
|
316
|
+
</span>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
}
|
|
320
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: MxButton, selector: "button[mxButton], a[mxButton]", inputs: ["variant", "size", "icon", "loading", "disabled"], exportAs: ["mxButton"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
321
|
+
}
|
|
322
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxTourPanel, decorators: [{
|
|
323
|
+
type: Component,
|
|
324
|
+
args: [{
|
|
325
|
+
selector: 'mx-tour-panel',
|
|
326
|
+
imports: [NgTemplateOutlet, CdkTrapFocus, MxButton],
|
|
327
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
328
|
+
encapsulation: ViewEncapsulation.None,
|
|
329
|
+
host: { class: 'mx-tour-layer' },
|
|
330
|
+
template: `
|
|
331
|
+
@if (spot(); as s) {
|
|
332
|
+
<div
|
|
333
|
+
class="mx-tour-spotlight"
|
|
334
|
+
aria-hidden="true"
|
|
335
|
+
[style.top.px]="s.top"
|
|
336
|
+
[style.left.px]="s.left"
|
|
337
|
+
[style.width.px]="s.width"
|
|
338
|
+
[style.height.px]="s.height"
|
|
339
|
+
></div>
|
|
340
|
+
} @else {
|
|
341
|
+
<div class="mx-tour-dim" aria-hidden="true"></div>
|
|
342
|
+
}
|
|
343
|
+
@if (step(); as st) {
|
|
344
|
+
<div
|
|
345
|
+
#card
|
|
346
|
+
class="mx-tour-card"
|
|
347
|
+
role="dialog"
|
|
348
|
+
aria-modal="true"
|
|
349
|
+
tabindex="-1"
|
|
350
|
+
cdkTrapFocus
|
|
351
|
+
[attr.aria-labelledby]="titleId"
|
|
352
|
+
[attr.aria-describedby]="progressId + ' ' + bodyId"
|
|
353
|
+
[attr.data-placement]="spot() ? placement() : 'center'"
|
|
354
|
+
[style.top.px]="spot() ? pos().top : null"
|
|
355
|
+
[style.left.px]="spot() ? pos().left : null"
|
|
356
|
+
[style.visibility]="ready() ? null : 'hidden'"
|
|
357
|
+
(keydown)="onKeydown($event)"
|
|
358
|
+
>
|
|
359
|
+
<div class="mx-tour-head">
|
|
360
|
+
<p class="mx-tour-progress" [id]="progressId">
|
|
361
|
+
{{ ref.labels.progress(ref.index() + 1, ref.steps.length) }}
|
|
362
|
+
</p>
|
|
363
|
+
@if (ref.dismissible) {
|
|
364
|
+
<button
|
|
365
|
+
type="button"
|
|
366
|
+
class="mx-tour-close"
|
|
367
|
+
[attr.aria-label]="ref.labels.close"
|
|
368
|
+
(click)="ref.close(false)"
|
|
369
|
+
>
|
|
370
|
+
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
371
|
+
<path
|
|
372
|
+
d="M4 4l8 8M12 4l-8 8"
|
|
373
|
+
stroke="currentColor"
|
|
374
|
+
stroke-width="1.5"
|
|
375
|
+
stroke-linecap="round"
|
|
376
|
+
/>
|
|
377
|
+
</svg>
|
|
378
|
+
</button>
|
|
379
|
+
}
|
|
380
|
+
</div>
|
|
381
|
+
<h2 class="mx-tour-title" [id]="titleId">{{ st.title }}</h2>
|
|
382
|
+
<div class="mx-tour-body" [id]="bodyId">
|
|
383
|
+
@if (st.template) {
|
|
384
|
+
<ng-container
|
|
385
|
+
*ngTemplateOutlet="st.template; context: { $implicit: st, index: ref.index(), tour: ref }"
|
|
386
|
+
/>
|
|
387
|
+
} @else if (st.content) {
|
|
388
|
+
<p>{{ st.content }}</p>
|
|
389
|
+
}
|
|
390
|
+
</div>
|
|
391
|
+
<div class="mx-tour-foot">
|
|
392
|
+
@if (ref.showDots && ref.steps.length > 1) {
|
|
393
|
+
<span class="mx-tour-dots" aria-hidden="true">
|
|
394
|
+
@for (s of ref.steps; track $index) {
|
|
395
|
+
<span class="mx-tour-dot" [attr.data-active]="$index === ref.index() ? '' : null"></span>
|
|
396
|
+
}
|
|
397
|
+
</span>
|
|
398
|
+
}
|
|
399
|
+
<span class="mx-tour-actions">
|
|
400
|
+
@if (ref.dismissible && !ref.isLast) {
|
|
401
|
+
<button mxButton variant="ghost" size="sm" type="button" (click)="ref.close(false)">
|
|
402
|
+
{{ ref.labels.skip }}
|
|
403
|
+
</button>
|
|
404
|
+
}
|
|
405
|
+
@if (ref.index() > 0) {
|
|
406
|
+
<button mxButton variant="outline" size="sm" type="button" (click)="ref.previous()">
|
|
407
|
+
{{ ref.labels.previous }}
|
|
408
|
+
</button>
|
|
409
|
+
}
|
|
410
|
+
<button mxButton size="sm" type="button" (click)="ref.next()">
|
|
411
|
+
{{ ref.isLast ? ref.labels.done : ref.labels.next }}
|
|
412
|
+
</button>
|
|
413
|
+
</span>
|
|
414
|
+
</div>
|
|
415
|
+
</div>
|
|
416
|
+
}
|
|
417
|
+
`,
|
|
418
|
+
}]
|
|
419
|
+
}], ctorParameters: () => [], propDecorators: { card: [{ type: i0.ViewChild, args: ['card', { isSignal: true }] }] } });
|
|
420
|
+
/**
|
|
421
|
+
* Visita guiada: destaca elementos da página, um passo de cada vez, com um cartão
|
|
422
|
+
* que explica cada um. O cartão é um diálogo modal: o foco fica nele, Escape fecha
|
|
423
|
+
* e, no fim, o foco volta para onde estava.
|
|
424
|
+
*
|
|
425
|
+
* ```html
|
|
426
|
+
* <input mxInput mxTourAnchor="pesquisa" aria-label="Pesquisar" />
|
|
427
|
+
* <button mxButton mxTourAnchor="nova">Nova factura</button>
|
|
428
|
+
* ```
|
|
429
|
+
*
|
|
430
|
+
* ```ts
|
|
431
|
+
* private readonly tour = inject(MxTour);
|
|
432
|
+
* mostrar() {
|
|
433
|
+
* this.tour.start([
|
|
434
|
+
* { title: 'Bem-vindo', content: 'Mostramos o essencial em 2 passos.' },
|
|
435
|
+
* { target: 'pesquisa', title: 'Pesquisa', content: 'Procura clientes e facturas.' },
|
|
436
|
+
* { target: 'nova', title: 'Nova factura', content: 'Começa aqui.' },
|
|
437
|
+
* ]).afterClosed.then((r) => r.completed && guardarQueViu());
|
|
438
|
+
* }
|
|
439
|
+
* ```
|
|
440
|
+
*/
|
|
441
|
+
class MxTour {
|
|
442
|
+
injector = inject(Injector);
|
|
443
|
+
anchors = new Map();
|
|
444
|
+
overlayRef;
|
|
445
|
+
/** A visita em curso, ou null. */
|
|
446
|
+
active = signal(null, ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
|
|
447
|
+
isOpen = computed(() => !!this.active(), ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
448
|
+
/** Começa uma visita. Se já houver uma aberta, fecha-a primeiro. */
|
|
449
|
+
start(steps, options = {}) {
|
|
450
|
+
this.active()?.close(false);
|
|
451
|
+
const before = document.activeElement;
|
|
452
|
+
const ref = new MxTourRef(steps, options, () => {
|
|
453
|
+
this.overlayRef?.dispose();
|
|
454
|
+
this.overlayRef = undefined;
|
|
455
|
+
this.active.set(null);
|
|
456
|
+
if (before && before.isConnected)
|
|
457
|
+
before.focus({ preventScroll: true });
|
|
458
|
+
});
|
|
459
|
+
if (!steps.length) {
|
|
460
|
+
ref.close(true);
|
|
461
|
+
return ref;
|
|
462
|
+
}
|
|
463
|
+
this.overlayRef = createOverlayRef(this.injector, {
|
|
464
|
+
positionStrategy: createGlobalPositionStrategy(this.injector).top('0').left('0'),
|
|
465
|
+
scrollStrategy: createNoopScrollStrategy(),
|
|
466
|
+
width: '100%',
|
|
467
|
+
height: '100%',
|
|
468
|
+
panelClass: 'mx-tour-pane',
|
|
469
|
+
});
|
|
470
|
+
const injector = Injector.create({
|
|
471
|
+
providers: [{ provide: MX_TOUR_REF, useValue: ref }],
|
|
472
|
+
parent: this.injector,
|
|
473
|
+
});
|
|
474
|
+
this.overlayRef.attach(new ComponentPortal(MxTourPanel, null, injector));
|
|
475
|
+
this.active.set(ref);
|
|
476
|
+
return ref;
|
|
477
|
+
}
|
|
478
|
+
/** Regista um elemento com um nome (usado por `mxTourAnchor`). */
|
|
479
|
+
register(name, element) {
|
|
480
|
+
this.anchors.set(name, element);
|
|
481
|
+
}
|
|
482
|
+
unregister(name, element) {
|
|
483
|
+
if (this.anchors.get(name) === element)
|
|
484
|
+
this.anchors.delete(name);
|
|
485
|
+
}
|
|
486
|
+
/** Encontra o elemento de um alvo: nome de âncora, selector CSS, elemento ou ElementRef. */
|
|
487
|
+
resolve(target) {
|
|
488
|
+
if (!target)
|
|
489
|
+
return null;
|
|
490
|
+
if (target instanceof ElementRef)
|
|
491
|
+
return target.nativeElement;
|
|
492
|
+
if (typeof target !== 'string')
|
|
493
|
+
return target;
|
|
494
|
+
return this.anchors.get(target) ?? safeQuery(target);
|
|
495
|
+
}
|
|
496
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxTour, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
497
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxTour, providedIn: 'root' });
|
|
498
|
+
}
|
|
499
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxTour, decorators: [{
|
|
500
|
+
type: Injectable,
|
|
501
|
+
args: [{ providedIn: 'root' }]
|
|
502
|
+
}] });
|
|
503
|
+
function safeQuery(selector) {
|
|
504
|
+
try {
|
|
505
|
+
return document.querySelector(selector);
|
|
506
|
+
}
|
|
507
|
+
catch {
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
/** Dá um nome a um elemento para ser alvo de um passo: `mxTourAnchor="pesquisa"`. */
|
|
512
|
+
class MxTourAnchor {
|
|
513
|
+
name = input.required({ ...(ngDevMode ? { debugName: "name" } : /* istanbul ignore next */ {}), alias: 'mxTourAnchor' });
|
|
514
|
+
tour = inject(MxTour);
|
|
515
|
+
el = inject(ElementRef).nativeElement;
|
|
516
|
+
constructor() {
|
|
517
|
+
effect((onCleanup) => {
|
|
518
|
+
const name = this.name();
|
|
519
|
+
this.tour.register(name, this.el);
|
|
520
|
+
onCleanup(() => this.tour.unregister(name, this.el));
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxTourAnchor, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
524
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.25", type: MxTourAnchor, isStandalone: true, selector: "[mxTourAnchor]", inputs: { name: { classPropertyName: "name", publicName: "mxTourAnchor", isSignal: true, isRequired: true, transformFunction: null } }, exportAs: ["mxTourAnchor"], ngImport: i0 });
|
|
525
|
+
}
|
|
526
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxTourAnchor, decorators: [{
|
|
527
|
+
type: Directive,
|
|
528
|
+
args: [{ selector: '[mxTourAnchor]', exportAs: 'mxTourAnchor' }]
|
|
529
|
+
}], ctorParameters: () => [], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "mxTourAnchor", required: true }] }] } });
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Generated bundle index. Do not edit.
|
|
533
|
+
*/
|
|
534
|
+
|
|
535
|
+
export { MX_TOUR_LABELS, MxTour, MxTourAnchor, MxTourPanel, MxTourRef };
|
|
536
|
+
//# sourceMappingURL=muxima-ui-core-tour.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"muxima-ui-core-tour.mjs","sources":["../../../projects/core/tour/tour.ts","../../../projects/core/tour/muxima-ui-core-tour.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n DestroyRef,\n Directive,\n ElementRef,\n Injectable,\n InjectionToken,\n Injector,\n NgZone,\n TemplateRef,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { CdkTrapFocus } from '@angular/cdk/a11y';\nimport {\n OverlayRef,\n createGlobalPositionStrategy,\n createNoopScrollStrategy,\n createOverlayRef,\n} from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { MxButton } from '@muxima-ui/core/button';\nimport { mxUniqueId } from '@muxima-ui/core/form-field';\n\n/** Onde fica o cartão em relação ao elemento destacado. */\nexport type MxTourPlacement = 'auto' | 'top' | 'bottom' | 'start' | 'end';\n\n/** Nome de um `mxTourAnchor`, um elemento, ou um ElementRef. */\nexport type MxTourTarget = string | Element | ElementRef<Element>;\n\n/** Contexto do modelo de conteúdo de um passo. */\nexport interface MxTourStepContext {\n $implicit: MxTourStep;\n index: number;\n tour: MxTourRef;\n}\n\n/** Um passo da visita guiada. */\nexport interface MxTourStep {\n /** Elemento a destacar. Sem alvo (ou se não existir), o cartão aparece ao centro. */\n target?: MxTourTarget | null;\n title: string;\n /** Texto do passo. */\n content?: string;\n /** Conteúdo rico, em vez de `content`. */\n template?: TemplateRef<MxTourStepContext>;\n placement?: MxTourPlacement;\n /** Espaço à volta do elemento destacado, em px. Por omissão 6. */\n padding?: number;\n /** Corre antes de mostrar o passo (ex.: abrir um separador). Pode devolver uma Promise. */\n beforeShow?: () => void | Promise<unknown>;\n}\n\nexport interface MxTourLabels {\n next: string;\n previous: string;\n done: string;\n skip: string;\n close: string;\n /** Texto do progresso, por exemplo \"Passo 2 de 5\". */\n progress: (current: number, total: number) => string;\n}\n\nexport const MX_TOUR_LABELS: MxTourLabels = {\n next: 'Seguinte',\n previous: 'Anterior',\n done: 'Concluir',\n skip: 'Saltar',\n close: 'Fechar a visita guiada',\n progress: (current, total) => `Passo ${current} de ${total}`,\n};\n\nexport interface MxTourOptions {\n /** Passo inicial (base 0). */\n startAt?: number;\n labels?: Partial<MxTourLabels>;\n /** false: sem Saltar, sem botão de fechar e sem Escape (a visita tem de ser feita até ao fim). */\n dismissible?: boolean;\n /** Mostra os pontos de progresso. Por omissão true. */\n showDots?: boolean;\n}\n\n/** Como terminou a visita. */\nexport interface MxTourResult {\n /** true se chegou ao fim com Concluir. */\n completed: boolean;\n /** Último passo mostrado (base 0). */\n index: number;\n}\n\n/** Referência a uma visita em curso. */\nexport class MxTourRef {\n /** Passo actual (base 0). */\n readonly index = signal(0);\n /** Resolve quando a visita fecha. */\n readonly afterClosed: Promise<MxTourResult>;\n readonly labels: MxTourLabels;\n readonly dismissible: boolean;\n readonly showDots: boolean;\n private resolve!: (r: MxTourResult) => void;\n private done = false;\n\n constructor(\n readonly steps: readonly MxTourStep[],\n options: MxTourOptions,\n private readonly onClose: (r: MxTourResult) => void,\n ) {\n this.labels = { ...MX_TOUR_LABELS, ...options.labels };\n this.dismissible = options.dismissible ?? true;\n this.showDots = options.showDots ?? true;\n this.index.set(Math.min(Math.max(options.startAt ?? 0, 0), Math.max(steps.length - 1, 0)));\n this.afterClosed = new Promise((r) => (this.resolve = r));\n }\n\n get isOpen(): boolean {\n return !this.done;\n }\n get isLast(): boolean {\n return this.index() === this.steps.length - 1;\n }\n\n next(): void {\n if (this.isLast) this.close(true);\n else this.goTo(this.index() + 1);\n }\n previous(): void {\n this.goTo(this.index() - 1);\n }\n goTo(index: number): void {\n if (this.done || index < 0 || index >= this.steps.length) return;\n this.index.set(index);\n }\n /** Fecha a visita; `completed` diz se foi até ao fim. */\n close(completed = false): void {\n if (this.done) return;\n this.done = true;\n const result = { completed, index: this.index() };\n this.onClose(result);\n this.resolve(result);\n }\n}\n\nconst MX_TOUR_REF = new InjectionToken<MxTourRef>('MX_TOUR_REF');\n\ninterface Box {\n top: number;\n left: number;\n width: number;\n height: number;\n}\n\n/** requestAnimationFrame, ou um setTimeout onde não exista (testes, SSR). */\nconst nextFrame = (cb: () => void): number =>\n typeof requestAnimationFrame === 'function' ? requestAnimationFrame(cb) : (setTimeout(cb, 16) as unknown as number);\nconst cancelFrame = (id: number): void =>\n typeof cancelAnimationFrame === 'function' ? cancelAnimationFrame(id) : clearTimeout(id);\n\nconst GAP = 12;\nconst MARGIN = 12;\n\n/** Painel da visita: o destaque, o fundo escurecido e o cartão do passo. Uso interno. */\n@Component({\n selector: 'mx-tour-panel',\n imports: [NgTemplateOutlet, CdkTrapFocus, MxButton],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n host: { class: 'mx-tour-layer' },\n template: `\n @if (spot(); as s) {\n <div\n class=\"mx-tour-spotlight\"\n aria-hidden=\"true\"\n [style.top.px]=\"s.top\"\n [style.left.px]=\"s.left\"\n [style.width.px]=\"s.width\"\n [style.height.px]=\"s.height\"\n ></div>\n } @else {\n <div class=\"mx-tour-dim\" aria-hidden=\"true\"></div>\n }\n @if (step(); as st) {\n <div\n #card\n class=\"mx-tour-card\"\n role=\"dialog\"\n aria-modal=\"true\"\n tabindex=\"-1\"\n cdkTrapFocus\n [attr.aria-labelledby]=\"titleId\"\n [attr.aria-describedby]=\"progressId + ' ' + bodyId\"\n [attr.data-placement]=\"spot() ? placement() : 'center'\"\n [style.top.px]=\"spot() ? pos().top : null\"\n [style.left.px]=\"spot() ? pos().left : null\"\n [style.visibility]=\"ready() ? null : 'hidden'\"\n (keydown)=\"onKeydown($event)\"\n >\n <div class=\"mx-tour-head\">\n <p class=\"mx-tour-progress\" [id]=\"progressId\">\n {{ ref.labels.progress(ref.index() + 1, ref.steps.length) }}\n </p>\n @if (ref.dismissible) {\n <button\n type=\"button\"\n class=\"mx-tour-close\"\n [attr.aria-label]=\"ref.labels.close\"\n (click)=\"ref.close(false)\"\n >\n <svg viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"M4 4l8 8M12 4l-8 8\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n }\n </div>\n <h2 class=\"mx-tour-title\" [id]=\"titleId\">{{ st.title }}</h2>\n <div class=\"mx-tour-body\" [id]=\"bodyId\">\n @if (st.template) {\n <ng-container\n *ngTemplateOutlet=\"st.template; context: { $implicit: st, index: ref.index(), tour: ref }\"\n />\n } @else if (st.content) {\n <p>{{ st.content }}</p>\n }\n </div>\n <div class=\"mx-tour-foot\">\n @if (ref.showDots && ref.steps.length > 1) {\n <span class=\"mx-tour-dots\" aria-hidden=\"true\">\n @for (s of ref.steps; track $index) {\n <span class=\"mx-tour-dot\" [attr.data-active]=\"$index === ref.index() ? '' : null\"></span>\n }\n </span>\n }\n <span class=\"mx-tour-actions\">\n @if (ref.dismissible && !ref.isLast) {\n <button mxButton variant=\"ghost\" size=\"sm\" type=\"button\" (click)=\"ref.close(false)\">\n {{ ref.labels.skip }}\n </button>\n }\n @if (ref.index() > 0) {\n <button mxButton variant=\"outline\" size=\"sm\" type=\"button\" (click)=\"ref.previous()\">\n {{ ref.labels.previous }}\n </button>\n }\n <button mxButton size=\"sm\" type=\"button\" (click)=\"ref.next()\">\n {{ ref.isLast ? ref.labels.done : ref.labels.next }}\n </button>\n </span>\n </div>\n </div>\n }\n `,\n})\nexport class MxTourPanel {\n protected readonly ref = inject(MX_TOUR_REF);\n private readonly tour = inject(MxTour);\n\n protected readonly titleId = mxUniqueId('mx-tour-title');\n protected readonly bodyId = mxUniqueId('mx-tour-body');\n protected readonly progressId = mxUniqueId('mx-tour-progress');\n\n private readonly card = viewChild<ElementRef<HTMLElement>>('card');\n private readonly zone = inject(NgZone);\n private readonly cdr = inject(ChangeDetectorRef);\n\n /** Passo que está desenhado (muda depois de beforeShow). */\n protected readonly step = signal<MxTourStep | null>(null);\n private readonly target = signal<Element | null>(null);\n protected readonly spot = signal<Box | null>(null);\n protected readonly pos = signal({ top: 0, left: 0 });\n protected readonly placement = signal<Exclude<MxTourPlacement, 'auto'>>('bottom');\n protected readonly ready = signal(false);\n\n private frame = 0;\n private resizeObserver?: ResizeObserver;\n private showToken = 0;\n\n constructor() {\n const destroy = inject(DestroyRef);\n // mostra o passo sempre que o índice muda\n effect(() => {\n const i = this.ref.index();\n untracked(() => void this.show(i));\n });\n\n afterNextRender(() => {\n const schedule = () => this.schedule();\n this.zone.runOutsideAngular(() => {\n window.addEventListener('scroll', schedule, { capture: true, passive: true });\n window.addEventListener('resize', schedule, { passive: true });\n });\n if (typeof ResizeObserver !== 'undefined') this.resizeObserver = new ResizeObserver(schedule);\n destroy.onDestroy(() => {\n window.removeEventListener('scroll', schedule, { capture: true });\n window.removeEventListener('resize', schedule);\n this.resizeObserver?.disconnect();\n cancelFrame(this.frame);\n });\n });\n }\n\n private async show(index: number): Promise<void> {\n const token = ++this.showToken;\n const st = this.ref.steps[index];\n if (!st) return;\n // só se esconde antes do primeiro passo (para o posicionar); depois, esconder\n // o cartão tirava-lhe o foco a meio da visita\n if (!this.step()) this.ready.set(false);\n try {\n await st.beforeShow?.();\n } catch {\n // um beforeShow que falha não deve prender a visita\n }\n // espera um frame para o DOM reflectir o que beforeShow mudou\n await new Promise<void>((r) => nextFrame(r));\n if (token !== this.showToken || !this.ref.isOpen) return;\n\n // o alvo pode só aparecer depois de beforeShow (ex.: conteúdo de um separador)\n let el = this.tour.resolve(st.target);\n for (let i = 0; !el && st.target && i < 5; i++) {\n await new Promise<void>((r) => nextFrame(r));\n if (token !== this.showToken || !this.ref.isOpen) return;\n el = this.tour.resolve(st.target);\n }\n this.resizeObserver?.disconnect();\n this.target.set(el);\n if (el) {\n this.resizeObserver?.observe(el);\n const r = el.getBoundingClientRect();\n const fora = r.top < 0 || r.bottom > window.innerHeight || r.left < 0 || r.right > window.innerWidth;\n if (fora) {\n const reduzir = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n el.scrollIntoView?.({ block: 'center', inline: 'nearest', behavior: reduzir ? 'auto' : 'smooth' });\n }\n }\n this.step.set(st);\n // desenha já o cartão para o medir, posicionar e dar-lhe o foco\n this.cdr.detectChanges();\n const card = this.card()?.nativeElement;\n if (card) this.resizeObserver?.observe(card);\n this.update();\n this.ready.set(true);\n this.cdr.detectChanges();\n card?.focus({ preventScroll: true });\n }\n\n private schedule(): void {\n cancelFrame(this.frame);\n this.frame = nextFrame(() => this.zone.run(() => this.update()));\n }\n\n private update(): void {\n const st = this.step();\n const el = this.target();\n const r = el?.getBoundingClientRect();\n if (!st || !r || (r.width === 0 && r.height === 0)) {\n this.spot.set(null);\n return;\n }\n const pad = st.padding ?? 6;\n const spot: Box = { top: r.top - pad, left: r.left - pad, width: r.width + pad * 2, height: r.height + pad * 2 };\n this.spot.set(spot);\n\n const card = this.card()?.nativeElement;\n const cw = card?.offsetWidth ?? 320;\n const ch = card?.offsetHeight ?? 160;\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n const rtl = getComputedStyle(document.documentElement).direction === 'rtl';\n const side = (p: Exclude<MxTourPlacement, 'auto'>): Exclude<MxTourPlacement, 'auto'> =>\n rtl && (p === 'start' || p === 'end') ? (p === 'start' ? 'end' : 'start') : p;\n\n const fits: Record<Exclude<MxTourPlacement, 'auto'>, boolean> = {\n bottom: spot.top + spot.height + GAP + ch <= vh - MARGIN,\n top: spot.top - GAP - ch >= MARGIN,\n end: spot.left + spot.width + GAP + cw <= vw - MARGIN,\n start: spot.left - GAP - cw >= MARGIN,\n };\n const wanted = st.placement && st.placement !== 'auto' ? side(st.placement) : null;\n const order: Exclude<MxTourPlacement, 'auto'>[] = wanted\n ? [wanted, 'bottom', 'top', 'end', 'start']\n : ['bottom', 'top', 'end', 'start'];\n const p = order.find((o) => fits[o]) ?? 'bottom';\n\n let top: number;\n let left: number;\n if (p === 'bottom' || p === 'top') {\n top = p === 'bottom' ? spot.top + spot.height + GAP : spot.top - GAP - ch;\n left = spot.left + spot.width / 2 - cw / 2;\n } else {\n left = p === 'end' ? spot.left + spot.width + GAP : spot.left - GAP - cw;\n top = spot.top + spot.height / 2 - ch / 2;\n }\n const clamp = (v: number, min: number, max: number) => Math.min(Math.max(v, min), Math.max(min, max));\n this.placement.set(rtl && (p === 'start' || p === 'end') ? (p === 'start' ? 'end' : 'start') : p);\n this.pos.set({ top: clamp(top, MARGIN, vh - ch - MARGIN), left: clamp(left, MARGIN, vw - cw - MARGIN) });\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n const t = event.target as HTMLElement;\n const editavel = t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName);\n if (event.key === 'Escape' && this.ref.dismissible) {\n event.preventDefault();\n event.stopPropagation();\n this.ref.close(false);\n } else if (!editavel && event.key === 'ArrowRight' && !this.ref.isLast) {\n event.preventDefault();\n this.ref.next();\n } else if (!editavel && event.key === 'ArrowLeft' && this.ref.index() > 0) {\n event.preventDefault();\n this.ref.previous();\n }\n }\n}\n\n/**\n * Visita guiada: destaca elementos da página, um passo de cada vez, com um cartão\n * que explica cada um. O cartão é um diálogo modal: o foco fica nele, Escape fecha\n * e, no fim, o foco volta para onde estava.\n *\n * ```html\n * <input mxInput mxTourAnchor=\"pesquisa\" aria-label=\"Pesquisar\" />\n * <button mxButton mxTourAnchor=\"nova\">Nova factura</button>\n * ```\n *\n * ```ts\n * private readonly tour = inject(MxTour);\n * mostrar() {\n * this.tour.start([\n * { title: 'Bem-vindo', content: 'Mostramos o essencial em 2 passos.' },\n * { target: 'pesquisa', title: 'Pesquisa', content: 'Procura clientes e facturas.' },\n * { target: 'nova', title: 'Nova factura', content: 'Começa aqui.' },\n * ]).afterClosed.then((r) => r.completed && guardarQueViu());\n * }\n * ```\n */\n@Injectable({ providedIn: 'root' })\nexport class MxTour {\n private readonly injector = inject(Injector);\n private readonly anchors = new Map<string, Element>();\n private overlayRef?: OverlayRef;\n /** A visita em curso, ou null. */\n readonly active = signal<MxTourRef | null>(null);\n readonly isOpen = computed(() => !!this.active());\n\n /** Começa uma visita. Se já houver uma aberta, fecha-a primeiro. */\n start(steps: readonly MxTourStep[], options: MxTourOptions = {}): MxTourRef {\n this.active()?.close(false);\n const before = document.activeElement as HTMLElement | null;\n const ref = new MxTourRef(steps, options, () => {\n this.overlayRef?.dispose();\n this.overlayRef = undefined;\n this.active.set(null);\n if (before && before.isConnected) before.focus({ preventScroll: true });\n });\n if (!steps.length) {\n ref.close(true);\n return ref;\n }\n this.overlayRef = createOverlayRef(this.injector, {\n positionStrategy: createGlobalPositionStrategy(this.injector).top('0').left('0'),\n scrollStrategy: createNoopScrollStrategy(),\n width: '100%',\n height: '100%',\n panelClass: 'mx-tour-pane',\n });\n const injector = Injector.create({\n providers: [{ provide: MX_TOUR_REF, useValue: ref }],\n parent: this.injector,\n });\n this.overlayRef.attach(new ComponentPortal(MxTourPanel, null, injector));\n this.active.set(ref);\n return ref;\n }\n\n /** Regista um elemento com um nome (usado por `mxTourAnchor`). */\n register(name: string, element: Element): void {\n this.anchors.set(name, element);\n }\n unregister(name: string, element: Element): void {\n if (this.anchors.get(name) === element) this.anchors.delete(name);\n }\n\n /** Encontra o elemento de um alvo: nome de âncora, selector CSS, elemento ou ElementRef. */\n resolve(target: MxTourTarget | null | undefined): Element | null {\n if (!target) return null;\n if (target instanceof ElementRef) return target.nativeElement;\n if (typeof target !== 'string') return target;\n return this.anchors.get(target) ?? safeQuery(target);\n }\n}\n\nfunction safeQuery(selector: string): Element | null {\n try {\n return document.querySelector(selector);\n } catch {\n return null;\n }\n}\n\n/** Dá um nome a um elemento para ser alvo de um passo: `mxTourAnchor=\"pesquisa\"`. */\n@Directive({ selector: '[mxTourAnchor]', exportAs: 'mxTourAnchor' })\nexport class MxTourAnchor {\n readonly name = input.required<string>({ alias: 'mxTourAnchor' });\n private readonly tour = inject(MxTour);\n private readonly el = inject<ElementRef<Element>>(ElementRef).nativeElement;\n\n constructor() {\n effect((onCleanup) => {\n const name = this.name();\n this.tour.register(name, this.el);\n onCleanup(() => this.tour.unregister(name, this.el));\n });\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"muxima-ui-core.mjs","sources":["../../../projects/core/src/public-api.ts","../../../projects/core/src/muxima-ui-core.ts"],"sourcesContent":["/*\n * Muxima UI — ponto de entrada principal.\n * Os componentes vivem em entradas secundárias, para só incluíres o que usas:\n * import { MxButton } from '@muxima-ui/core/button';\n */\nexport const MUXIMA_VERSION = '1.
|
|
1
|
+
{"version":3,"file":"muxima-ui-core.mjs","sources":["../../../projects/core/src/public-api.ts","../../../projects/core/src/muxima-ui-core.ts"],"sourcesContent":["/*\n * Muxima UI — ponto de entrada principal.\n * Os componentes vivem em entradas secundárias, para só incluíres o que usas:\n * import { MxButton } from '@muxima-ui/core/button';\n */\nexport const MUXIMA_VERSION = '1.4.1';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"AAAA;;;;AAIG;AACI,MAAM,cAAc,GAAG;;ACL9B;;AAEG;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muxima-ui/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Componentes Angular acessíveis e sóbrios para aplicações empresariais.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"homepage": "https://github.com/Aldemiro20/muxima-ui",
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
18
|
-
"url": "https://github.com/Aldemiro20/muxima-ui.git"
|
|
18
|
+
"url": "git+https://github.com/Aldemiro20/muxima-ui.git"
|
|
19
19
|
},
|
|
20
20
|
"bugs": {
|
|
21
21
|
"url": "https://github.com/Aldemiro20/muxima-ui/issues"
|
|
@@ -238,6 +238,10 @@
|
|
|
238
238
|
"./tooltip": {
|
|
239
239
|
"types": "./types/muxima-ui-core-tooltip.d.ts",
|
|
240
240
|
"default": "./fesm2022/muxima-ui-core-tooltip.mjs"
|
|
241
|
+
},
|
|
242
|
+
"./tour": {
|
|
243
|
+
"types": "./types/muxima-ui-core-tour.d.ts",
|
|
244
|
+
"default": "./fesm2022/muxima-ui-core-tour.mjs"
|
|
241
245
|
}
|
|
242
246
|
},
|
|
243
247
|
"sideEffects": [
|
package/styles/base.css
CHANGED
|
@@ -109,6 +109,7 @@ button.mx-input-addon:is(:focus-visible, [data-mx-state='focus']),
|
|
|
109
109
|
.mx-calendar-day:is(:focus-visible, [data-mx-state='focus']),
|
|
110
110
|
.mx-date-range-input:focus-visible,
|
|
111
111
|
.mx-chip-remove:focus-visible,
|
|
112
|
+
.mx-tour-close:focus-visible,
|
|
112
113
|
.mx-editor-tool:focus-visible,
|
|
113
114
|
.mx-editor-text-button:focus-visible,
|
|
114
115
|
.mx-cropper-button:focus-visible,
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/* ---------- Tour (visita guiada) ---------- */
|
|
2
|
+
|
|
3
|
+
.mx-tour-pane {
|
|
4
|
+
pointer-events: auto;
|
|
5
|
+
}
|
|
6
|
+
.mx-tour-layer {
|
|
7
|
+
position: fixed;
|
|
8
|
+
inset: 0;
|
|
9
|
+
display: block;
|
|
10
|
+
font-family: var(--mx-font-sans);
|
|
11
|
+
}
|
|
12
|
+
.mx-tour-dim {
|
|
13
|
+
position: fixed;
|
|
14
|
+
inset: 0;
|
|
15
|
+
background: var(--mx-backdrop);
|
|
16
|
+
animation: mx-fade-in var(--mx-duration) var(--mx-ease);
|
|
17
|
+
}
|
|
18
|
+
/* o recorte: uma sombra enorme escurece tudo à volta do elemento */
|
|
19
|
+
.mx-tour-spotlight {
|
|
20
|
+
position: fixed;
|
|
21
|
+
border-radius: var(--mx-radius);
|
|
22
|
+
box-shadow:
|
|
23
|
+
0 0 0 2px var(--mx-accent),
|
|
24
|
+
0 0 0 200vmax var(--mx-backdrop);
|
|
25
|
+
pointer-events: none;
|
|
26
|
+
transition:
|
|
27
|
+
top 200ms var(--mx-ease),
|
|
28
|
+
left 200ms var(--mx-ease),
|
|
29
|
+
width 200ms var(--mx-ease),
|
|
30
|
+
height 200ms var(--mx-ease);
|
|
31
|
+
}
|
|
32
|
+
.mx-tour-card {
|
|
33
|
+
position: fixed;
|
|
34
|
+
box-sizing: border-box;
|
|
35
|
+
width: min(340px, calc(100vw - 24px));
|
|
36
|
+
padding: 16px 16px 14px;
|
|
37
|
+
border: var(--mx-border);
|
|
38
|
+
border-radius: var(--mx-radius-surface);
|
|
39
|
+
background: var(--mx-surface);
|
|
40
|
+
box-shadow: var(--mx-shadow-overlay);
|
|
41
|
+
color: var(--mx-ink);
|
|
42
|
+
font-size: var(--mx-text-md);
|
|
43
|
+
line-height: 1.5;
|
|
44
|
+
outline: none;
|
|
45
|
+
animation: mx-fade-in var(--mx-duration) var(--mx-ease);
|
|
46
|
+
}
|
|
47
|
+
.mx-tour-card:focus,
|
|
48
|
+
.mx-tour-card:focus-visible {
|
|
49
|
+
outline: none;
|
|
50
|
+
}
|
|
51
|
+
.mx-tour-card[data-placement='center'] {
|
|
52
|
+
top: 50%;
|
|
53
|
+
left: 50%;
|
|
54
|
+
width: min(400px, calc(100vw - 24px));
|
|
55
|
+
transform: translate(-50%, -50%);
|
|
56
|
+
}
|
|
57
|
+
.mx-tour-head {
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
justify-content: space-between;
|
|
61
|
+
gap: 8px;
|
|
62
|
+
min-height: 24px;
|
|
63
|
+
}
|
|
64
|
+
.mx-tour-progress {
|
|
65
|
+
margin: 0;
|
|
66
|
+
color: var(--mx-muted);
|
|
67
|
+
font-size: var(--mx-text-xs);
|
|
68
|
+
font-variant-numeric: tabular-nums;
|
|
69
|
+
}
|
|
70
|
+
.mx-tour-close {
|
|
71
|
+
display: inline-grid;
|
|
72
|
+
place-items: center;
|
|
73
|
+
width: 28px;
|
|
74
|
+
height: 28px;
|
|
75
|
+
margin: -4px -6px -4px 0;
|
|
76
|
+
padding: 0;
|
|
77
|
+
border: 0;
|
|
78
|
+
border-radius: var(--mx-radius);
|
|
79
|
+
background: transparent;
|
|
80
|
+
color: var(--mx-muted);
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
}
|
|
83
|
+
.mx-tour-close:hover {
|
|
84
|
+
background: var(--mx-subtle);
|
|
85
|
+
color: var(--mx-ink);
|
|
86
|
+
}
|
|
87
|
+
.mx-tour-close svg {
|
|
88
|
+
width: 16px;
|
|
89
|
+
height: 16px;
|
|
90
|
+
}
|
|
91
|
+
.mx-tour-title {
|
|
92
|
+
margin: 6px 0 4px;
|
|
93
|
+
font-size: var(--mx-text-lg);
|
|
94
|
+
font-weight: 600;
|
|
95
|
+
line-height: 1.35;
|
|
96
|
+
}
|
|
97
|
+
.mx-tour-body {
|
|
98
|
+
color: var(--mx-muted);
|
|
99
|
+
}
|
|
100
|
+
.mx-tour-body > :first-child {
|
|
101
|
+
margin-top: 0;
|
|
102
|
+
}
|
|
103
|
+
.mx-tour-body > :last-child {
|
|
104
|
+
margin-bottom: 0;
|
|
105
|
+
}
|
|
106
|
+
.mx-tour-foot {
|
|
107
|
+
display: flex;
|
|
108
|
+
flex-wrap: wrap;
|
|
109
|
+
align-items: center;
|
|
110
|
+
justify-content: space-between;
|
|
111
|
+
gap: 10px;
|
|
112
|
+
margin-top: 14px;
|
|
113
|
+
}
|
|
114
|
+
.mx-tour-dots {
|
|
115
|
+
display: inline-flex;
|
|
116
|
+
gap: 5px;
|
|
117
|
+
}
|
|
118
|
+
.mx-tour-dot {
|
|
119
|
+
width: 6px;
|
|
120
|
+
height: 6px;
|
|
121
|
+
border-radius: 50%;
|
|
122
|
+
background: var(--mx-stone);
|
|
123
|
+
}
|
|
124
|
+
.mx-tour-dot[data-active] {
|
|
125
|
+
width: 16px;
|
|
126
|
+
border-radius: 3px;
|
|
127
|
+
background: var(--mx-accent);
|
|
128
|
+
}
|
|
129
|
+
.mx-tour-actions {
|
|
130
|
+
display: inline-flex;
|
|
131
|
+
gap: 6px;
|
|
132
|
+
margin-left: auto;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@media (prefers-reduced-motion: reduce) {
|
|
136
|
+
.mx-tour-spotlight {
|
|
137
|
+
transition: none;
|
|
138
|
+
}
|
|
139
|
+
.mx-tour-card,
|
|
140
|
+
.mx-tour-dim {
|
|
141
|
+
animation: none;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/* sem sombras em alto contraste: o destaque passa a ser um contorno */
|
|
145
|
+
@media (forced-colors: active) {
|
|
146
|
+
.mx-tour-spotlight {
|
|
147
|
+
outline: 3px solid Highlight;
|
|
148
|
+
}
|
|
149
|
+
.mx-tour-card {
|
|
150
|
+
border: 1px solid CanvasText;
|
|
151
|
+
}
|
|
152
|
+
.mx-tour-dot[data-active] {
|
|
153
|
+
background: Highlight;
|
|
154
|
+
}
|
|
155
|
+
}
|
package/styles/muxima.css
CHANGED
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
@import './components/time-picker.css';
|
|
54
54
|
@import './components/color-picker.css';
|
|
55
55
|
@import './components/editor.css';
|
|
56
|
+
@import './components/tour.css';
|
|
56
57
|
@import './components/otp.css';
|
|
57
58
|
@import './components/file-upload.css';
|
|
58
59
|
@import './components/image-cropper.css';
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
import { ElementRef, TemplateRef } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
/** Onde fica o cartão em relação ao elemento destacado. */
|
|
5
|
+
type MxTourPlacement = 'auto' | 'top' | 'bottom' | 'start' | 'end';
|
|
6
|
+
/** Nome de um `mxTourAnchor`, um elemento, ou um ElementRef. */
|
|
7
|
+
type MxTourTarget = string | Element | ElementRef<Element>;
|
|
8
|
+
/** Contexto do modelo de conteúdo de um passo. */
|
|
9
|
+
interface MxTourStepContext {
|
|
10
|
+
$implicit: MxTourStep;
|
|
11
|
+
index: number;
|
|
12
|
+
tour: MxTourRef;
|
|
13
|
+
}
|
|
14
|
+
/** Um passo da visita guiada. */
|
|
15
|
+
interface MxTourStep {
|
|
16
|
+
/** Elemento a destacar. Sem alvo (ou se não existir), o cartão aparece ao centro. */
|
|
17
|
+
target?: MxTourTarget | null;
|
|
18
|
+
title: string;
|
|
19
|
+
/** Texto do passo. */
|
|
20
|
+
content?: string;
|
|
21
|
+
/** Conteúdo rico, em vez de `content`. */
|
|
22
|
+
template?: TemplateRef<MxTourStepContext>;
|
|
23
|
+
placement?: MxTourPlacement;
|
|
24
|
+
/** Espaço à volta do elemento destacado, em px. Por omissão 6. */
|
|
25
|
+
padding?: number;
|
|
26
|
+
/** Corre antes de mostrar o passo (ex.: abrir um separador). Pode devolver uma Promise. */
|
|
27
|
+
beforeShow?: () => void | Promise<unknown>;
|
|
28
|
+
}
|
|
29
|
+
interface MxTourLabels {
|
|
30
|
+
next: string;
|
|
31
|
+
previous: string;
|
|
32
|
+
done: string;
|
|
33
|
+
skip: string;
|
|
34
|
+
close: string;
|
|
35
|
+
/** Texto do progresso, por exemplo "Passo 2 de 5". */
|
|
36
|
+
progress: (current: number, total: number) => string;
|
|
37
|
+
}
|
|
38
|
+
declare const MX_TOUR_LABELS: MxTourLabels;
|
|
39
|
+
interface MxTourOptions {
|
|
40
|
+
/** Passo inicial (base 0). */
|
|
41
|
+
startAt?: number;
|
|
42
|
+
labels?: Partial<MxTourLabels>;
|
|
43
|
+
/** false: sem Saltar, sem botão de fechar e sem Escape (a visita tem de ser feita até ao fim). */
|
|
44
|
+
dismissible?: boolean;
|
|
45
|
+
/** Mostra os pontos de progresso. Por omissão true. */
|
|
46
|
+
showDots?: boolean;
|
|
47
|
+
}
|
|
48
|
+
/** Como terminou a visita. */
|
|
49
|
+
interface MxTourResult {
|
|
50
|
+
/** true se chegou ao fim com Concluir. */
|
|
51
|
+
completed: boolean;
|
|
52
|
+
/** Último passo mostrado (base 0). */
|
|
53
|
+
index: number;
|
|
54
|
+
}
|
|
55
|
+
/** Referência a uma visita em curso. */
|
|
56
|
+
declare class MxTourRef {
|
|
57
|
+
readonly steps: readonly MxTourStep[];
|
|
58
|
+
private readonly onClose;
|
|
59
|
+
/** Passo actual (base 0). */
|
|
60
|
+
readonly index: _angular_core.WritableSignal<number>;
|
|
61
|
+
/** Resolve quando a visita fecha. */
|
|
62
|
+
readonly afterClosed: Promise<MxTourResult>;
|
|
63
|
+
readonly labels: MxTourLabels;
|
|
64
|
+
readonly dismissible: boolean;
|
|
65
|
+
readonly showDots: boolean;
|
|
66
|
+
private resolve;
|
|
67
|
+
private done;
|
|
68
|
+
constructor(steps: readonly MxTourStep[], options: MxTourOptions, onClose: (r: MxTourResult) => void);
|
|
69
|
+
get isOpen(): boolean;
|
|
70
|
+
get isLast(): boolean;
|
|
71
|
+
next(): void;
|
|
72
|
+
previous(): void;
|
|
73
|
+
goTo(index: number): void;
|
|
74
|
+
/** Fecha a visita; `completed` diz se foi até ao fim. */
|
|
75
|
+
close(completed?: boolean): void;
|
|
76
|
+
}
|
|
77
|
+
interface Box {
|
|
78
|
+
top: number;
|
|
79
|
+
left: number;
|
|
80
|
+
width: number;
|
|
81
|
+
height: number;
|
|
82
|
+
}
|
|
83
|
+
/** Painel da visita: o destaque, o fundo escurecido e o cartão do passo. Uso interno. */
|
|
84
|
+
declare class MxTourPanel {
|
|
85
|
+
protected readonly ref: MxTourRef;
|
|
86
|
+
private readonly tour;
|
|
87
|
+
protected readonly titleId: string;
|
|
88
|
+
protected readonly bodyId: string;
|
|
89
|
+
protected readonly progressId: string;
|
|
90
|
+
private readonly card;
|
|
91
|
+
private readonly zone;
|
|
92
|
+
private readonly cdr;
|
|
93
|
+
/** Passo que está desenhado (muda depois de beforeShow). */
|
|
94
|
+
protected readonly step: _angular_core.WritableSignal<MxTourStep | null>;
|
|
95
|
+
private readonly target;
|
|
96
|
+
protected readonly spot: _angular_core.WritableSignal<Box | null>;
|
|
97
|
+
protected readonly pos: _angular_core.WritableSignal<{
|
|
98
|
+
top: number;
|
|
99
|
+
left: number;
|
|
100
|
+
}>;
|
|
101
|
+
protected readonly placement: _angular_core.WritableSignal<"top" | "bottom" | "start" | "end">;
|
|
102
|
+
protected readonly ready: _angular_core.WritableSignal<boolean>;
|
|
103
|
+
private frame;
|
|
104
|
+
private resizeObserver?;
|
|
105
|
+
private showToken;
|
|
106
|
+
constructor();
|
|
107
|
+
private show;
|
|
108
|
+
private schedule;
|
|
109
|
+
private update;
|
|
110
|
+
protected onKeydown(event: KeyboardEvent): void;
|
|
111
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<MxTourPanel, never>;
|
|
112
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<MxTourPanel, "mx-tour-panel", never, {}, {}, never, never, true, never>;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Visita guiada: destaca elementos da página, um passo de cada vez, com um cartão
|
|
116
|
+
* que explica cada um. O cartão é um diálogo modal: o foco fica nele, Escape fecha
|
|
117
|
+
* e, no fim, o foco volta para onde estava.
|
|
118
|
+
*
|
|
119
|
+
* ```html
|
|
120
|
+
* <input mxInput mxTourAnchor="pesquisa" aria-label="Pesquisar" />
|
|
121
|
+
* <button mxButton mxTourAnchor="nova">Nova factura</button>
|
|
122
|
+
* ```
|
|
123
|
+
*
|
|
124
|
+
* ```ts
|
|
125
|
+
* private readonly tour = inject(MxTour);
|
|
126
|
+
* mostrar() {
|
|
127
|
+
* this.tour.start([
|
|
128
|
+
* { title: 'Bem-vindo', content: 'Mostramos o essencial em 2 passos.' },
|
|
129
|
+
* { target: 'pesquisa', title: 'Pesquisa', content: 'Procura clientes e facturas.' },
|
|
130
|
+
* { target: 'nova', title: 'Nova factura', content: 'Começa aqui.' },
|
|
131
|
+
* ]).afterClosed.then((r) => r.completed && guardarQueViu());
|
|
132
|
+
* }
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
declare class MxTour {
|
|
136
|
+
private readonly injector;
|
|
137
|
+
private readonly anchors;
|
|
138
|
+
private overlayRef?;
|
|
139
|
+
/** A visita em curso, ou null. */
|
|
140
|
+
readonly active: _angular_core.WritableSignal<MxTourRef | null>;
|
|
141
|
+
readonly isOpen: _angular_core.Signal<boolean>;
|
|
142
|
+
/** Começa uma visita. Se já houver uma aberta, fecha-a primeiro. */
|
|
143
|
+
start(steps: readonly MxTourStep[], options?: MxTourOptions): MxTourRef;
|
|
144
|
+
/** Regista um elemento com um nome (usado por `mxTourAnchor`). */
|
|
145
|
+
register(name: string, element: Element): void;
|
|
146
|
+
unregister(name: string, element: Element): void;
|
|
147
|
+
/** Encontra o elemento de um alvo: nome de âncora, selector CSS, elemento ou ElementRef. */
|
|
148
|
+
resolve(target: MxTourTarget | null | undefined): Element | null;
|
|
149
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<MxTour, never>;
|
|
150
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<MxTour>;
|
|
151
|
+
}
|
|
152
|
+
/** Dá um nome a um elemento para ser alvo de um passo: `mxTourAnchor="pesquisa"`. */
|
|
153
|
+
declare class MxTourAnchor {
|
|
154
|
+
readonly name: _angular_core.InputSignal<string>;
|
|
155
|
+
private readonly tour;
|
|
156
|
+
private readonly el;
|
|
157
|
+
constructor();
|
|
158
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<MxTourAnchor, never>;
|
|
159
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<MxTourAnchor, "[mxTourAnchor]", ["mxTourAnchor"], { "name": { "alias": "mxTourAnchor"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export { MX_TOUR_LABELS, MxTour, MxTourAnchor, MxTourPanel, MxTourRef };
|
|
163
|
+
export type { MxTourLabels, MxTourOptions, MxTourPlacement, MxTourResult, MxTourStep, MxTourStepContext, MxTourTarget };
|