@eagami/ui 5.41.0 → 5.41.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/fesm2022/eagami-ui.mjs +127 -76
- package/fesm2022/eagami-ui.mjs.map +1 -1
- package/package.json +1 -1
package/fesm2022/eagami-ui.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, makeEnvironmentProviders, provideEnvironmentInitializer, inject, CSP_NONCE, signal, effect, computed, Injectable, input, ChangeDetectionStrategy, Component, HostBinding, Directive, model, output, Injector, afterNextRender,
|
|
2
|
+
import { InjectionToken, makeEnvironmentProviders, provideEnvironmentInitializer, inject, CSP_NONCE, signal, effect, computed, Injectable, input, ChangeDetectionStrategy, Component, HostBinding, Directive, model, output, DestroyRef, Injector, afterNextRender, viewChild, ElementRef, untracked, HostListener, forwardRef, Renderer2, ViewContainerRef, TemplateRef, RendererStyleFlags2, ViewEncapsulation, contentChild, viewChildren, PLATFORM_ID, afterRenderEffect } from '@angular/core';
|
|
3
3
|
import { NgClass, NgComponentOutlet, NgTemplateOutlet, isPlatformBrowser } from '@angular/common';
|
|
4
|
-
import { NgControl, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
4
|
+
import { TouchedChangeEvent, NgControl, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
5
5
|
|
|
6
6
|
/** DI token carrying the consumer-supplied i18n configuration. */
|
|
7
7
|
const EAGAMI_I18N_CONFIG = new InjectionToken('EAGAMI_I18N_CONFIG');
|
|
@@ -4210,32 +4210,151 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
4210
4210
|
args: [{ selector: 'ea-field-messages', imports: [AlertCircleIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (error()) {\n <p\n class=\"ea-field-messages__message ea-field-messages__message--error\"\n [id]=\"id() + '-error'\"\n role=\"alert\">\n <ea-icon-alert-circle class=\"ea-field-messages__icon\" />\n {{ error() }}\n </p>\n} @else if (hint()) {\n <p\n class=\"ea-field-messages__message ea-field-messages__message--hint\"\n [id]=\"id() + '-hint'\">\n {{ hint() }}\n </p>\n}\n", styles: [":host{display:contents}.ea-field-messages__message{display:flex;align-items:center;gap:var(--space-1);font-size:max(var(--ea-field-messages-size, var(--text-helper-size)),.6875rem);font-weight:var(--text-helper-weight);line-height:var(--text-helper-lh)}.ea-field-messages__message--hint{color:var(--color-text-secondary)}.ea-field-messages__message--error{color:var(--color-error-default)}.ea-field-messages__icon{flex-shrink:0;font-size:1.2em;transform:translateY(-.0625em)}\n"] }]
|
|
4211
4211
|
}], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }] } });
|
|
4212
4212
|
|
|
4213
|
+
/**
|
|
4214
|
+
* Tracks where the press behind the current `click` started and ended.
|
|
4215
|
+
*
|
|
4216
|
+
* A `click` is dispatched on the nearest common ancestor of the press and the
|
|
4217
|
+
* release, so a drag that begins on a panel and ends on the backdrop arrives as
|
|
4218
|
+
* a click on the backdrop. Dismissal handlers that only read `event.target`
|
|
4219
|
+
* therefore treat "select text in a dialog, release outside it" as an outside
|
|
4220
|
+
* click, and shut the dialog under the user. Consulting both ends of the press
|
|
4221
|
+
* keeps a dismissal to presses that stayed outside from start to finish.
|
|
4222
|
+
*
|
|
4223
|
+
* Both ends read `null` for a click with no pointer behind it (keyboard
|
|
4224
|
+
* activation, `element.click()`), which every check below treats as a plain
|
|
4225
|
+
* click on its target.
|
|
4226
|
+
*
|
|
4227
|
+
* It also knows whether a press is still in progress, so work that would move
|
|
4228
|
+
* content under the pointer can wait until the press has landed its click.
|
|
4229
|
+
*/
|
|
4230
|
+
class PointerPressTracker {
|
|
4231
|
+
downTarget = null;
|
|
4232
|
+
upTarget = null;
|
|
4233
|
+
pressing = false;
|
|
4234
|
+
pressEndCallbacks = new Set();
|
|
4235
|
+
onDown = (event) => {
|
|
4236
|
+
this.downTarget = event.target;
|
|
4237
|
+
this.upTarget = null;
|
|
4238
|
+
this.pressing = true;
|
|
4239
|
+
};
|
|
4240
|
+
onUp = (event) => {
|
|
4241
|
+
this.upTarget = event.target;
|
|
4242
|
+
};
|
|
4243
|
+
// A click is already bound to its target by the time capture listeners run,
|
|
4244
|
+
// so work released from here can no longer redirect it
|
|
4245
|
+
onPressEnd = () => {
|
|
4246
|
+
this.pressing = false;
|
|
4247
|
+
const callbacks = [...this.pressEndCallbacks];
|
|
4248
|
+
this.pressEndCallbacks.clear();
|
|
4249
|
+
callbacks.forEach(callback => callback());
|
|
4250
|
+
};
|
|
4251
|
+
// A keyboard-driven click carries no pointer events of its own, and would
|
|
4252
|
+
// otherwise be read against whichever press came before it
|
|
4253
|
+
onKeydown = () => {
|
|
4254
|
+
this.downTarget = null;
|
|
4255
|
+
this.upTarget = null;
|
|
4256
|
+
this.onPressEnd();
|
|
4257
|
+
};
|
|
4258
|
+
constructor() {
|
|
4259
|
+
if (typeof document === 'undefined') {
|
|
4260
|
+
return;
|
|
4261
|
+
}
|
|
4262
|
+
// Capture phase, so a handler that stops propagation cannot hide the press
|
|
4263
|
+
document.addEventListener('pointerdown', this.onDown, true);
|
|
4264
|
+
document.addEventListener('pointerup', this.onUp, true);
|
|
4265
|
+
document.addEventListener('click', this.onPressEnd, true);
|
|
4266
|
+
document.addEventListener('pointercancel', this.onPressEnd, true);
|
|
4267
|
+
document.addEventListener('keydown', this.onKeydown, true);
|
|
4268
|
+
inject(DestroyRef).onDestroy(() => {
|
|
4269
|
+
document.removeEventListener('pointerdown', this.onDown, true);
|
|
4270
|
+
document.removeEventListener('pointerup', this.onUp, true);
|
|
4271
|
+
document.removeEventListener('click', this.onPressEnd, true);
|
|
4272
|
+
document.removeEventListener('pointercancel', this.onPressEnd, true);
|
|
4273
|
+
document.removeEventListener('keydown', this.onKeydown, true);
|
|
4274
|
+
});
|
|
4275
|
+
}
|
|
4276
|
+
/** True when either end of the press landed on `el` or inside it. */
|
|
4277
|
+
touchedInside(el) {
|
|
4278
|
+
if (!el) {
|
|
4279
|
+
return false;
|
|
4280
|
+
}
|
|
4281
|
+
return ((this.downTarget !== null && el.contains(this.downTarget)) ||
|
|
4282
|
+
(this.upTarget !== null && el.contains(this.upTarget)));
|
|
4283
|
+
}
|
|
4284
|
+
/** True when neither end of the press landed anywhere but `el` itself. */
|
|
4285
|
+
stayedOn(el) {
|
|
4286
|
+
if (!el) {
|
|
4287
|
+
return false;
|
|
4288
|
+
}
|
|
4289
|
+
return ((this.downTarget === null || this.downTarget === el) &&
|
|
4290
|
+
(this.upTarget === null || this.upTarget === el));
|
|
4291
|
+
}
|
|
4292
|
+
/**
|
|
4293
|
+
* Runs `callback` once the press in progress has landed its click or been
|
|
4294
|
+
* cancelled, or straight away when no press is in progress. Returns a
|
|
4295
|
+
* function that drops the pending call.
|
|
4296
|
+
*/
|
|
4297
|
+
whenPressEnds(callback) {
|
|
4298
|
+
if (!this.pressing) {
|
|
4299
|
+
callback();
|
|
4300
|
+
return () => undefined;
|
|
4301
|
+
}
|
|
4302
|
+
this.pressEndCallbacks.add(callback);
|
|
4303
|
+
return () => this.pressEndCallbacks.delete(callback);
|
|
4304
|
+
}
|
|
4305
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: PointerPressTracker, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4306
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: PointerPressTracker, providedIn: 'root' });
|
|
4307
|
+
}
|
|
4308
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: PointerPressTracker, decorators: [{
|
|
4309
|
+
type: Injectable,
|
|
4310
|
+
args: [{ providedIn: 'root' }]
|
|
4311
|
+
}], ctorParameters: () => [] });
|
|
4312
|
+
|
|
4213
4313
|
/**
|
|
4214
4314
|
* Derives a form control's display error from, in priority order: an explicit
|
|
4215
4315
|
* `errorMsg`, a consumer `errorMessages` override, then the library's localized
|
|
4216
4316
|
* default for the active `ValidationErrors` key. Auto-derived messages surface
|
|
4217
4317
|
* only once the bound control is `invalid && touched`, matching the moment a
|
|
4218
|
-
* user expects validation feedback.
|
|
4219
|
-
*
|
|
4220
|
-
*
|
|
4318
|
+
* user expects validation feedback. A message revealed by a pointer press
|
|
4319
|
+
* moving focus away waits until that press has landed its click. The control
|
|
4320
|
+
* arrives as a signal so callers decide how it is resolved: from the host's own
|
|
4321
|
+
* `NgControl` (CVA components) or from projected content (`<ea-form-field>`).
|
|
4221
4322
|
*/
|
|
4222
4323
|
function controlErrorStateFrom(control, config) {
|
|
4223
4324
|
const i18n = inject(EagamiI18nService);
|
|
4325
|
+
const press = inject(PointerPressTracker);
|
|
4224
4326
|
// Bumped on every status/touched change so the computed re-reads the control
|
|
4225
4327
|
const revision = signal(0, /* @ts-ignore */
|
|
4226
4328
|
...(ngDevMode ? [{ debugName: "revision" }] : /* istanbul ignore next */ []));
|
|
4329
|
+
// The message a blur reveals grows the field and shifts the layout under the
|
|
4330
|
+
// pointer, so while the press behind that blur is down the click would land
|
|
4331
|
+
// on whatever moved into its place
|
|
4332
|
+
const revealPending = signal(false, /* @ts-ignore */
|
|
4333
|
+
...(ngDevMode ? [{ debugName: "revealPending" }] : /* istanbul ignore next */ []));
|
|
4227
4334
|
effect(onCleanup => {
|
|
4228
4335
|
const ctrl = control();
|
|
4229
4336
|
if (!ctrl) {
|
|
4230
4337
|
return;
|
|
4231
4338
|
}
|
|
4232
|
-
|
|
4233
|
-
|
|
4339
|
+
let dropPendingReveal = null;
|
|
4340
|
+
const sub = ctrl.events.subscribe(event => {
|
|
4341
|
+
if (event instanceof TouchedChangeEvent && event.touched) {
|
|
4342
|
+
dropPendingReveal?.();
|
|
4343
|
+
revealPending.set(true);
|
|
4344
|
+
dropPendingReveal = press.whenPressEnds(() => revealPending.set(false));
|
|
4345
|
+
}
|
|
4346
|
+
revision.update(r => r + 1);
|
|
4347
|
+
});
|
|
4348
|
+
onCleanup(() => {
|
|
4349
|
+
sub.unsubscribe();
|
|
4350
|
+
dropPendingReveal?.();
|
|
4351
|
+
revealPending.set(false);
|
|
4352
|
+
});
|
|
4234
4353
|
});
|
|
4235
4354
|
const autoError = computed(() => {
|
|
4236
4355
|
revision();
|
|
4237
4356
|
const ctrl = control();
|
|
4238
|
-
if (!ctrl || !ctrl.invalid || !ctrl.touched || !ctrl.errors) {
|
|
4357
|
+
if (revealPending() || !ctrl || !ctrl.invalid || !ctrl.touched || !ctrl.errors) {
|
|
4239
4358
|
return null;
|
|
4240
4359
|
}
|
|
4241
4360
|
return resolveValidationMessage(ctrl.errors, config.errorMessages(), i18n.messages());
|
|
@@ -4315,74 +4434,6 @@ function isRtl(element) {
|
|
|
4315
4434
|
return getComputedStyle(element).direction === 'rtl';
|
|
4316
4435
|
}
|
|
4317
4436
|
|
|
4318
|
-
/**
|
|
4319
|
-
* Tracks where the press behind the current `click` started and ended.
|
|
4320
|
-
*
|
|
4321
|
-
* A `click` is dispatched on the nearest common ancestor of the press and the
|
|
4322
|
-
* release, so a drag that begins on a panel and ends on the backdrop arrives as
|
|
4323
|
-
* a click on the backdrop. Dismissal handlers that only read `event.target`
|
|
4324
|
-
* therefore treat "select text in a dialog, release outside it" as an outside
|
|
4325
|
-
* click, and shut the dialog under the user. Consulting both ends of the press
|
|
4326
|
-
* keeps a dismissal to presses that stayed outside from start to finish.
|
|
4327
|
-
*
|
|
4328
|
-
* Both ends read `null` for a click with no pointer behind it (keyboard
|
|
4329
|
-
* activation, `element.click()`), which every check below treats as a plain
|
|
4330
|
-
* click on its target.
|
|
4331
|
-
*/
|
|
4332
|
-
class PointerPressTracker {
|
|
4333
|
-
downTarget = null;
|
|
4334
|
-
upTarget = null;
|
|
4335
|
-
onDown = (event) => {
|
|
4336
|
-
this.downTarget = event.target;
|
|
4337
|
-
this.upTarget = null;
|
|
4338
|
-
};
|
|
4339
|
-
onUp = (event) => {
|
|
4340
|
-
this.upTarget = event.target;
|
|
4341
|
-
};
|
|
4342
|
-
// A keyboard-driven click carries no pointer events of its own, and would
|
|
4343
|
-
// otherwise be read against whichever press came before it
|
|
4344
|
-
onKeydown = () => {
|
|
4345
|
-
this.downTarget = null;
|
|
4346
|
-
this.upTarget = null;
|
|
4347
|
-
};
|
|
4348
|
-
constructor() {
|
|
4349
|
-
if (typeof document === 'undefined') {
|
|
4350
|
-
return;
|
|
4351
|
-
}
|
|
4352
|
-
// Capture phase, so a handler that stops propagation cannot hide the press
|
|
4353
|
-
document.addEventListener('pointerdown', this.onDown, true);
|
|
4354
|
-
document.addEventListener('pointerup', this.onUp, true);
|
|
4355
|
-
document.addEventListener('keydown', this.onKeydown, true);
|
|
4356
|
-
inject(DestroyRef).onDestroy(() => {
|
|
4357
|
-
document.removeEventListener('pointerdown', this.onDown, true);
|
|
4358
|
-
document.removeEventListener('pointerup', this.onUp, true);
|
|
4359
|
-
document.removeEventListener('keydown', this.onKeydown, true);
|
|
4360
|
-
});
|
|
4361
|
-
}
|
|
4362
|
-
/** True when either end of the press landed on `el` or inside it. */
|
|
4363
|
-
touchedInside(el) {
|
|
4364
|
-
if (!el) {
|
|
4365
|
-
return false;
|
|
4366
|
-
}
|
|
4367
|
-
return ((this.downTarget !== null && el.contains(this.downTarget)) ||
|
|
4368
|
-
(this.upTarget !== null && el.contains(this.upTarget)));
|
|
4369
|
-
}
|
|
4370
|
-
/** True when neither end of the press landed anywhere but `el` itself. */
|
|
4371
|
-
stayedOn(el) {
|
|
4372
|
-
if (!el) {
|
|
4373
|
-
return false;
|
|
4374
|
-
}
|
|
4375
|
-
return ((this.downTarget === null || this.downTarget === el) &&
|
|
4376
|
-
(this.upTarget === null || this.upTarget === el));
|
|
4377
|
-
}
|
|
4378
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: PointerPressTracker, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4379
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: PointerPressTracker, providedIn: 'root' });
|
|
4380
|
-
}
|
|
4381
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: PointerPressTracker, decorators: [{
|
|
4382
|
-
type: Injectable,
|
|
4383
|
-
args: [{ providedIn: 'root' }]
|
|
4384
|
-
}], ctorParameters: () => [] });
|
|
4385
|
-
|
|
4386
4437
|
/*
|
|
4387
4438
|
* A modal `<dialog>` (`showModal()`) renders in the browser's top layer, which
|
|
4388
4439
|
* paints above every z-index in the normal layer. An overlay portaled to
|