@ethlete/core 5.0.0-next.26 → 5.0.0-next.28
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/CHANGELOG.md +58 -0
- package/fesm2022/ethlete-core.mjs +782 -410
- package/fesm2022/ethlete-core.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ethlete-core.d.ts +79 -20
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, ElementRef, Directive, assertInInjectionContext, DestroyRef, TemplateRef, QueryList,
|
|
2
|
+
import { InjectionToken, inject, ElementRef, Directive, assertInInjectionContext, DestroyRef, TemplateRef, QueryList, RendererFactory2, RendererStyleFlags2, isSignal, DOCUMENT, signal, linkedSignal, isDevMode, computed, afterNextRender, effect, NgZone, untracked, EnvironmentInjector, runInInjectionContext, Injector, booleanAttribute, numberAttribute, PLATFORM_ID, ApplicationRef, createComponent, model, ViewContainerRef, input, output, Pipe, ViewEncapsulation, Component } from '@angular/core';
|
|
3
3
|
import { takeUntilDestroyed, toSignal, toObservable, outputFromObservable } from '@angular/core/rxjs-interop';
|
|
4
|
-
import { Subject, BehaviorSubject, debounceTime, combineLatest, map, merge, fromEvent, filter, tap, switchMap, startWith, of, timer, takeUntil, pairwise, distinctUntilChanged, take, Observable, concatMap, EMPTY, concat,
|
|
4
|
+
import { Subject, BehaviorSubject, debounceTime, combineLatest, map, merge, fromEvent, filter, tap, switchMap, startWith, of, defer, from, catchError, timer, takeUntil, pairwise, distinctUntilChanged, take, Observable, concatMap, EMPTY, concat, exhaustMap, share } from 'rxjs';
|
|
5
5
|
import { FormGroup, FormArray, FormControl } from '@angular/forms';
|
|
6
6
|
import { SIGNAL, signalSetFn } from '@angular/core/primitives/signals';
|
|
7
|
-
import { BreakpointObserver } from '@angular/cdk/layout';
|
|
8
7
|
import { coerceElement } from '@angular/cdk/coercion';
|
|
9
8
|
import { DOCUMENT as DOCUMENT$1, isPlatformBrowser } from '@angular/common';
|
|
9
|
+
import { BreakpointObserver } from '@angular/cdk/layout';
|
|
10
10
|
import { Router, NavigationEnd, NavigationSkipped } from '@angular/router';
|
|
11
11
|
import { Overlay } from '@angular/cdk/overlay';
|
|
12
12
|
import { ComponentPortal } from '@angular/cdk/portal';
|
|
@@ -132,6 +132,29 @@ const createComponentId = (prefix) => {
|
|
|
132
132
|
componentIds.set(prefix, id + 1);
|
|
133
133
|
return `${prefix}-${id}`;
|
|
134
134
|
};
|
|
135
|
+
let randomIdFallbackCounter = 0;
|
|
136
|
+
/**
|
|
137
|
+
* A unique id string. Uses `crypto.randomUUID()` when available, but falls back gracefully when it is
|
|
138
|
+
* not — crucially in **insecure contexts** (a plain-HTTP page served from a LAN IP, not `localhost`),
|
|
139
|
+
* where `crypto.randomUUID` is `undefined`. Calling it there throws, so anything using it during e.g.
|
|
140
|
+
* an overlay/menu open would break on such hosts. Prefer this over `crypto.randomUUID()` directly.
|
|
141
|
+
*/
|
|
142
|
+
const randomId = () => {
|
|
143
|
+
const cryptoObj = typeof globalThis !== 'undefined' ? globalThis.crypto : undefined;
|
|
144
|
+
if (typeof cryptoObj?.randomUUID === 'function') {
|
|
145
|
+
return cryptoObj.randomUUID();
|
|
146
|
+
}
|
|
147
|
+
// `getRandomValues` is available even in insecure contexts — build an RFC-4122-ish v4 id from it.
|
|
148
|
+
if (typeof cryptoObj?.getRandomValues === 'function') {
|
|
149
|
+
const bytes = Array.from(cryptoObj.getRandomValues(new Uint8Array(16)));
|
|
150
|
+
bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x40;
|
|
151
|
+
bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80;
|
|
152
|
+
const hex = bytes.map((byte) => (byte ?? 0).toString(16).padStart(2, '0'));
|
|
153
|
+
return `${hex.slice(0, 4).join('')}-${hex.slice(4, 6).join('')}-${hex.slice(6, 8).join('')}-${hex.slice(8, 10).join('')}-${hex.slice(10, 16).join('')}`;
|
|
154
|
+
}
|
|
155
|
+
// last resort: no Web Crypto at all — a session-unique (not cryptographic) id is enough for DOM ids
|
|
156
|
+
return `et-id-${(randomIdFallbackCounter++).toString(36)}-${Date.now().toString(36)}`;
|
|
157
|
+
};
|
|
135
158
|
|
|
136
159
|
const createDestroy = () => {
|
|
137
160
|
assertInInjectionContext(createDestroy);
|
|
@@ -360,6 +383,49 @@ const Validators = {
|
|
|
360
383
|
ValidateAtLeastOneRequired,
|
|
361
384
|
};
|
|
362
385
|
|
|
386
|
+
const copyToClipboard = (text) => defer(() => {
|
|
387
|
+
if (typeof navigator === 'undefined' || typeof document === 'undefined') {
|
|
388
|
+
return of(false);
|
|
389
|
+
}
|
|
390
|
+
if (navigator.clipboard) {
|
|
391
|
+
return from(navigator.clipboard.writeText(text)).pipe(map(() => true),
|
|
392
|
+
// The async Clipboard API can be blocked (missing permission, insecure context) — try the legacy path.
|
|
393
|
+
catchError(() => of(copyToClipboardViaExecCommand(text))));
|
|
394
|
+
}
|
|
395
|
+
return of(copyToClipboardViaExecCommand(text));
|
|
396
|
+
});
|
|
397
|
+
const readFromClipboard = () => defer(() => {
|
|
398
|
+
if (typeof navigator === 'undefined' || !navigator.clipboard) {
|
|
399
|
+
return of(null);
|
|
400
|
+
}
|
|
401
|
+
return from(navigator.clipboard.readText()).pipe(catchError(() => of(null)));
|
|
402
|
+
});
|
|
403
|
+
const copyToClipboardViaExecCommand = (text) => {
|
|
404
|
+
const previouslyFocusedElement = document.activeElement;
|
|
405
|
+
const textarea = document.createElement('textarea');
|
|
406
|
+
textarea.value = text;
|
|
407
|
+
textarea.setAttribute('readonly', '');
|
|
408
|
+
textarea.setAttribute('aria-hidden', 'true');
|
|
409
|
+
textarea.style.position = 'fixed';
|
|
410
|
+
textarea.style.top = '-9999px';
|
|
411
|
+
textarea.style.opacity = '0';
|
|
412
|
+
document.body.appendChild(textarea);
|
|
413
|
+
textarea.select();
|
|
414
|
+
textarea.setSelectionRange(0, text.length);
|
|
415
|
+
let didCopy = false;
|
|
416
|
+
try {
|
|
417
|
+
didCopy = document.execCommand('copy');
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
// execCommand throws in some environments instead of returning false.
|
|
421
|
+
}
|
|
422
|
+
textarea.remove();
|
|
423
|
+
if (previouslyFocusedElement instanceof HTMLElement) {
|
|
424
|
+
previouslyFocusedElement.focus();
|
|
425
|
+
}
|
|
426
|
+
return didCopy;
|
|
427
|
+
};
|
|
428
|
+
|
|
363
429
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
364
430
|
/* eslint-disable no-var */
|
|
365
431
|
/**
|
|
@@ -641,179 +707,189 @@ class KeyPressManager {
|
|
|
641
707
|
}
|
|
642
708
|
}
|
|
643
709
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
const duration = options.duration ?? 1000;
|
|
650
|
-
const easing = options.easing ?? easeOut;
|
|
651
|
-
const round = options.round ?? ((v) => Math.round(v));
|
|
652
|
-
const initialValue = options.initialValue ?? 0;
|
|
653
|
-
const animatedValue = signal(initialValue, /* @ts-ignore */
|
|
654
|
-
...(ngDevMode ? [{ debugName: "animatedValue" }] : /* istanbul ignore next */ []));
|
|
655
|
-
let rafId = null;
|
|
656
|
-
let currentAnimatedValue = initialValue;
|
|
657
|
-
const cancelAnimation = () => {
|
|
658
|
-
if (rafId !== null) {
|
|
659
|
-
cancelAnimationFrame(rafId);
|
|
660
|
-
rafId = null;
|
|
661
|
-
}
|
|
710
|
+
const [provideRenderer, injectRenderer] = createRootProvider(() => {
|
|
711
|
+
const rendererFactory = inject(RendererFactory2);
|
|
712
|
+
const renderer = rendererFactory.createRenderer(null, null);
|
|
713
|
+
const addClass = (element, ...classes) => {
|
|
714
|
+
classes.forEach((cls) => renderer.addClass(element, cls));
|
|
662
715
|
};
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
const
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
// Skip animation if no change
|
|
671
|
-
if (delta === 0) {
|
|
672
|
-
options.onAnimationEnd?.();
|
|
673
|
-
return readonlySignal;
|
|
716
|
+
const removeClass = (element, ...classes) => {
|
|
717
|
+
classes.forEach((cls) => renderer.removeClass(element, cls));
|
|
718
|
+
};
|
|
719
|
+
const toggleClass = (element, className, force) => {
|
|
720
|
+
const shouldAdd = force ?? !element.classList.contains(className);
|
|
721
|
+
if (shouldAdd) {
|
|
722
|
+
renderer.addClass(element, className);
|
|
674
723
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
const progress = Math.min(elapsed / duration, 1);
|
|
680
|
-
const easedProgress = easing(progress);
|
|
681
|
-
const value = startValue + delta * easedProgress;
|
|
682
|
-
currentAnimatedValue = value;
|
|
683
|
-
untracked(() => {
|
|
684
|
-
animatedValue.set(round(value));
|
|
685
|
-
});
|
|
686
|
-
if (progress < 1) {
|
|
687
|
-
rafId = requestAnimationFrame(animate);
|
|
688
|
-
}
|
|
689
|
-
else {
|
|
690
|
-
rafId = null;
|
|
691
|
-
ngZone.run(() => {
|
|
692
|
-
options.onAnimationEnd?.();
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
};
|
|
696
|
-
rafId = requestAnimationFrame(animate);
|
|
697
|
-
});
|
|
698
|
-
return readonlySignal;
|
|
724
|
+
else {
|
|
725
|
+
renderer.removeClass(element, className);
|
|
726
|
+
}
|
|
727
|
+
return shouldAdd;
|
|
699
728
|
};
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
729
|
+
const setStyle = (element, style, flags) => {
|
|
730
|
+
Object.entries(style).forEach(([key, value]) => {
|
|
731
|
+
if (!Number.isNaN(Number(key)))
|
|
732
|
+
return;
|
|
733
|
+
if (value !== null && value !== undefined) {
|
|
734
|
+
renderer.setStyle(element, key, value, flags);
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
renderer.removeStyle(element, key, flags);
|
|
738
|
+
}
|
|
739
|
+
});
|
|
705
740
|
};
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
741
|
+
const removeStyle = (element, style, flags) => {
|
|
742
|
+
if (!Number.isNaN(Number(style)))
|
|
743
|
+
return;
|
|
744
|
+
renderer.removeStyle(element, style, flags);
|
|
709
745
|
};
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
readonlySignal.stop = stop;
|
|
713
|
-
return readonlySignal;
|
|
714
|
-
};
|
|
715
|
-
/* Linear easing function */
|
|
716
|
-
const easeLinear = (t) => t;
|
|
717
|
-
/* Ease in (slow start) */
|
|
718
|
-
const easeIn = (t) => t * t;
|
|
719
|
-
/* Ease out (slow end) */
|
|
720
|
-
const easeOut = (t) => t * (2 - t);
|
|
721
|
-
/* Ease in-out (slow start and end) */
|
|
722
|
-
const easeInOut = (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t);
|
|
723
|
-
/** Elastic easing function (overshoots) */
|
|
724
|
-
const easeElastic = (t) => {
|
|
725
|
-
const c4 = (2 * Math.PI) / 3;
|
|
726
|
-
return t === 0 ? 0 : t === 1 ? 1 : Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
|
|
727
|
-
};
|
|
728
|
-
/** Back easing function (overshoots slightly then settles) */
|
|
729
|
-
const easeOutBack = (t) => {
|
|
730
|
-
const c1 = 1.70158;
|
|
731
|
-
const c3 = c1 + 1;
|
|
732
|
-
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
|
733
|
-
};
|
|
734
|
-
/** Back easing function with more overshoot */
|
|
735
|
-
const easeOutBackStrong = (t) => {
|
|
736
|
-
const c1 = 2.5;
|
|
737
|
-
const c3 = c1 + 1;
|
|
738
|
-
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
|
739
|
-
};
|
|
740
|
-
|
|
741
|
-
const BREAKPOINT_ORDER = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];
|
|
742
|
-
/**
|
|
743
|
-
* Default viewport config based on Tailwind CSS.
|
|
744
|
-
* @see https://tailwindcss.com/docs/screens
|
|
745
|
-
*/
|
|
746
|
-
const DEFAULT_VIEWPORT_CONFIG = {
|
|
747
|
-
breakpoints: {
|
|
748
|
-
xs: [0, 639],
|
|
749
|
-
sm: [640, 767],
|
|
750
|
-
md: [768, 1023],
|
|
751
|
-
lg: [1024, 1279],
|
|
752
|
-
xl: [1280, 1535],
|
|
753
|
-
'2xl': [1536, Infinity],
|
|
754
|
-
},
|
|
755
|
-
};
|
|
756
|
-
const [provideViewportConfig, injectViewportConfig] = createStaticRootProvider(DEFAULT_VIEWPORT_CONFIG, {
|
|
757
|
-
name: 'Viewport Config',
|
|
758
|
-
});
|
|
759
|
-
const [provideBreakpointObserver, injectBreakpointObserver] = createRootProvider(() => {
|
|
760
|
-
const breakpointObserver = inject(BreakpointObserver);
|
|
761
|
-
const viewportConfig = injectViewportConfig();
|
|
762
|
-
const isMediaQueryMatched = (mediaQuery) => breakpointObserver.isMatched(mediaQuery);
|
|
763
|
-
const observeMediaQuery = (mediaQuery) => {
|
|
764
|
-
return toSignal(breakpointObserver.observe(mediaQuery).pipe(map((x) => x.matches), startWith(isMediaQueryMatched(mediaQuery))), { requireSync: true });
|
|
746
|
+
const removeStyles = (element, ...styles) => {
|
|
747
|
+
styles.forEach((style) => removeStyle(element, style));
|
|
765
748
|
};
|
|
766
|
-
const
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
const index = option === 'min' ? 0 : 1;
|
|
770
|
-
const size = viewportConfig.breakpoints[type][index];
|
|
771
|
-
if (size === Infinity || size === 0) {
|
|
772
|
-
return size;
|
|
749
|
+
const setCssProperty = (element, name, value) => {
|
|
750
|
+
if (value !== null && value !== undefined) {
|
|
751
|
+
renderer.setStyle(element, name, value, RendererStyleFlags2.DashCase);
|
|
773
752
|
}
|
|
774
|
-
|
|
775
|
-
|
|
753
|
+
else {
|
|
754
|
+
renderer.removeStyle(element, name, RendererStyleFlags2.DashCase);
|
|
776
755
|
}
|
|
777
|
-
// Due to scaling, the actual size of the viewport may be a decimal number.
|
|
778
|
-
// Eg. on Windows 11 with 150% scaling, the viewport size may be 1535.33px
|
|
779
|
-
// and thus not matching any of the default breakpoints.
|
|
780
|
-
return size + 0.9;
|
|
781
756
|
};
|
|
782
|
-
const
|
|
783
|
-
|
|
784
|
-
|
|
757
|
+
const setCssProperties = (element, properties) => {
|
|
758
|
+
Object.entries(properties).forEach(([name, value]) => {
|
|
759
|
+
setCssProperty(element, name, value);
|
|
760
|
+
});
|
|
761
|
+
};
|
|
762
|
+
const setAttribute = (element, name, value, namespace) => {
|
|
763
|
+
if (value !== null && value !== undefined) {
|
|
764
|
+
renderer.setAttribute(element, name, value, namespace);
|
|
785
765
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
if (typeof options.min === 'number') {
|
|
789
|
-
mediaQueryParts.push(`(min-width: ${options.min}px)`);
|
|
790
|
-
}
|
|
791
|
-
else {
|
|
792
|
-
mediaQueryParts.push(`(min-width: ${getBreakpointSize(options.min, 'min')}px)`);
|
|
793
|
-
}
|
|
766
|
+
else {
|
|
767
|
+
renderer.removeAttribute(element, name, namespace);
|
|
794
768
|
}
|
|
795
|
-
|
|
796
|
-
|
|
769
|
+
};
|
|
770
|
+
const removeAttribute = (element, ...names) => {
|
|
771
|
+
names.forEach((name) => renderer.removeAttribute(element, name));
|
|
772
|
+
};
|
|
773
|
+
const setProperty = (element, name, value) => {
|
|
774
|
+
renderer.setProperty(element, name, value);
|
|
775
|
+
};
|
|
776
|
+
const setProperties = (element, properties) => {
|
|
777
|
+
Object.entries(properties).forEach(([name, value]) => {
|
|
778
|
+
renderer.setProperty(element, name, value);
|
|
779
|
+
});
|
|
780
|
+
};
|
|
781
|
+
const createElement = (tagName, namespace) => {
|
|
782
|
+
return renderer.createElement(tagName, namespace);
|
|
783
|
+
};
|
|
784
|
+
const createText = (value) => {
|
|
785
|
+
return renderer.createText(value);
|
|
786
|
+
};
|
|
787
|
+
const createComment = (value) => {
|
|
788
|
+
return renderer.createComment(value);
|
|
789
|
+
};
|
|
790
|
+
const appendChild = (parent, newChild) => {
|
|
791
|
+
renderer.appendChild(parent, newChild);
|
|
792
|
+
};
|
|
793
|
+
const insertBefore = (parent, newChild, refChild) => {
|
|
794
|
+
renderer.insertBefore(parent, newChild, refChild);
|
|
795
|
+
};
|
|
796
|
+
const removeChild = (parent, oldChild, isHostElement) => {
|
|
797
|
+
renderer.removeChild(parent, oldChild, isHostElement);
|
|
798
|
+
};
|
|
799
|
+
const selectRootElement = (selectorOrNode, preserveContent) => {
|
|
800
|
+
return renderer.selectRootElement(selectorOrNode, preserveContent);
|
|
801
|
+
};
|
|
802
|
+
const parentNode = (node) => {
|
|
803
|
+
return renderer.parentNode(node);
|
|
804
|
+
};
|
|
805
|
+
const nextSibling = (node) => {
|
|
806
|
+
return renderer.nextSibling(node);
|
|
807
|
+
};
|
|
808
|
+
const setValue = (node, value) => {
|
|
809
|
+
renderer.setValue(node, value);
|
|
810
|
+
};
|
|
811
|
+
const listen = (target, eventName, callback) => {
|
|
812
|
+
return renderer.listen(target, eventName, callback);
|
|
813
|
+
};
|
|
814
|
+
const destroyNode = (node) => {
|
|
815
|
+
if (renderer.destroyNode) {
|
|
816
|
+
renderer.destroyNode(node);
|
|
797
817
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
818
|
+
};
|
|
819
|
+
const replaceChild = (parent, newChild, oldChild) => {
|
|
820
|
+
renderer.insertBefore(parent, newChild, oldChild);
|
|
821
|
+
renderer.removeChild(parent, oldChild);
|
|
822
|
+
};
|
|
823
|
+
const empty = (element) => {
|
|
824
|
+
while (element.firstChild) {
|
|
825
|
+
renderer.removeChild(element, element.firstChild);
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
const setTextContent = (element, text) => {
|
|
829
|
+
renderer.setProperty(element, 'textContent', text);
|
|
830
|
+
};
|
|
831
|
+
const setInnerHTML = (element, html) => {
|
|
832
|
+
renderer.setProperty(element, 'innerHTML', html);
|
|
833
|
+
};
|
|
834
|
+
const setAttributes = (element, attributes, namespace) => {
|
|
835
|
+
Object.entries(attributes).forEach(([name, value]) => {
|
|
836
|
+
setAttribute(element, name, value, namespace);
|
|
837
|
+
});
|
|
838
|
+
};
|
|
839
|
+
const setDataAttributes = (element, data) => {
|
|
840
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
841
|
+
setAttribute(element, `data-${key}`, value);
|
|
842
|
+
});
|
|
843
|
+
};
|
|
844
|
+
const moveBefore = (config) => {
|
|
845
|
+
const { newParent, child, before = null } = config;
|
|
846
|
+
if ('moveBefore' in newParent) {
|
|
847
|
+
try {
|
|
848
|
+
newParent.moveBefore(child, before);
|
|
801
849
|
}
|
|
802
|
-
|
|
803
|
-
|
|
850
|
+
catch {
|
|
851
|
+
newParent.insertBefore(child, before);
|
|
804
852
|
}
|
|
805
853
|
}
|
|
806
|
-
|
|
854
|
+
else {
|
|
855
|
+
newParent.insertBefore(child, before);
|
|
856
|
+
}
|
|
807
857
|
};
|
|
808
858
|
return {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
859
|
+
addClass,
|
|
860
|
+
removeClass,
|
|
861
|
+
toggleClass,
|
|
862
|
+
setStyle,
|
|
863
|
+
removeStyle,
|
|
864
|
+
removeStyles,
|
|
865
|
+
setAttribute,
|
|
866
|
+
removeAttribute,
|
|
867
|
+
setProperty,
|
|
868
|
+
setProperties,
|
|
869
|
+
createElement,
|
|
870
|
+
createText,
|
|
871
|
+
createComment,
|
|
872
|
+
appendChild,
|
|
873
|
+
insertBefore,
|
|
874
|
+
removeChild,
|
|
875
|
+
selectRootElement,
|
|
876
|
+
parentNode,
|
|
877
|
+
nextSibling,
|
|
878
|
+
setValue,
|
|
879
|
+
listen,
|
|
880
|
+
destroyNode,
|
|
881
|
+
replaceChild,
|
|
882
|
+
empty,
|
|
883
|
+
setTextContent,
|
|
884
|
+
setInnerHTML,
|
|
885
|
+
setAttributes,
|
|
886
|
+
setDataAttributes,
|
|
887
|
+
setCssProperty,
|
|
888
|
+
setCssProperties,
|
|
889
|
+
moveBefore,
|
|
890
|
+
renderer,
|
|
815
891
|
};
|
|
816
|
-
}, { name: '
|
|
892
|
+
}, { name: 'Angular Renderer' });
|
|
817
893
|
|
|
818
894
|
const isElementSignal = (el) => {
|
|
819
895
|
if (!isSignal(el))
|
|
@@ -1255,6 +1331,270 @@ const injectScrollbarDimensions = memoizeSignal(() => {
|
|
|
1255
1331
|
});
|
|
1256
1332
|
});
|
|
1257
1333
|
|
|
1334
|
+
/**
|
|
1335
|
+
* Smoothly animates an element's `block-size` when its content changes size (e.g. a list filtering,
|
|
1336
|
+
* a loading state resolving). Set up once in an injection context; it observes the given content
|
|
1337
|
+
* element(s) and animates the host from its previous height to its new natural height.
|
|
1338
|
+
*
|
|
1339
|
+
* Robustness notes: the baseline height is captured on the first render (via `afterNextRender`) and
|
|
1340
|
+
* only later changes animate, so the initial layout settling never plays as a grow-from-0.
|
|
1341
|
+
* A change that interrupts an in-flight animation continues from the current animated height, and
|
|
1342
|
+
* zero/pre-layout measurements are ignored. Respects `prefers-reduced-motion`.
|
|
1343
|
+
*
|
|
1344
|
+
* The animation starts synchronously inside the `ResizeObserver` callback — it runs after layout
|
|
1345
|
+
* but **before paint**, so the host never paints a frame at its new natural size. Routing the
|
|
1346
|
+
* resize through a signal + effect instead would apply the animation one change-detection cycle
|
|
1347
|
+
* (= one painted frame) late: a growing panel would flash at its final size, snap back, and only
|
|
1348
|
+
* then animate.
|
|
1349
|
+
*/
|
|
1350
|
+
const injectAnimatedBlockSize = (config) => {
|
|
1351
|
+
const hostBinding = config.host ?? inject(ElementRef);
|
|
1352
|
+
const renderer = injectRenderer();
|
|
1353
|
+
const prefersReducedMotion = injectPrefersReducedMotion();
|
|
1354
|
+
const destroyRef = inject(DestroyRef);
|
|
1355
|
+
const hostSignal = firstElementSignal(buildElementSignal(hostBinding));
|
|
1356
|
+
const observeTargets = Array.isArray(config.observe) ? config.observe : [config.observe];
|
|
1357
|
+
const observedElements = observeTargets.map((target) => firstElementSignal(buildElementSignal(target)));
|
|
1358
|
+
const duration = config.duration ?? 160;
|
|
1359
|
+
const easing = config.easing ?? 'ease';
|
|
1360
|
+
let lastBlockSize = null;
|
|
1361
|
+
let animation = null;
|
|
1362
|
+
let ready = false;
|
|
1363
|
+
const hostElement = () => hostSignal().currentElement;
|
|
1364
|
+
const run = () => {
|
|
1365
|
+
const host = hostElement();
|
|
1366
|
+
if (!host)
|
|
1367
|
+
return;
|
|
1368
|
+
// continue from the current animated height when interrupting a running animation
|
|
1369
|
+
const from = animation?.playState === 'running' ? host.getBoundingClientRect().height : lastBlockSize;
|
|
1370
|
+
animation?.cancel();
|
|
1371
|
+
animation = null;
|
|
1372
|
+
const to = host.getBoundingClientRect().height;
|
|
1373
|
+
// never record or animate a zero / pre-layout height
|
|
1374
|
+
if (to === 0)
|
|
1375
|
+
return;
|
|
1376
|
+
lastBlockSize = to;
|
|
1377
|
+
if (from === null || from < 1 || Math.abs(from - to) < 1 || prefersReducedMotion()) {
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
if (config.resizingClass && renderer) {
|
|
1381
|
+
renderer.addClass(host, config.resizingClass);
|
|
1382
|
+
}
|
|
1383
|
+
const nextAnimation = host.animate([{ blockSize: `${from}px` }, { blockSize: `${to}px` }], { duration, easing });
|
|
1384
|
+
const cleanup = () => {
|
|
1385
|
+
if (config.resizingClass && renderer) {
|
|
1386
|
+
renderer.removeClass(host, config.resizingClass);
|
|
1387
|
+
}
|
|
1388
|
+
if (animation === nextAnimation) {
|
|
1389
|
+
animation = null;
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
nextAnimation.finished.then(cleanup).catch(cleanup);
|
|
1393
|
+
animation = nextAnimation;
|
|
1394
|
+
};
|
|
1395
|
+
// no zone/signal indirection — see the pre-paint timing note in the doc comment above
|
|
1396
|
+
const observer = new ResizeObserver(() => {
|
|
1397
|
+
if (!ready)
|
|
1398
|
+
return;
|
|
1399
|
+
run();
|
|
1400
|
+
});
|
|
1401
|
+
effect(() => {
|
|
1402
|
+
const elements = observedElements.map((element) => element().currentElement);
|
|
1403
|
+
untracked(() => {
|
|
1404
|
+
observer.disconnect();
|
|
1405
|
+
for (const element of elements) {
|
|
1406
|
+
if (element) {
|
|
1407
|
+
observer.observe(element);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1411
|
+
});
|
|
1412
|
+
// Capture the settled height of the first render as the baseline, then only animate later changes.
|
|
1413
|
+
afterNextRender(() => {
|
|
1414
|
+
const host = hostElement();
|
|
1415
|
+
lastBlockSize = host ? host.getBoundingClientRect().height || null : null;
|
|
1416
|
+
ready = true;
|
|
1417
|
+
});
|
|
1418
|
+
destroyRef.onDestroy(() => {
|
|
1419
|
+
observer.disconnect();
|
|
1420
|
+
animation?.cancel();
|
|
1421
|
+
});
|
|
1422
|
+
};
|
|
1423
|
+
|
|
1424
|
+
/**
|
|
1425
|
+
* Creates a signal that animates the transition of a numeric signal's value over time.
|
|
1426
|
+
*/
|
|
1427
|
+
const signalAnimatedNumber = (source, options = {}) => {
|
|
1428
|
+
const ngZone = inject(NgZone);
|
|
1429
|
+
const duration = options.duration ?? 1000;
|
|
1430
|
+
const easing = options.easing ?? easeOut;
|
|
1431
|
+
const round = options.round ?? ((v) => Math.round(v));
|
|
1432
|
+
const initialValue = options.initialValue ?? 0;
|
|
1433
|
+
const animatedValue = signal(initialValue, /* @ts-ignore */
|
|
1434
|
+
...(ngDevMode ? [{ debugName: "animatedValue" }] : /* istanbul ignore next */ []));
|
|
1435
|
+
let rafId = null;
|
|
1436
|
+
let currentAnimatedValue = initialValue;
|
|
1437
|
+
const cancelAnimation = () => {
|
|
1438
|
+
if (rafId !== null) {
|
|
1439
|
+
cancelAnimationFrame(rafId);
|
|
1440
|
+
rafId = null;
|
|
1441
|
+
}
|
|
1442
|
+
};
|
|
1443
|
+
const readonlySignal = animatedValue.asReadonly();
|
|
1444
|
+
const play = () => {
|
|
1445
|
+
const targetValue = isSignal(source) ? source() : source;
|
|
1446
|
+
cancelAnimation();
|
|
1447
|
+
const startValue = currentAnimatedValue;
|
|
1448
|
+
const startTime = performance.now();
|
|
1449
|
+
const delta = targetValue - startValue;
|
|
1450
|
+
// Skip animation if no change
|
|
1451
|
+
if (delta === 0) {
|
|
1452
|
+
options.onAnimationEnd?.();
|
|
1453
|
+
return readonlySignal;
|
|
1454
|
+
}
|
|
1455
|
+
options.onAnimationStart?.();
|
|
1456
|
+
ngZone.runOutsideAngular(() => {
|
|
1457
|
+
const animate = (timestamp) => {
|
|
1458
|
+
const elapsed = timestamp - startTime;
|
|
1459
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
1460
|
+
const easedProgress = easing(progress);
|
|
1461
|
+
const value = startValue + delta * easedProgress;
|
|
1462
|
+
currentAnimatedValue = value;
|
|
1463
|
+
untracked(() => {
|
|
1464
|
+
animatedValue.set(round(value));
|
|
1465
|
+
});
|
|
1466
|
+
if (progress < 1) {
|
|
1467
|
+
rafId = requestAnimationFrame(animate);
|
|
1468
|
+
}
|
|
1469
|
+
else {
|
|
1470
|
+
rafId = null;
|
|
1471
|
+
ngZone.run(() => {
|
|
1472
|
+
options.onAnimationEnd?.();
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1476
|
+
rafId = requestAnimationFrame(animate);
|
|
1477
|
+
});
|
|
1478
|
+
return readonlySignal;
|
|
1479
|
+
};
|
|
1480
|
+
const reset = () => {
|
|
1481
|
+
cancelAnimation();
|
|
1482
|
+
currentAnimatedValue = initialValue;
|
|
1483
|
+
animatedValue.set(initialValue);
|
|
1484
|
+
return readonlySignal;
|
|
1485
|
+
};
|
|
1486
|
+
const stop = () => {
|
|
1487
|
+
cancelAnimation();
|
|
1488
|
+
return readonlySignal;
|
|
1489
|
+
};
|
|
1490
|
+
readonlySignal.play = play;
|
|
1491
|
+
readonlySignal.reset = reset;
|
|
1492
|
+
readonlySignal.stop = stop;
|
|
1493
|
+
return readonlySignal;
|
|
1494
|
+
};
|
|
1495
|
+
/* Linear easing function */
|
|
1496
|
+
const easeLinear = (t) => t;
|
|
1497
|
+
/* Ease in (slow start) */
|
|
1498
|
+
const easeIn = (t) => t * t;
|
|
1499
|
+
/* Ease out (slow end) */
|
|
1500
|
+
const easeOut = (t) => t * (2 - t);
|
|
1501
|
+
/* Ease in-out (slow start and end) */
|
|
1502
|
+
const easeInOut = (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t);
|
|
1503
|
+
/** Elastic easing function (overshoots) */
|
|
1504
|
+
const easeElastic = (t) => {
|
|
1505
|
+
const c4 = (2 * Math.PI) / 3;
|
|
1506
|
+
return t === 0 ? 0 : t === 1 ? 1 : Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
|
|
1507
|
+
};
|
|
1508
|
+
/** Back easing function (overshoots slightly then settles) */
|
|
1509
|
+
const easeOutBack = (t) => {
|
|
1510
|
+
const c1 = 1.70158;
|
|
1511
|
+
const c3 = c1 + 1;
|
|
1512
|
+
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
|
1513
|
+
};
|
|
1514
|
+
/** Back easing function with more overshoot */
|
|
1515
|
+
const easeOutBackStrong = (t) => {
|
|
1516
|
+
const c1 = 2.5;
|
|
1517
|
+
const c3 = c1 + 1;
|
|
1518
|
+
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
|
1519
|
+
};
|
|
1520
|
+
|
|
1521
|
+
const BREAKPOINT_ORDER = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];
|
|
1522
|
+
/**
|
|
1523
|
+
* Default viewport config based on Tailwind CSS.
|
|
1524
|
+
* @see https://tailwindcss.com/docs/screens
|
|
1525
|
+
*/
|
|
1526
|
+
const DEFAULT_VIEWPORT_CONFIG = {
|
|
1527
|
+
breakpoints: {
|
|
1528
|
+
xs: [0, 639],
|
|
1529
|
+
sm: [640, 767],
|
|
1530
|
+
md: [768, 1023],
|
|
1531
|
+
lg: [1024, 1279],
|
|
1532
|
+
xl: [1280, 1535],
|
|
1533
|
+
'2xl': [1536, Infinity],
|
|
1534
|
+
},
|
|
1535
|
+
};
|
|
1536
|
+
const [provideViewportConfig, injectViewportConfig] = createStaticRootProvider(DEFAULT_VIEWPORT_CONFIG, {
|
|
1537
|
+
name: 'Viewport Config',
|
|
1538
|
+
});
|
|
1539
|
+
const [provideBreakpointObserver, injectBreakpointObserver] = createRootProvider(() => {
|
|
1540
|
+
const breakpointObserver = inject(BreakpointObserver);
|
|
1541
|
+
const viewportConfig = injectViewportConfig();
|
|
1542
|
+
const isMediaQueryMatched = (mediaQuery) => breakpointObserver.isMatched(mediaQuery);
|
|
1543
|
+
const observeMediaQuery = (mediaQuery) => {
|
|
1544
|
+
return toSignal(breakpointObserver.observe(mediaQuery).pipe(map((x) => x.matches), startWith(isMediaQueryMatched(mediaQuery))), { requireSync: true });
|
|
1545
|
+
};
|
|
1546
|
+
const observeBreakpoint = (options) => observeMediaQuery(buildMediaQueryString(options));
|
|
1547
|
+
const isBreakpointMatched = (options) => isMediaQueryMatched(buildMediaQueryString(options));
|
|
1548
|
+
const getBreakpointSize = (type, option) => {
|
|
1549
|
+
const index = option === 'min' ? 0 : 1;
|
|
1550
|
+
const size = viewportConfig.breakpoints[type][index];
|
|
1551
|
+
if (size === Infinity || size === 0) {
|
|
1552
|
+
return size;
|
|
1553
|
+
}
|
|
1554
|
+
if (option === 'min') {
|
|
1555
|
+
return size;
|
|
1556
|
+
}
|
|
1557
|
+
// Due to scaling, the actual size of the viewport may be a decimal number.
|
|
1558
|
+
// Eg. on Windows 11 with 150% scaling, the viewport size may be 1535.33px
|
|
1559
|
+
// and thus not matching any of the default breakpoints.
|
|
1560
|
+
return size + 0.9;
|
|
1561
|
+
};
|
|
1562
|
+
const buildMediaQueryString = (options) => {
|
|
1563
|
+
if (!options.min && !options.max) {
|
|
1564
|
+
throw new Error('At least one of min or max must be defined');
|
|
1565
|
+
}
|
|
1566
|
+
const mediaQueryParts = [];
|
|
1567
|
+
if (options.min) {
|
|
1568
|
+
if (typeof options.min === 'number') {
|
|
1569
|
+
mediaQueryParts.push(`(min-width: ${options.min}px)`);
|
|
1570
|
+
}
|
|
1571
|
+
else {
|
|
1572
|
+
mediaQueryParts.push(`(min-width: ${getBreakpointSize(options.min, 'min')}px)`);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
if (options.min && options.max) {
|
|
1576
|
+
mediaQueryParts.push('and');
|
|
1577
|
+
}
|
|
1578
|
+
if (options.max) {
|
|
1579
|
+
if (typeof options.max === 'number') {
|
|
1580
|
+
mediaQueryParts.push(`(max-width: ${options.max}px)`);
|
|
1581
|
+
}
|
|
1582
|
+
else {
|
|
1583
|
+
mediaQueryParts.push(`(max-width: ${getBreakpointSize(options.max, 'max')}px)`);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
return mediaQueryParts.join(' ');
|
|
1587
|
+
};
|
|
1588
|
+
return {
|
|
1589
|
+
observeBreakpoint,
|
|
1590
|
+
isBreakpointMatched,
|
|
1591
|
+
getBreakpointSize,
|
|
1592
|
+
buildMediaQueryString,
|
|
1593
|
+
observeMediaQuery,
|
|
1594
|
+
isMediaQueryMatched,
|
|
1595
|
+
};
|
|
1596
|
+
}, { name: 'Breakpoint Observer' });
|
|
1597
|
+
|
|
1258
1598
|
const BREAKPOINT_KEY_SET = new Set(BREAKPOINT_ORDER);
|
|
1259
1599
|
const isBreakpointMap = (value) => {
|
|
1260
1600
|
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
@@ -2643,6 +2983,20 @@ const createLogger = (config) => {
|
|
|
2643
2983
|
};
|
|
2644
2984
|
|
|
2645
2985
|
const escapeHtml = (str) => str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
2986
|
+
/** Escapes HTML-special characters but leaves existing entity references intact — for input that
|
|
2987
|
+
* may already contain entities (e.g. Markdown serialized from the editor's DOM, where `&` is
|
|
2988
|
+
* stored as `&`), where plain escaping would double-escape them. */
|
|
2989
|
+
const escapeHtmlPreservingEntities = (str) => str
|
|
2990
|
+
.replace(/&(?![a-z]+;|#\d+;|#x[0-9a-f]+;)/gi, '&')
|
|
2991
|
+
.replace(/</g, '<')
|
|
2992
|
+
.replace(/>/g, '>')
|
|
2993
|
+
.replace(/"/g, '"');
|
|
2994
|
+
/** Rejects URL schemes that execute script when navigated. Whitespace/control characters are
|
|
2995
|
+
* stripped before checking — browsers ignore them inside URLs, so `java\tscript:` would
|
|
2996
|
+
* otherwise slip through. */
|
|
2997
|
+
const isSafeUrl = (url) =>
|
|
2998
|
+
// eslint-disable-next-line no-control-regex -- stripping control characters is the point: browsers ignore them inside URLs
|
|
2999
|
+
!/^(javascript|data|vbscript):/i.test(url.replace(/[\s\u0000-\u001f]/g, ''));
|
|
2646
3000
|
const unescapeHtml = (str) => str
|
|
2647
3001
|
.replace(/&/g, '&')
|
|
2648
3002
|
.replace(/</g, '<')
|
|
@@ -2656,30 +3010,183 @@ const parseTableRow = (line) => line
|
|
|
2656
3010
|
.slice(1, -1)
|
|
2657
3011
|
.map((cell) => cell.trim());
|
|
2658
3012
|
const isTableSeparatorLine = (line) => /^\|?(\s*:?-+:?\s*\|)+\s*$/.test(line);
|
|
3013
|
+
/** Column alignments from a GFM separator line: `:---` left, `:---:` center, `---:` right. */
|
|
3014
|
+
const parseSeparatorAligns = (line) => parseTableRow(line).map((cell) => {
|
|
3015
|
+
const left = cell.startsWith(':');
|
|
3016
|
+
const right = cell.endsWith(':');
|
|
3017
|
+
return left && right ? 'center' : right ? 'right' : left ? 'left' : null;
|
|
3018
|
+
});
|
|
3019
|
+
/** The GFM separator token for a column alignment. */
|
|
3020
|
+
const separatorFor = (align) => align === 'center' ? ':---:' : align === 'right' ? '---:' : align === 'left' ? ':---' : '---';
|
|
3021
|
+
const alignStyle = (align) => (align ? ` style="text-align: ${align}"` : '');
|
|
3022
|
+
/** Wraps `content` in an emphasis `marker`, hoisting boundary whitespace outside the delimiters —
|
|
3023
|
+
* CommonMark emphasis must not face whitespace on the inside (`** fett**` doesn't parse), and
|
|
3024
|
+
* ` ` counts as whitespace there too. Whitespace-only content stays unwrapped. */
|
|
3025
|
+
const emphasize = (marker, content) => {
|
|
3026
|
+
const lead = /^(?:\s| )+/i.exec(content)?.[0] ?? '';
|
|
3027
|
+
const rest = content.slice(lead.length);
|
|
3028
|
+
const trail = /(?:\s| )+$/i.exec(rest)?.[0] ?? '';
|
|
3029
|
+
const core = rest.slice(0, rest.length - trail.length);
|
|
3030
|
+
return core ? `${lead}${marker}${core}${marker}${trail}` : content;
|
|
3031
|
+
};
|
|
2659
3032
|
const makePlaceholder = (kind, idx) => `\u{E000}${kind}${idx}\u{E001}`;
|
|
2660
3033
|
const placeholderRe = (kind) => new RegExp(`\u{E000}${kind}(\\d+)\u{E001}`, 'gu');
|
|
2661
3034
|
const isPlaceholder = (kind, str) => new RegExp(`^\u{E000}${kind}\\d+\u{E001}$`, 'u').test(str);
|
|
3035
|
+
/** Tags allowed to survive inside an aligned block's raw-HTML passthrough. */
|
|
3036
|
+
const SAFE_INLINE_TAGS = new Set(['strong', 'em', 'b', 'i', 'del', 's', 'u', 'code', 'br']);
|
|
3037
|
+
/** Reduces raw inline HTML to the editor's own vocabulary: allowed tags lose all their attributes
|
|
3038
|
+
* (`<a>` keeps a safe `href`), everything else — including any event-handler attribute — is
|
|
3039
|
+
* dropped, and the remaining text is escaped. */
|
|
3040
|
+
const sanitizeInlineHtml = (html) => {
|
|
3041
|
+
const kept = [];
|
|
3042
|
+
const stashed = html.replace(/<\s*(\/?)\s*([a-z][a-z0-9]*)\b[^>]*>/gi, (full, closing, tag) => {
|
|
3043
|
+
const name = tag.toLowerCase();
|
|
3044
|
+
if (name === 'a') {
|
|
3045
|
+
const href = closing ? null : /\bhref\s*=\s*"([^"]*)"/i.exec(full)?.[1];
|
|
3046
|
+
const anchor = closing ? '</a>' : href && isSafeUrl(href) ? `<a href="${href}">` : '<a>';
|
|
3047
|
+
return makePlaceholder('TAG', kept.push(anchor) - 1);
|
|
3048
|
+
}
|
|
3049
|
+
if (!SAFE_INLINE_TAGS.has(name))
|
|
3050
|
+
return '';
|
|
3051
|
+
return makePlaceholder('TAG', kept.push(closing ? `</${name}>` : `<${name}>`) - 1);
|
|
3052
|
+
});
|
|
3053
|
+
return escapeHtmlPreservingEntities(stashed).replace(placeholderRe('TAG'), (_, i) => kept[+i] ?? '');
|
|
3054
|
+
};
|
|
2662
3055
|
const processInline = (text) => {
|
|
2663
3056
|
const inlineCodes = [];
|
|
2664
3057
|
text = text.replace(/`([^`]+)`/g, (_, code) => {
|
|
2665
3058
|
const idx = inlineCodes.push(`<code>${escapeHtml(code)}</code>`) - 1;
|
|
2666
3059
|
return makePlaceholder('IC', idx);
|
|
2667
3060
|
});
|
|
2668
|
-
//
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
text = text.replace(
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
3061
|
+
// "Open in new tab" links round-trip as raw HTML because Markdown has no `target` syntax. Extract
|
|
3062
|
+
// them before the raw-HTML escape below, keeping only a safe href, `target="_blank"` and a forced
|
|
3063
|
+
// `rel="noopener noreferrer"` (any other attribute — including event handlers — is dropped); their
|
|
3064
|
+
// inner markup is processed recursively. Anything else that looks like raw HTML is still escaped.
|
|
3065
|
+
const newTabLinks = [];
|
|
3066
|
+
text = text.replace(/<a\b([^>]*)>([\s\S]*?)<\/a>/gi, (full, attrs, inner) => {
|
|
3067
|
+
const href = /\bhref\s*=\s*"([^"]*)"/i.exec(attrs)?.[1];
|
|
3068
|
+
const newTab = /\btarget\s*=\s*"_blank"/i.test(attrs);
|
|
3069
|
+
if (!newTab || !href || !isSafeUrl(href))
|
|
3070
|
+
return full;
|
|
3071
|
+
const anchor = `<a href="${href}" target="_blank" rel="noopener noreferrer">${processInline(inner)}</a>`;
|
|
3072
|
+
return makePlaceholder('NTLINK', newTabLinks.push(anchor) - 1);
|
|
3073
|
+
});
|
|
3074
|
+
// Raw HTML in Markdown text is escaped, not rendered — the editor writes this HTML straight
|
|
3075
|
+
// into the DOM via innerHTML, so anything else would let a crafted value inject markup. `<u>`
|
|
3076
|
+
// is the one deliberate exception: underline has no Markdown form and round-trips as raw <u>.
|
|
3077
|
+
text = escapeHtmlPreservingEntities(text).replace(/<(\/?)u>/gi, '<$1u>');
|
|
3078
|
+
// Bold + italic — *** before ** before *. A delimiter flanking whitespace doesn't open/close a
|
|
3079
|
+
// run (CommonMark's flanking rule), so literal asterisks as in `2 * 3 * 4` stay text.
|
|
3080
|
+
text = text.replace(/\*\*\*([^\s*](?:.*?[^\s*])?)\*\*\*/g, '<strong><em>$1</em></strong>');
|
|
3081
|
+
text = text.replace(/\*\*([^\s*](?:.*?[^\s*])?)\*\*/g, '<strong>$1</strong>');
|
|
3082
|
+
text = text.replace(/__([^\s_](?:.*?[^\s_])?)__/g, '<strong>$1</strong>');
|
|
3083
|
+
text = text.replace(/\*([^\s*](?:.*?[^\s*])?)\*/g, '<em>$1</em>');
|
|
3084
|
+
// Single-underscore emphasis must not fire inside a word (snake_case identifiers).
|
|
3085
|
+
text = text.replace(/(?<![\w*_])_([^\s_](?:.*?[^\s_])?)_(?![\w_])/g, '<em>$1</em>');
|
|
3086
|
+
text = text.replace(/~~([^\s~](?:.*?[^\s~])?)~~/g, '<del>$1</del>');
|
|
3087
|
+
// Images before links (order matters); URLs with script-running schemes stay literal text
|
|
3088
|
+
text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => isSafeUrl(src) ? `<img src="${src}" alt="${alt}">` : match);
|
|
3089
|
+
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, label, href) => isSafeUrl(href) ? `<a href="${href}">${label}</a>` : match);
|
|
3090
|
+
return text
|
|
3091
|
+
.replace(placeholderRe('IC'), (_, i) => inlineCodes[+i] ?? '')
|
|
3092
|
+
.replace(placeholderRe('NTLINK'), (_, i) => newTabLinks[+i] ?? '');
|
|
3093
|
+
};
|
|
3094
|
+
// --- Nested list helpers (regex can't match balanced nesting, so these scan by tag depth) ---
|
|
3095
|
+
/** The first top-level `<ul>`/`<ol>` in `html`, matched with balanced nesting. */
|
|
3096
|
+
const findList = (html) => {
|
|
3097
|
+
const open = /<(ul|ol)\b[^>]*>/i.exec(html);
|
|
3098
|
+
if (!open)
|
|
3099
|
+
return null;
|
|
3100
|
+
const innerStart = open.index + open[0].length;
|
|
3101
|
+
const tag = /<(\/?)(?:ul|ol)\b[^>]*>/gi;
|
|
3102
|
+
tag.lastIndex = innerStart;
|
|
3103
|
+
let depth = 1;
|
|
3104
|
+
let match;
|
|
3105
|
+
while ((match = tag.exec(html))) {
|
|
3106
|
+
depth += match[1] ? -1 : 1;
|
|
3107
|
+
if (depth === 0) {
|
|
3108
|
+
return {
|
|
3109
|
+
start: open.index,
|
|
3110
|
+
end: tag.lastIndex,
|
|
3111
|
+
ordered: open[1].toLowerCase() === 'ol',
|
|
3112
|
+
inner: html.slice(innerStart, match.index),
|
|
3113
|
+
};
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
return null;
|
|
3117
|
+
};
|
|
3118
|
+
/** The inner HTML of each direct `<li>` child of a list's inner HTML (balanced over nested lists). */
|
|
3119
|
+
const findListItems = (inner) => {
|
|
3120
|
+
const items = [];
|
|
3121
|
+
const tag = /<(\/?)li\b[^>]*>/gi;
|
|
3122
|
+
let depth = 0;
|
|
3123
|
+
let start = -1;
|
|
3124
|
+
let match;
|
|
3125
|
+
while ((match = tag.exec(inner))) {
|
|
3126
|
+
if (match[1]) {
|
|
3127
|
+
depth--;
|
|
3128
|
+
if (depth === 0 && start >= 0) {
|
|
3129
|
+
items.push(inner.slice(start, match.index));
|
|
3130
|
+
start = -1;
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
else {
|
|
3134
|
+
if (depth === 0)
|
|
3135
|
+
start = match.index + match[0].length;
|
|
3136
|
+
depth++;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
return items;
|
|
3140
|
+
};
|
|
3141
|
+
/** Serializes a list's inner HTML to Markdown, indenting nested lists two spaces per level. */
|
|
3142
|
+
const listToMarkdown = (inner, ordered, depth) => {
|
|
3143
|
+
const indent = ' '.repeat(depth);
|
|
3144
|
+
let n = 1;
|
|
3145
|
+
return findListItems(inner)
|
|
3146
|
+
.map((itemInner) => {
|
|
3147
|
+
let content = itemInner;
|
|
3148
|
+
let nestedMarkdown = '';
|
|
3149
|
+
for (let nested = findList(content); nested; nested = findList(content)) {
|
|
3150
|
+
nestedMarkdown += `\n${listToMarkdown(nested.inner, nested.ordered, depth + 1)}`;
|
|
3151
|
+
content = content.slice(0, nested.start) + content.slice(nested.end);
|
|
3152
|
+
}
|
|
3153
|
+
const marker = ordered ? `${n++}. ` : '- ';
|
|
3154
|
+
// list items are single-line in this serializer, so a <br> degrades to a space
|
|
3155
|
+
return `${indent}${marker}${stripTags(content.replace(/<br\s*\/?>/gi, ' ')).trim()}${nestedMarkdown}`;
|
|
3156
|
+
})
|
|
3157
|
+
.join('\n');
|
|
3158
|
+
};
|
|
3159
|
+
/** Parses list lines into (indent-level, ordered, text) — two leading spaces per nesting level. */
|
|
3160
|
+
const parseListLines = (lines) => lines
|
|
3161
|
+
.map((line) => {
|
|
3162
|
+
const match = /^(\s*)([-*+]|\d+\.)\s+(.*)$/.exec(line);
|
|
3163
|
+
return match
|
|
3164
|
+
? { indent: Math.floor((match[1] ?? '').length / 2), ordered: /\d/.test(match[2] ?? ''), text: match[3] ?? '' }
|
|
3165
|
+
: null;
|
|
3166
|
+
})
|
|
3167
|
+
.filter((line) => line !== null);
|
|
3168
|
+
/** Builds nested `<ul>`/`<ol>` HTML from parsed list lines starting at `start`, for one indent level. */
|
|
3169
|
+
const buildListHtml = (lines, start, baseIndent) => {
|
|
3170
|
+
const ordered = lines[start]?.ordered ?? false;
|
|
3171
|
+
let items = '';
|
|
3172
|
+
let i = start;
|
|
3173
|
+
while (i < lines.length && (lines[i]?.indent ?? -1) === baseIndent) {
|
|
3174
|
+
let item = processInline(lines[i].text);
|
|
3175
|
+
i++;
|
|
3176
|
+
if (i < lines.length && (lines[i]?.indent ?? -1) > baseIndent) {
|
|
3177
|
+
const nested = buildListHtml(lines, i, lines[i].indent);
|
|
3178
|
+
item += nested.html;
|
|
3179
|
+
i = nested.next;
|
|
3180
|
+
}
|
|
3181
|
+
items += `<li>${item}</li>`;
|
|
3182
|
+
}
|
|
3183
|
+
const tag = ordered ? 'ol' : 'ul';
|
|
3184
|
+
return { html: `<${tag}>${items}</${tag}>`, next: i };
|
|
2678
3185
|
};
|
|
2679
3186
|
/**
|
|
2680
3187
|
* Converts a markdown string to HTML.
|
|
2681
3188
|
* Covers headings, bold, italic, strikethrough, inline code, fenced code blocks,
|
|
2682
|
-
* links, images, block quotes, unordered/ordered lists, tables, horizontal rules, and paragraphs.
|
|
3189
|
+
* links, images, block quotes, unordered/ordered (and nested) lists, tables, horizontal rules, and paragraphs.
|
|
2683
3190
|
*/
|
|
2684
3191
|
const markdownToHtml = (markdown) => {
|
|
2685
3192
|
if (!markdown)
|
|
@@ -2704,6 +3211,16 @@ const markdownToHtml = (markdown) => {
|
|
|
2704
3211
|
return '';
|
|
2705
3212
|
if (isPlaceholder('CODE', trimmed))
|
|
2706
3213
|
return trimmed;
|
|
3214
|
+
// Aligned block: text-align has no Markdown form, so it round-trips as raw native HTML.
|
|
3215
|
+
// Rebuild it instead of passing it through verbatim: only the alignment survives on the tag
|
|
3216
|
+
// (no other attributes, e.g. event handlers) and the inner markup is reduced to the editor's
|
|
3217
|
+
// own inline vocabulary.
|
|
3218
|
+
const aligned = /^<(p|h[1-6]|div)\b[^>]*\bstyle=["'][^"']*text-align:\s*([a-z]+)[^>]*>([\s\S]*)<\/\1>$/i.exec(trimmed);
|
|
3219
|
+
if (aligned) {
|
|
3220
|
+
const tag = (aligned[1] ?? 'p').toLowerCase();
|
|
3221
|
+
const align = (aligned[2] ?? 'left').toLowerCase();
|
|
3222
|
+
return `<${tag} style="text-align: ${align}">${sanitizeInlineHtml(aligned[3] ?? '')}</${tag}>`;
|
|
3223
|
+
}
|
|
2707
3224
|
// Heading
|
|
2708
3225
|
const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)$/);
|
|
2709
3226
|
if (headingMatch?.[1] && headingMatch?.[2]) {
|
|
@@ -2713,46 +3230,39 @@ const markdownToHtml = (markdown) => {
|
|
|
2713
3230
|
// Horizontal rule
|
|
2714
3231
|
if (/^(---|\*\*\*|___)\s*$/.test(trimmed))
|
|
2715
3232
|
return '<hr>';
|
|
2716
|
-
// Table (GFM)
|
|
3233
|
+
// Table (GFM) — column alignment from the separator line lands as text-align on every cell
|
|
2717
3234
|
const tableLines = trimmed.split('\n');
|
|
2718
3235
|
if (tableLines.length >= 2 && /\|/.test(tableLines[0] ?? '') && isTableSeparatorLine(tableLines[1] ?? '')) {
|
|
3236
|
+
const aligns = parseSeparatorAligns(tableLines[1] ?? '');
|
|
2719
3237
|
const headers = parseTableRow(tableLines[0] ?? '');
|
|
2720
|
-
const thead = `<thead><tr>${headers
|
|
3238
|
+
const thead = `<thead><tr>${headers
|
|
3239
|
+
.map((h, i) => `<th${alignStyle(aligns[i] ?? null)}>${processInline(h)}</th>`)
|
|
3240
|
+
.join('')}</tr></thead>`;
|
|
2721
3241
|
const bodyRows = tableLines.slice(2);
|
|
2722
3242
|
const tbody = bodyRows.length > 0
|
|
2723
3243
|
? `<tbody>${bodyRows
|
|
2724
3244
|
.map((row) => `<tr>${parseTableRow(row)
|
|
2725
|
-
.map((cell) => `<td>${processInline(cell)}</td>`)
|
|
3245
|
+
.map((cell, i) => `<td${alignStyle(aligns[i] ?? null)}>${processInline(cell)}</td>`)
|
|
2726
3246
|
.join('')}</tr>`)
|
|
2727
3247
|
.join('')}</tbody>`
|
|
2728
3248
|
: '';
|
|
2729
3249
|
return `<table>${thead}${tbody}</table>`;
|
|
2730
3250
|
}
|
|
2731
|
-
// Blockquote
|
|
3251
|
+
// Blockquote — each line runs through the inline pass on its own (processInline escapes raw
|
|
3252
|
+
// HTML, so joining first would escape the <br> separators too)
|
|
2732
3253
|
if (/^> /.test(trimmed)) {
|
|
2733
3254
|
const content = trimmed
|
|
2734
3255
|
.split('\n')
|
|
2735
|
-
.map((l) => l.replace(/^>\s?/, ''))
|
|
3256
|
+
.map((l) => processInline(l.replace(/^>\s?/, '')))
|
|
2736
3257
|
.join('<br>');
|
|
2737
|
-
return `<blockquote>${
|
|
3258
|
+
return `<blockquote>${content}</blockquote>`;
|
|
2738
3259
|
}
|
|
2739
|
-
//
|
|
2740
|
-
if (/^[-*+]\s/.test(trimmed)) {
|
|
2741
|
-
const
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
.join('');
|
|
2746
|
-
return `<ul>${items}</ul>`;
|
|
2747
|
-
}
|
|
2748
|
-
// Ordered list
|
|
2749
|
-
if (/^\d+\.\s/.test(trimmed)) {
|
|
2750
|
-
const items = trimmed
|
|
2751
|
-
.split('\n')
|
|
2752
|
-
.filter((l) => /^\d+\.\s/.test(l))
|
|
2753
|
-
.map((l) => `<li>${processInline(l.replace(/^\d+\.\s+/, '').trim())}</li>`)
|
|
2754
|
-
.join('');
|
|
2755
|
-
return `<ol>${items}</ol>`;
|
|
3260
|
+
// List (unordered/ordered, with indentation-based nesting)
|
|
3261
|
+
if (/^([-*+]|\d+\.)\s/.test(trimmed)) {
|
|
3262
|
+
const lines = parseListLines(trimmed.split('\n'));
|
|
3263
|
+
if (lines.length > 0) {
|
|
3264
|
+
return buildListHtml(lines, 0, lines[0].indent).html;
|
|
3265
|
+
}
|
|
2756
3266
|
}
|
|
2757
3267
|
// Paragraph — single newlines within a block become <br>
|
|
2758
3268
|
return `<p>${trimmed
|
|
@@ -2773,6 +3283,11 @@ const htmlToMarkdown = (html) => {
|
|
|
2773
3283
|
if (!html)
|
|
2774
3284
|
return '';
|
|
2775
3285
|
let md = html;
|
|
3286
|
+
// Aligned blocks: text-align has no Markdown form, so preserve them verbatim as native HTML (their
|
|
3287
|
+
// inner markup stays HTML) and round-trip via a placeholder — extracted before the block passes
|
|
3288
|
+
// below rewrite them, restored after the final tag-strip.
|
|
3289
|
+
const alignedBlocks = [];
|
|
3290
|
+
md = md.replace(/<(p|h[1-6]|div)\b[^>]*\bstyle="[^"]*text-align[^"]*"[^>]*>[\s\S]*?<\/\1>/gi, (match) => makePlaceholder('ALIGN', alignedBlocks.push(match) - 1));
|
|
2776
3291
|
// Code blocks — process before inline code
|
|
2777
3292
|
md = md.replace(/<pre[^>]*><code[^>]*class="language-([^"]+)"[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (_, lang, code) => `\n\`\`\`${lang}\n${unescapeHtml(code.trim())}\n\`\`\`\n`);
|
|
2778
3293
|
md = md.replace(/<pre[^>]*><code[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (_, code) => `\n\`\`\`\n${unescapeHtml(code.trim())}\n\`\`\`\n`);
|
|
@@ -2782,50 +3297,64 @@ const htmlToMarkdown = (html) => {
|
|
|
2782
3297
|
for (let i = 6; i >= 1; i--) {
|
|
2783
3298
|
md = md.replace(new RegExp(`<h${i}[^>]*>([\\s\\S]*?)<\\/h${i}>`, 'gi'), (_, content) => `\n${'#'.repeat(i)} ${content.trim()}\n`);
|
|
2784
3299
|
}
|
|
2785
|
-
// Bold + italic (combined before individual)
|
|
2786
|
-
|
|
2787
|
-
md = md.replace(/<
|
|
2788
|
-
md = md.replace(/<
|
|
2789
|
-
md = md.replace(/<(
|
|
2790
|
-
md = md.replace(/<(
|
|
3300
|
+
// Bold + italic (combined before individual). Boundary whitespace is hoisted out of the
|
|
3301
|
+
// delimiters (emphasize) — `<strong> x</strong>` must become ` **x**`, not the invalid `** x**`.
|
|
3302
|
+
md = md.replace(/<strong[^>]*><em[^>]*>([\s\S]*?)<\/em><\/strong>/gi, (_, c) => emphasize('***', c));
|
|
3303
|
+
md = md.replace(/<em[^>]*><strong[^>]*>([\s\S]*?)<\/strong><\/em>/gi, (_, c) => emphasize('***', c));
|
|
3304
|
+
md = md.replace(/<(strong|b)[^>]*>([\s\S]*?)<\/\1>/gi, (_, _tag, c) => emphasize('**', c));
|
|
3305
|
+
md = md.replace(/<(em|i)[^>]*>([\s\S]*?)<\/\1>/gi, (_, _tag, c) => emphasize('*', c));
|
|
3306
|
+
md = md.replace(/<(del|s|strike)[^>]*>([\s\S]*?)<\/\1>/gi, (_, _tag, c) => emphasize('~~', c));
|
|
2791
3307
|
// Inline code
|
|
2792
3308
|
md = md.replace(/<code[^>]*>([\s\S]*?)<\/code>/gi, (_, code) => `\`${unescapeHtml(code)}\``);
|
|
2793
|
-
// Links and images
|
|
2794
|
-
|
|
3309
|
+
// Links and images. A link with `target="_blank"` has no Markdown form, so it round-trips as raw
|
|
3310
|
+
// HTML (keeping only href + target + rel) via a placeholder — like underline below; ordinary links
|
|
3311
|
+
// become `[text](url)`.
|
|
3312
|
+
const newTabLinks = [];
|
|
3313
|
+
md = md.replace(/<a\b([^>]*)>([\s\S]*?)<\/a>/gi, (_, attrs, inner) => {
|
|
3314
|
+
const href = /\bhref\s*=\s*"([^"]*)"/i.exec(attrs)?.[1] ?? '';
|
|
3315
|
+
if (/\btarget\s*=\s*"_blank"/i.test(attrs) && href) {
|
|
3316
|
+
const anchor = `<a href="${href}" target="_blank" rel="noopener noreferrer">${inner}</a>`;
|
|
3317
|
+
return makePlaceholder('NTLINK', newTabLinks.push(anchor) - 1);
|
|
3318
|
+
}
|
|
3319
|
+
return href ? `[${inner}](${href})` : inner;
|
|
3320
|
+
});
|
|
2795
3321
|
md = md.replace(/<img[^>]+src="([^"]*)"[^>]*alt="([^"]*)"[^>]*\/?>/gi, '');
|
|
2796
3322
|
md = md.replace(/<img[^>]+alt="([^"]*)"[^>]*src="([^"]*)"[^>]*\/?>/gi, '');
|
|
2797
3323
|
md = md.replace(/<img[^>]+src="([^"]*)"[^>]*\/?>/gi, '');
|
|
2798
|
-
//
|
|
3324
|
+
// Underline has no Markdown form — preserve it as raw <u> so it round-trips. Extract it now (its
|
|
3325
|
+
// inner markup is already converted above) so the block passes below don't strip the tag; the
|
|
3326
|
+
// placeholder is restored after the final tag-strip.
|
|
3327
|
+
const underlines = [];
|
|
3328
|
+
md = md.replace(/<u[^>]*>([\s\S]*?)<\/u>/gi, (_, inner) => makePlaceholder('U', underlines.push(inner) - 1));
|
|
3329
|
+
// Block quotes — <br> line breaks become one quoted line each (stripTags would swallow them)
|
|
2799
3330
|
md = md.replace(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/gi, (_, content) => {
|
|
2800
|
-
return (stripTags(content)
|
|
3331
|
+
return (stripTags(content.replace(/<br\s*\/?>/gi, '\n'))
|
|
2801
3332
|
.trim()
|
|
2802
3333
|
.split('\n')
|
|
2803
3334
|
.map((line) => `> ${line.trim()}`)
|
|
2804
3335
|
.join('\n') + '\n');
|
|
2805
3336
|
});
|
|
2806
|
-
// Lists
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
});
|
|
2811
|
-
md = md.replace(/<ol[^>]*>([\s\S]*?)<\/ol>/gi, (_, content) => {
|
|
2812
|
-
let n = 1;
|
|
2813
|
-
return ('\n' +
|
|
2814
|
-
content.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_m, item) => `${n++}. ${stripTags(item).trim()}\n`));
|
|
2815
|
-
});
|
|
3337
|
+
// Lists — replace each top-level list with its recursively-serialized Markdown (handles nesting).
|
|
3338
|
+
for (let list = findList(md); list; list = findList(md)) {
|
|
3339
|
+
md = `${md.slice(0, list.start)}\n${listToMarkdown(list.inner, list.ordered, 0)}\n${md.slice(list.end)}`;
|
|
3340
|
+
}
|
|
2816
3341
|
// Tables
|
|
2817
3342
|
md = md.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_, tableContent) => {
|
|
2818
|
-
|
|
3343
|
+
// GFM cells are single-line, so a <br> inside one degrades to a space (not silently dropped)
|
|
3344
|
+
const extractCells = (row) => [...row.matchAll(/<t[hd][^>]*>([\s\S]*?)<\/t[hd]>/gi)].map((m) => stripTags((m[1] ?? '').replace(/<br\s*\/?>/gi, ' ')).trim());
|
|
3345
|
+
// GFM alignment is per column, read from the header cells' text-align styles
|
|
3346
|
+
const extractAligns = (row) => [...row.matchAll(/<t[hd]([^>]*)>/gi)].map((m) => {
|
|
3347
|
+
const align = /text-align:\s*(left|center|right)/i.exec(m[1] ?? '')?.[1]?.toLowerCase();
|
|
3348
|
+
return align ?? null;
|
|
3349
|
+
});
|
|
2819
3350
|
const theadMatch = tableContent.match(/<thead[^>]*>([\s\S]*?)<\/thead>/i);
|
|
2820
3351
|
const firstTrMatch = tableContent.match(/<tr[^>]*>([\s\S]*?)<\/tr>/i);
|
|
2821
|
-
const
|
|
2822
|
-
|
|
2823
|
-
: firstTrMatch
|
|
2824
|
-
? extractCells(firstTrMatch[1] ?? '')
|
|
2825
|
-
: [];
|
|
3352
|
+
const headerRow = theadMatch?.[1] ?? firstTrMatch?.[1] ?? '';
|
|
3353
|
+
const headerCells = headerRow ? extractCells(headerRow) : [];
|
|
2826
3354
|
if (headerCells.length === 0)
|
|
2827
3355
|
return '';
|
|
2828
|
-
const
|
|
3356
|
+
const aligns = extractAligns(headerRow);
|
|
3357
|
+
const separator = headerCells.map((_cell, i) => separatorFor(aligns[i] ?? null)).join(' | ');
|
|
2829
3358
|
const bodySource = theadMatch
|
|
2830
3359
|
? tableContent.replace(theadMatch[0], '')
|
|
2831
3360
|
: tableContent.replace(firstTrMatch?.[0] ?? '', '');
|
|
@@ -2833,13 +3362,21 @@ const htmlToMarkdown = (html) => {
|
|
|
2833
3362
|
const lines = [`| ${headerCells.join(' | ')} |`, `| ${separator} |`, ...bodyRows.map((r) => `| ${r} |`)];
|
|
2834
3363
|
return `\n${lines.join('\n')}\n`;
|
|
2835
3364
|
});
|
|
2836
|
-
// Block elements
|
|
2837
|
-
|
|
3365
|
+
// Block elements — a <br> inside a paragraph is a soft line break; turn it into a newline before
|
|
3366
|
+
// the tag-strip removes it, so it round-trips (a single newline within a block renders as <br>)
|
|
3367
|
+
md = md.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, (_, content) => `\n${stripTags(content.replace(/<br\s*\/?>/gi, '\n')).trim()}\n`);
|
|
3368
|
+
// A <div> boundary acts as a paragraph boundary — clipboard HTML commonly uses divs as
|
|
3369
|
+
// paragraphs, and silently stripping them would merge adjacent blocks into one.
|
|
3370
|
+
md = md.replace(/<\/div\s*>/gi, '\n\n').replace(/<div[^>]*>/gi, '\n\n');
|
|
2838
3371
|
md = md.replace(/<br\s*\/?>/gi, '\n');
|
|
2839
3372
|
md = md.replace(/<hr\s*\/?>/gi, '\n---\n');
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
3373
|
+
md = unescapeHtml(stripTags(md)).replace(placeholderRe('U'), (_, i) => `<u>${underlines[+i] ?? ''}</u>`);
|
|
3374
|
+
// new-tab links survive the tag-strip as raw HTML (restored after it, like underline)
|
|
3375
|
+
md = md.replace(placeholderRe('NTLINK'), (_, i) => newTabLinks[+i] ?? '');
|
|
3376
|
+
// aligned blocks are block-level, so pad with blank lines before collapsing so they survive as
|
|
3377
|
+
// their own Markdown block
|
|
3378
|
+
md = md.replace(placeholderRe('ALIGN'), (_, i) => `\n\n${alignedBlocks[+i] ?? ''}\n\n`);
|
|
3379
|
+
return md.replace(/\n{3,}/g, '\n\n').trim();
|
|
2843
3380
|
};
|
|
2844
3381
|
|
|
2845
3382
|
const clamp = (value, min = 0, max = 100) => {
|
|
@@ -3145,190 +3682,6 @@ const [provideLocale, injectLocale] = createRootProvider(() => {
|
|
|
3145
3682
|
name: 'Locale',
|
|
3146
3683
|
});
|
|
3147
3684
|
|
|
3148
|
-
const [provideRenderer, injectRenderer] = createRootProvider(() => {
|
|
3149
|
-
const rendererFactory = inject(RendererFactory2);
|
|
3150
|
-
const renderer = rendererFactory.createRenderer(null, null);
|
|
3151
|
-
const addClass = (element, ...classes) => {
|
|
3152
|
-
classes.forEach((cls) => renderer.addClass(element, cls));
|
|
3153
|
-
};
|
|
3154
|
-
const removeClass = (element, ...classes) => {
|
|
3155
|
-
classes.forEach((cls) => renderer.removeClass(element, cls));
|
|
3156
|
-
};
|
|
3157
|
-
const toggleClass = (element, className, force) => {
|
|
3158
|
-
const shouldAdd = force ?? !element.classList.contains(className);
|
|
3159
|
-
if (shouldAdd) {
|
|
3160
|
-
renderer.addClass(element, className);
|
|
3161
|
-
}
|
|
3162
|
-
else {
|
|
3163
|
-
renderer.removeClass(element, className);
|
|
3164
|
-
}
|
|
3165
|
-
return shouldAdd;
|
|
3166
|
-
};
|
|
3167
|
-
const setStyle = (element, style, flags) => {
|
|
3168
|
-
Object.entries(style).forEach(([key, value]) => {
|
|
3169
|
-
if (!Number.isNaN(Number(key)))
|
|
3170
|
-
return;
|
|
3171
|
-
if (value !== null && value !== undefined) {
|
|
3172
|
-
renderer.setStyle(element, key, value, flags);
|
|
3173
|
-
}
|
|
3174
|
-
else {
|
|
3175
|
-
renderer.removeStyle(element, key, flags);
|
|
3176
|
-
}
|
|
3177
|
-
});
|
|
3178
|
-
};
|
|
3179
|
-
const removeStyle = (element, style, flags) => {
|
|
3180
|
-
if (!Number.isNaN(Number(style)))
|
|
3181
|
-
return;
|
|
3182
|
-
renderer.removeStyle(element, style, flags);
|
|
3183
|
-
};
|
|
3184
|
-
const removeStyles = (element, ...styles) => {
|
|
3185
|
-
styles.forEach((style) => removeStyle(element, style));
|
|
3186
|
-
};
|
|
3187
|
-
const setCssProperty = (element, name, value) => {
|
|
3188
|
-
if (value !== null && value !== undefined) {
|
|
3189
|
-
renderer.setStyle(element, name, value, RendererStyleFlags2.DashCase);
|
|
3190
|
-
}
|
|
3191
|
-
else {
|
|
3192
|
-
renderer.removeStyle(element, name, RendererStyleFlags2.DashCase);
|
|
3193
|
-
}
|
|
3194
|
-
};
|
|
3195
|
-
const setCssProperties = (element, properties) => {
|
|
3196
|
-
Object.entries(properties).forEach(([name, value]) => {
|
|
3197
|
-
setCssProperty(element, name, value);
|
|
3198
|
-
});
|
|
3199
|
-
};
|
|
3200
|
-
const setAttribute = (element, name, value, namespace) => {
|
|
3201
|
-
if (value !== null && value !== undefined) {
|
|
3202
|
-
renderer.setAttribute(element, name, value, namespace);
|
|
3203
|
-
}
|
|
3204
|
-
else {
|
|
3205
|
-
renderer.removeAttribute(element, name, namespace);
|
|
3206
|
-
}
|
|
3207
|
-
};
|
|
3208
|
-
const removeAttribute = (element, ...names) => {
|
|
3209
|
-
names.forEach((name) => renderer.removeAttribute(element, name));
|
|
3210
|
-
};
|
|
3211
|
-
const setProperty = (element, name, value) => {
|
|
3212
|
-
renderer.setProperty(element, name, value);
|
|
3213
|
-
};
|
|
3214
|
-
const setProperties = (element, properties) => {
|
|
3215
|
-
Object.entries(properties).forEach(([name, value]) => {
|
|
3216
|
-
renderer.setProperty(element, name, value);
|
|
3217
|
-
});
|
|
3218
|
-
};
|
|
3219
|
-
const createElement = (tagName, namespace) => {
|
|
3220
|
-
return renderer.createElement(tagName, namespace);
|
|
3221
|
-
};
|
|
3222
|
-
const createText = (value) => {
|
|
3223
|
-
return renderer.createText(value);
|
|
3224
|
-
};
|
|
3225
|
-
const createComment = (value) => {
|
|
3226
|
-
return renderer.createComment(value);
|
|
3227
|
-
};
|
|
3228
|
-
const appendChild = (parent, newChild) => {
|
|
3229
|
-
renderer.appendChild(parent, newChild);
|
|
3230
|
-
};
|
|
3231
|
-
const insertBefore = (parent, newChild, refChild) => {
|
|
3232
|
-
renderer.insertBefore(parent, newChild, refChild);
|
|
3233
|
-
};
|
|
3234
|
-
const removeChild = (parent, oldChild, isHostElement) => {
|
|
3235
|
-
renderer.removeChild(parent, oldChild, isHostElement);
|
|
3236
|
-
};
|
|
3237
|
-
const selectRootElement = (selectorOrNode, preserveContent) => {
|
|
3238
|
-
return renderer.selectRootElement(selectorOrNode, preserveContent);
|
|
3239
|
-
};
|
|
3240
|
-
const parentNode = (node) => {
|
|
3241
|
-
return renderer.parentNode(node);
|
|
3242
|
-
};
|
|
3243
|
-
const nextSibling = (node) => {
|
|
3244
|
-
return renderer.nextSibling(node);
|
|
3245
|
-
};
|
|
3246
|
-
const setValue = (node, value) => {
|
|
3247
|
-
renderer.setValue(node, value);
|
|
3248
|
-
};
|
|
3249
|
-
const listen = (target, eventName, callback) => {
|
|
3250
|
-
return renderer.listen(target, eventName, callback);
|
|
3251
|
-
};
|
|
3252
|
-
const destroyNode = (node) => {
|
|
3253
|
-
if (renderer.destroyNode) {
|
|
3254
|
-
renderer.destroyNode(node);
|
|
3255
|
-
}
|
|
3256
|
-
};
|
|
3257
|
-
const replaceChild = (parent, newChild, oldChild) => {
|
|
3258
|
-
renderer.insertBefore(parent, newChild, oldChild);
|
|
3259
|
-
renderer.removeChild(parent, oldChild);
|
|
3260
|
-
};
|
|
3261
|
-
const empty = (element) => {
|
|
3262
|
-
while (element.firstChild) {
|
|
3263
|
-
renderer.removeChild(element, element.firstChild);
|
|
3264
|
-
}
|
|
3265
|
-
};
|
|
3266
|
-
const setTextContent = (element, text) => {
|
|
3267
|
-
renderer.setProperty(element, 'textContent', text);
|
|
3268
|
-
};
|
|
3269
|
-
const setInnerHTML = (element, html) => {
|
|
3270
|
-
renderer.setProperty(element, 'innerHTML', html);
|
|
3271
|
-
};
|
|
3272
|
-
const setAttributes = (element, attributes, namespace) => {
|
|
3273
|
-
Object.entries(attributes).forEach(([name, value]) => {
|
|
3274
|
-
setAttribute(element, name, value, namespace);
|
|
3275
|
-
});
|
|
3276
|
-
};
|
|
3277
|
-
const setDataAttributes = (element, data) => {
|
|
3278
|
-
Object.entries(data).forEach(([key, value]) => {
|
|
3279
|
-
setAttribute(element, `data-${key}`, value);
|
|
3280
|
-
});
|
|
3281
|
-
};
|
|
3282
|
-
const moveBefore = (config) => {
|
|
3283
|
-
const { newParent, child, before = null } = config;
|
|
3284
|
-
if ('moveBefore' in newParent) {
|
|
3285
|
-
try {
|
|
3286
|
-
newParent.moveBefore(child, before);
|
|
3287
|
-
}
|
|
3288
|
-
catch {
|
|
3289
|
-
newParent.insertBefore(child, before);
|
|
3290
|
-
}
|
|
3291
|
-
}
|
|
3292
|
-
else {
|
|
3293
|
-
newParent.insertBefore(child, before);
|
|
3294
|
-
}
|
|
3295
|
-
};
|
|
3296
|
-
return {
|
|
3297
|
-
addClass,
|
|
3298
|
-
removeClass,
|
|
3299
|
-
toggleClass,
|
|
3300
|
-
setStyle,
|
|
3301
|
-
removeStyle,
|
|
3302
|
-
removeStyles,
|
|
3303
|
-
setAttribute,
|
|
3304
|
-
removeAttribute,
|
|
3305
|
-
setProperty,
|
|
3306
|
-
setProperties,
|
|
3307
|
-
createElement,
|
|
3308
|
-
createText,
|
|
3309
|
-
createComment,
|
|
3310
|
-
appendChild,
|
|
3311
|
-
insertBefore,
|
|
3312
|
-
removeChild,
|
|
3313
|
-
selectRootElement,
|
|
3314
|
-
parentNode,
|
|
3315
|
-
nextSibling,
|
|
3316
|
-
setValue,
|
|
3317
|
-
listen,
|
|
3318
|
-
destroyNode,
|
|
3319
|
-
replaceChild,
|
|
3320
|
-
empty,
|
|
3321
|
-
setTextContent,
|
|
3322
|
-
setInnerHTML,
|
|
3323
|
-
setAttributes,
|
|
3324
|
-
setDataAttributes,
|
|
3325
|
-
setCssProperty,
|
|
3326
|
-
setCssProperties,
|
|
3327
|
-
moveBefore,
|
|
3328
|
-
renderer,
|
|
3329
|
-
};
|
|
3330
|
-
}, { name: 'Angular Renderer' });
|
|
3331
|
-
|
|
3332
3685
|
const [provideStyleManager, injectStyleManager] = createRootProvider(() => {
|
|
3333
3686
|
const appRef = inject(ApplicationRef);
|
|
3334
3687
|
const destroyRef = inject(DestroyRef);
|
|
@@ -4596,12 +4949,14 @@ const createAnchoredPositionCleanup = (strategy, paneElement, overlayRef, render
|
|
|
4596
4949
|
middleware.push(flip({
|
|
4597
4950
|
fallbackPlacements: strategy.fallbackPlacements ?? undefined,
|
|
4598
4951
|
fallbackAxisSideDirection: 'start',
|
|
4952
|
+
boundary: strategy.boundary,
|
|
4599
4953
|
}));
|
|
4600
4954
|
if (strategy.shift !== false) {
|
|
4601
4955
|
middleware.push(shift({
|
|
4602
4956
|
crossAxis: typeof strategy.shift === 'object' ? (strategy.shift.crossAxis ?? false) : false,
|
|
4603
4957
|
limiter: limitShift(),
|
|
4604
4958
|
padding: strategy.viewportPadding ?? 8,
|
|
4959
|
+
boundary: strategy.boundary,
|
|
4605
4960
|
}));
|
|
4606
4961
|
}
|
|
4607
4962
|
// size must run AFTER shift so the available space is measured from the shifted position —
|
|
@@ -7416,6 +7771,7 @@ class ProvideColorDirective {
|
|
|
7416
7771
|
this.themes = injectColorThemes({ optional: true });
|
|
7417
7772
|
this.prefix = injectColorThemesPrefix({ optional: true });
|
|
7418
7773
|
this.injector = inject(Injector);
|
|
7774
|
+
this.parentProvider = inject(COLOR_PROVIDER, { optional: true, skipSelf: true });
|
|
7419
7775
|
this.currentProviderSync = null;
|
|
7420
7776
|
this.forcedColor = signal(FORCED_COLOR_UNSET, /* @ts-ignore */
|
|
7421
7777
|
...(ngDevMode ? [{ debugName: "forcedColor" }] : /* istanbul ignore next */ []));
|
|
@@ -7428,6 +7784,22 @@ class ProvideColorDirective {
|
|
|
7428
7784
|
return this.color();
|
|
7429
7785
|
}, /* @ts-ignore */
|
|
7430
7786
|
...(ngDevMode ? [{ debugName: "effectiveColor" }] : /* istanbul ignore next */ []));
|
|
7787
|
+
/**
|
|
7788
|
+
* The color that actually applies at this provider's location: its own effective color, or —
|
|
7789
|
+
* when this provider is passive (no input, nothing forced) — the nearest ancestor provider's.
|
|
7790
|
+
* Mirrors the CSS cascade, which falls through a provider's `-color--inherited` scope class;
|
|
7791
|
+
* consumers that re-apply context across detached DOM (overlay panes) must use this instead
|
|
7792
|
+
* of `effectiveColor` or a passive in-between provider (e.g. a form field's) erases the theme.
|
|
7793
|
+
*/
|
|
7794
|
+
this.resolvedColor = computed(() => {
|
|
7795
|
+
const own = this.effectiveColor();
|
|
7796
|
+
const ownName = typeof own === 'object' && own !== null ? own.name : own;
|
|
7797
|
+
if (ownName) {
|
|
7798
|
+
return own;
|
|
7799
|
+
}
|
|
7800
|
+
return this.parentProvider?.resolvedColor();
|
|
7801
|
+
}, /* @ts-ignore */
|
|
7802
|
+
...(ngDevMode ? [{ debugName: "resolvedColor" }] : /* istanbul ignore next */ []));
|
|
7431
7803
|
this.colorName = computed(() => {
|
|
7432
7804
|
const raw = this.effectiveColor();
|
|
7433
7805
|
const value = typeof raw === 'object' && raw !== null ? raw.name : raw;
|
|
@@ -7452,7 +7824,7 @@ class ProvideColorDirective {
|
|
|
7452
7824
|
this.currentProviderSync?.destroy();
|
|
7453
7825
|
runInInjectionContext(this.injector, () => {
|
|
7454
7826
|
this.currentProviderSync = effect(() => {
|
|
7455
|
-
const provideColor = provider.
|
|
7827
|
+
const provideColor = provider.resolvedColor();
|
|
7456
7828
|
untracked(() => {
|
|
7457
7829
|
if (provideColor === undefined) {
|
|
7458
7830
|
this.clearForcedColor();
|
|
@@ -7563,5 +7935,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
|
|
|
7563
7935
|
* Generated bundle index. Do not edit.
|
|
7564
7936
|
*/
|
|
7565
7937
|
|
|
7566
|
-
export { ANIMATABLE_TOKEN, ANIMATED_IF_TOKEN, ANIMATED_LIFECYCLE_TOKEN, ANIMATION_DEBUG_STORAGE_KEY, AT_LEAST_ONE_REQUIRED, AnimatableDirective, AnimatedIfDirective, AnimatedLifecycleDirective, AnimatedOverlayDirective, AutoSurfaceDirective, BOUNDARY_ELEMENT_TOKEN, BREAKPOINT_INSTANCE_TOKEN, BREAKPOINT_ORDER, COLOR_PROVIDER, ClickOutsideDirective, ColorInteractiveContainerDirective, ColorInteractiveDirective, ColorInteractiveExcludeDirective, ColorInteractiveHasFocusDirective, DEFAULT_MULTI_INSTANCE_RELS, DEFAULT_MULTI_INSTANCE_TAGS, DEFAULT_VIEWPORT_CONFIG, DISABLE_LOGGER_PARAM, DragHandleDirective, ET_DISABLE_SCROLL_TOP, ET_DISABLE_SCROLL_TOP_AS_RETURN_ROUTE, ET_DISABLE_SCROLL_TOP_ON_PATH_PARAM_CHANGE, ET_PROPERTY_REMOVED, FOCUSABLE_SELECTOR, FOCUS_VISIBLE_TRACKER_TOKEN, HtmlToMarkdownPipe, IS_ARRAY_NOT_EMPTY, IS_EMAIL, InferMimeTypePipe, InitialsPipe, IsArrayNotEmpty, IsEmail, jsonLd as JsonLD, KeyPressManager, MUST_MATCH, MarkdownToHtmlPipe, MustMatch, NormalizeGameResultTypePipe, NormalizeMatchParticipantsPipe, NormalizeMatchScorePipe, NormalizeMatchStatePipe, NormalizeMatchTypePipe, PropsDirective, ProvideColorDirective, ProvideSurfaceDirective, RUNTIME_ERROR_NO_DATA, RepeatDirective, ResizeHandlesComponent, RuntimeError, SEO_DIRECTIVE_TOKEN, SURFACE_PROVIDER, SURFACE_TYPE, ScrollObserverDirective, ScrollObserverEndDirective, ScrollObserverStartDirective, SeoDirective, SlugifyPipe, StructuredDataComponent, SurfaceInteractiveDirective, ToArrayPipe, TypedQueryList, ValidateAtLeastOneRequired, Validators, animationDebugLog, applyAlternateBinding, applyAlternateLanguagesBindings, applyArticleBindings, applyAuthorBinding, applyCanonicalBinding, applyCenteredPosition, applyDescriptionBinding, applyGlobalPosition, applyHeadBinding, applyHeadTitleBinding, applyHostListener, applyHostListeners, applyInitialFocus, applyKeywordsBinding, applyLinkBinding, applyMetaBinding, applyNextBinding, applyOgBinding, applyOpenGraphBindings, applyPrevBinding, applyResourceHintsBindings, applyRobotsBinding, applySocialMediaBindings, applyStructuredDataBinding, applyTwitterCardBindings, areScrollStatesEqual, bindProps, boolBreakpointTransform, booleanBreakpointAttribute, boundingClientRectToElementRect, breakpointTransformBase, buildElementSignal, buildSignalEffects, canUseSessionMemory, clamp, clone, cloneFormGroup, computedTillFalsy, computedTillTruthy, controlValueSignal, controlValueSignalWithPrevious, createAnchoredPositionCleanup, createArrayPropertyBinding, createAutoSessionMemoryKey, createBulkPropertyBinding, createCanAnimateSignal, createComponentId, createCssColorThemeName, createCssSurfaceName, createDependencyStash, createDestroy, createDocumentElementSignal, createElementDictionary, createElementDimensions, createEmptyElementSignal, createFlipAnimation, createFlipAnimationGroup, createHostProps, createIsRenderedSignal, createLogger, createOverlayRuntimeRef, createPropHandlers, createPropertyBinding, createProps, createProvider, createRootProvider, createRootThemeCss, createRoute, createRouterState, createRxHostListener, createSessionMemory, createSetup, createStaticProvider, createStaticRootProvider, createSwatchCss, createSwipeTracker, createTailwindColorThemes, createTailwindCssVar, createTailwindRgbVar, createThemeStyle, createUserConsentProvider, deferredSignal, deleteCookie, easeElastic, easeIn, easeInOut, easeLinear, easeOut, easeOutBack, easeOutBackStrong, elementCanScroll, equal, firstElementSignal, focusElement, forceReflow, formatRuntimeError, fromNextFrame, getCookie, getDomain, getElementScrollCoordinates, getFocusableElements, getFormGroupValue, getGroupMatchPoints, getGroupMatchScore, getHeadingElement, getKnockoutMatchScore, getMatchScoreSubLine, getObjectProperty, getScrollContainerTarget, getScrollItemTarget, getScrollSnapTarget, hasCookie, htmlToMarkdown, inferMimeType, initials, injectAngularRootElement, injectBoundaryElement, injectBreakpointInput, injectBreakpointIsMatched, injectBreakpointObserver, injectCanHover, injectColorThemes, injectColorThemesPrefix, injectCurrentBreakpoint, injectDeviceInputType, injectDisplayOrientation, injectErrorTheme, injectFocusVisibleTracker, injectFragment, injectHasPrecisionInput, injectHasTouchInput, injectHostElement, injectIs2Xl, injectIsLandscape, injectIsLg, injectIsMd, injectIsPortrait, injectIsRouterInitialized, injectIsSm, injectIsXl, injectIsXs, injectLinkStore, injectLinkStoreConfig, injectLocale, injectMediaQueryIsMatched, injectMetaConfig, injectMetaStore, injectObserveBreakpoint, injectObserveMediaQuery, injectOverlayRuntime, injectPathParam, injectPathParamChanges, injectPathParams, injectPrefersReducedMotion, injectQueryParam, injectQueryParamChanges, injectQueryParams, injectRenderer, injectRoute, injectRouteData, injectRouteDataItem, injectRouteTitle, injectRouterEvent, injectRouterState, injectScrollbarDimensions, injectStructuredDataConfig, injectStructuredDataStore, injectStyleManager, injectSuccessTheme, injectSurfaceContextTracker, injectSurfaceThemes, injectSurfaceThemesPrefix, injectTemplateRef, injectTitleConfig, injectTitleStore, injectUrl, injectViewportConfig, injectViewportDimensions, injectViewportSize, injectWarningTheme, isAnimationDebugEnabled, isArray, isElementSignal, isElementVisible, isFocusable, isGroupMatch, isHTMLElement, isKnockoutMatch, isObject, markdownToHtml, maybeSignalValue, memoizeSignal, nextFrame, normalizeGameResultType, normalizeMatchParticipant, normalizeMatchParticipants, normalizeMatchScore, normalizeMatchState, normalizeMatchType, numberBreakpointAttribute, numberBreakpointTransform, previousSignalValue, provideBoundaryElement, provideBreakpointInstance, provideBreakpointObserver, provideColorThemes, provideColorThemesWithTailwind4, provideFocusVisibleTracker, provideLinkStore, provideLinkStoreConfig, provideLocale, provideMetaConfig, provideMetaStore, provideOverlayRuntime, provideRenderer, provideStructuredDataConfig, provideStructuredDataStore, provideStyleManager, provideSurfaceContextTracker, provideSurfaceThemesWithTailwind4, provideTitleConfig, provideTitleStore, provideViewportConfig, resetPositioningStyles, resolveSurfaceByElevation, round, routerDisableScrollTop, scrollToElement, setBackdropStyles, setBaseElementStyles, setCookie, setInputSignal, setupFocusTrap, setupPositioning, setupScrollRestoration, signalAnimatedNumber, signalAttributes, signalClasses, signalElementChildren, signalElementDimensions, signalElementIntersection, signalElementLastScrollDirection, signalElementMutations, signalElementScrollState, signalHostAttributes, signalHostClasses, signalHostElementDimensions, signalHostElementIntersection, signalHostElementLastScrollDirection, signalHostElementMutations, signalHostElementScrollState, signalHostStyles, signalIsRendered, signalStyles, slugify, switchQueryListChanges, syncSignal, templateComputed, toArray, toArrayTrackByFn, toStringBinding, transformOrReturn, typedBreakpointTransform, unbindProps, useCursorDragScroll, writeScrollbarSizeToCssVariables, writeViewportSizeToCssVariables, ɵProvideColorThemes, ɵProvideColorThemesPrefix, ɵProvideSurfaceThemes, ɵProvideSurfaceThemesPrefix };
|
|
7938
|
+
export { ANIMATABLE_TOKEN, ANIMATED_IF_TOKEN, ANIMATED_LIFECYCLE_TOKEN, ANIMATION_DEBUG_STORAGE_KEY, AT_LEAST_ONE_REQUIRED, AnimatableDirective, AnimatedIfDirective, AnimatedLifecycleDirective, AnimatedOverlayDirective, AutoSurfaceDirective, BOUNDARY_ELEMENT_TOKEN, BREAKPOINT_INSTANCE_TOKEN, BREAKPOINT_ORDER, COLOR_PROVIDER, ClickOutsideDirective, ColorInteractiveContainerDirective, ColorInteractiveDirective, ColorInteractiveExcludeDirective, ColorInteractiveHasFocusDirective, DEFAULT_MULTI_INSTANCE_RELS, DEFAULT_MULTI_INSTANCE_TAGS, DEFAULT_VIEWPORT_CONFIG, DISABLE_LOGGER_PARAM, DragHandleDirective, ET_DISABLE_SCROLL_TOP, ET_DISABLE_SCROLL_TOP_AS_RETURN_ROUTE, ET_DISABLE_SCROLL_TOP_ON_PATH_PARAM_CHANGE, ET_PROPERTY_REMOVED, FOCUSABLE_SELECTOR, FOCUS_VISIBLE_TRACKER_TOKEN, HtmlToMarkdownPipe, IS_ARRAY_NOT_EMPTY, IS_EMAIL, InferMimeTypePipe, InitialsPipe, IsArrayNotEmpty, IsEmail, jsonLd as JsonLD, KeyPressManager, MUST_MATCH, MarkdownToHtmlPipe, MustMatch, NormalizeGameResultTypePipe, NormalizeMatchParticipantsPipe, NormalizeMatchScorePipe, NormalizeMatchStatePipe, NormalizeMatchTypePipe, PropsDirective, ProvideColorDirective, ProvideSurfaceDirective, RUNTIME_ERROR_NO_DATA, RepeatDirective, ResizeHandlesComponent, RuntimeError, SEO_DIRECTIVE_TOKEN, SURFACE_PROVIDER, SURFACE_TYPE, ScrollObserverDirective, ScrollObserverEndDirective, ScrollObserverStartDirective, SeoDirective, SlugifyPipe, StructuredDataComponent, SurfaceInteractiveDirective, ToArrayPipe, TypedQueryList, ValidateAtLeastOneRequired, Validators, animationDebugLog, applyAlternateBinding, applyAlternateLanguagesBindings, applyArticleBindings, applyAuthorBinding, applyCanonicalBinding, applyCenteredPosition, applyDescriptionBinding, applyGlobalPosition, applyHeadBinding, applyHeadTitleBinding, applyHostListener, applyHostListeners, applyInitialFocus, applyKeywordsBinding, applyLinkBinding, applyMetaBinding, applyNextBinding, applyOgBinding, applyOpenGraphBindings, applyPrevBinding, applyResourceHintsBindings, applyRobotsBinding, applySocialMediaBindings, applyStructuredDataBinding, applyTwitterCardBindings, areScrollStatesEqual, bindProps, boolBreakpointTransform, booleanBreakpointAttribute, boundingClientRectToElementRect, breakpointTransformBase, buildElementSignal, buildSignalEffects, canUseSessionMemory, clamp, clone, cloneFormGroup, computedTillFalsy, computedTillTruthy, controlValueSignal, controlValueSignalWithPrevious, copyToClipboard, createAnchoredPositionCleanup, createArrayPropertyBinding, createAutoSessionMemoryKey, createBulkPropertyBinding, createCanAnimateSignal, createComponentId, createCssColorThemeName, createCssSurfaceName, createDependencyStash, createDestroy, createDocumentElementSignal, createElementDictionary, createElementDimensions, createEmptyElementSignal, createFlipAnimation, createFlipAnimationGroup, createHostProps, createIsRenderedSignal, createLogger, createOverlayRuntimeRef, createPropHandlers, createPropertyBinding, createProps, createProvider, createRootProvider, createRootThemeCss, createRoute, createRouterState, createRxHostListener, createSessionMemory, createSetup, createStaticProvider, createStaticRootProvider, createSwatchCss, createSwipeTracker, createTailwindColorThemes, createTailwindCssVar, createTailwindRgbVar, createThemeStyle, createUserConsentProvider, deferredSignal, deleteCookie, easeElastic, easeIn, easeInOut, easeLinear, easeOut, easeOutBack, easeOutBackStrong, elementCanScroll, equal, firstElementSignal, focusElement, forceReflow, formatRuntimeError, fromNextFrame, getCookie, getDomain, getElementScrollCoordinates, getFocusableElements, getFormGroupValue, getGroupMatchPoints, getGroupMatchScore, getHeadingElement, getKnockoutMatchScore, getMatchScoreSubLine, getObjectProperty, getScrollContainerTarget, getScrollItemTarget, getScrollSnapTarget, hasCookie, htmlToMarkdown, inferMimeType, initials, injectAngularRootElement, injectAnimatedBlockSize, injectBoundaryElement, injectBreakpointInput, injectBreakpointIsMatched, injectBreakpointObserver, injectCanHover, injectColorThemes, injectColorThemesPrefix, injectCurrentBreakpoint, injectDeviceInputType, injectDisplayOrientation, injectErrorTheme, injectFocusVisibleTracker, injectFragment, injectHasPrecisionInput, injectHasTouchInput, injectHostElement, injectIs2Xl, injectIsLandscape, injectIsLg, injectIsMd, injectIsPortrait, injectIsRouterInitialized, injectIsSm, injectIsXl, injectIsXs, injectLinkStore, injectLinkStoreConfig, injectLocale, injectMediaQueryIsMatched, injectMetaConfig, injectMetaStore, injectObserveBreakpoint, injectObserveMediaQuery, injectOverlayRuntime, injectPathParam, injectPathParamChanges, injectPathParams, injectPrefersReducedMotion, injectQueryParam, injectQueryParamChanges, injectQueryParams, injectRenderer, injectRoute, injectRouteData, injectRouteDataItem, injectRouteTitle, injectRouterEvent, injectRouterState, injectScrollbarDimensions, injectStructuredDataConfig, injectStructuredDataStore, injectStyleManager, injectSuccessTheme, injectSurfaceContextTracker, injectSurfaceThemes, injectSurfaceThemesPrefix, injectTemplateRef, injectTitleConfig, injectTitleStore, injectUrl, injectViewportConfig, injectViewportDimensions, injectViewportSize, injectWarningTheme, isAnimationDebugEnabled, isArray, isElementSignal, isElementVisible, isFocusable, isGroupMatch, isHTMLElement, isKnockoutMatch, isObject, markdownToHtml, maybeSignalValue, memoizeSignal, nextFrame, normalizeGameResultType, normalizeMatchParticipant, normalizeMatchParticipants, normalizeMatchScore, normalizeMatchState, normalizeMatchType, numberBreakpointAttribute, numberBreakpointTransform, previousSignalValue, provideBoundaryElement, provideBreakpointInstance, provideBreakpointObserver, provideColorThemes, provideColorThemesWithTailwind4, provideFocusVisibleTracker, provideLinkStore, provideLinkStoreConfig, provideLocale, provideMetaConfig, provideMetaStore, provideOverlayRuntime, provideRenderer, provideStructuredDataConfig, provideStructuredDataStore, provideStyleManager, provideSurfaceContextTracker, provideSurfaceThemesWithTailwind4, provideTitleConfig, provideTitleStore, provideViewportConfig, randomId, readFromClipboard, resetPositioningStyles, resolveSurfaceByElevation, round, routerDisableScrollTop, scrollToElement, setBackdropStyles, setBaseElementStyles, setCookie, setInputSignal, setupFocusTrap, setupPositioning, setupScrollRestoration, signalAnimatedNumber, signalAttributes, signalClasses, signalElementChildren, signalElementDimensions, signalElementIntersection, signalElementLastScrollDirection, signalElementMutations, signalElementScrollState, signalHostAttributes, signalHostClasses, signalHostElementDimensions, signalHostElementIntersection, signalHostElementLastScrollDirection, signalHostElementMutations, signalHostElementScrollState, signalHostStyles, signalIsRendered, signalStyles, slugify, switchQueryListChanges, syncSignal, templateComputed, toArray, toArrayTrackByFn, toStringBinding, transformOrReturn, typedBreakpointTransform, unbindProps, useCursorDragScroll, writeScrollbarSizeToCssVariables, writeViewportSizeToCssVariables, ɵProvideColorThemes, ɵProvideColorThemesPrefix, ɵProvideSurfaceThemes, ɵProvideSurfaceThemesPrefix };
|
|
7567
7939
|
//# sourceMappingURL=ethlete-core.mjs.map
|