@ethlete/core 5.0.0-next.26 → 5.0.0-next.27
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 +40 -0
- package/fesm2022/ethlete-core.mjs +683 -399
- package/fesm2022/ethlete-core.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ethlete-core.d.ts +57 -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
4
|
import { Subject, BehaviorSubject, debounceTime, combineLatest, map, merge, fromEvent, filter, tap, switchMap, startWith, of, timer, takeUntil, pairwise, distinctUntilChanged, take, Observable, concatMap, EMPTY, concat, defer, 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';
|
|
@@ -360,6 +360,58 @@ const Validators = {
|
|
|
360
360
|
ValidateAtLeastOneRequired,
|
|
361
361
|
};
|
|
362
362
|
|
|
363
|
+
const copyToClipboard = async (text) => {
|
|
364
|
+
if (typeof navigator === 'undefined' || typeof document === 'undefined') {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
if (navigator.clipboard) {
|
|
368
|
+
try {
|
|
369
|
+
await navigator.clipboard.writeText(text);
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
// The async Clipboard API can be blocked (missing permission, insecure context) — try the legacy path.
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return copyToClipboardViaExecCommand(text);
|
|
377
|
+
};
|
|
378
|
+
const readFromClipboard = async () => {
|
|
379
|
+
if (typeof navigator === 'undefined' || !navigator.clipboard) {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
try {
|
|
383
|
+
return await navigator.clipboard.readText();
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
const copyToClipboardViaExecCommand = (text) => {
|
|
390
|
+
const previouslyFocusedElement = document.activeElement;
|
|
391
|
+
const textarea = document.createElement('textarea');
|
|
392
|
+
textarea.value = text;
|
|
393
|
+
textarea.setAttribute('readonly', '');
|
|
394
|
+
textarea.setAttribute('aria-hidden', 'true');
|
|
395
|
+
textarea.style.position = 'fixed';
|
|
396
|
+
textarea.style.top = '-9999px';
|
|
397
|
+
textarea.style.opacity = '0';
|
|
398
|
+
document.body.appendChild(textarea);
|
|
399
|
+
textarea.select();
|
|
400
|
+
textarea.setSelectionRange(0, text.length);
|
|
401
|
+
let didCopy = false;
|
|
402
|
+
try {
|
|
403
|
+
didCopy = document.execCommand('copy');
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
// execCommand throws in some environments instead of returning false.
|
|
407
|
+
}
|
|
408
|
+
textarea.remove();
|
|
409
|
+
if (previouslyFocusedElement instanceof HTMLElement) {
|
|
410
|
+
previouslyFocusedElement.focus();
|
|
411
|
+
}
|
|
412
|
+
return didCopy;
|
|
413
|
+
};
|
|
414
|
+
|
|
363
415
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
364
416
|
/* eslint-disable no-var */
|
|
365
417
|
/**
|
|
@@ -641,179 +693,189 @@ class KeyPressManager {
|
|
|
641
693
|
}
|
|
642
694
|
}
|
|
643
695
|
|
|
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
|
-
}
|
|
696
|
+
const [provideRenderer, injectRenderer] = createRootProvider(() => {
|
|
697
|
+
const rendererFactory = inject(RendererFactory2);
|
|
698
|
+
const renderer = rendererFactory.createRenderer(null, null);
|
|
699
|
+
const addClass = (element, ...classes) => {
|
|
700
|
+
classes.forEach((cls) => renderer.addClass(element, cls));
|
|
662
701
|
};
|
|
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;
|
|
702
|
+
const removeClass = (element, ...classes) => {
|
|
703
|
+
classes.forEach((cls) => renderer.removeClass(element, cls));
|
|
704
|
+
};
|
|
705
|
+
const toggleClass = (element, className, force) => {
|
|
706
|
+
const shouldAdd = force ?? !element.classList.contains(className);
|
|
707
|
+
if (shouldAdd) {
|
|
708
|
+
renderer.addClass(element, className);
|
|
674
709
|
}
|
|
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;
|
|
710
|
+
else {
|
|
711
|
+
renderer.removeClass(element, className);
|
|
712
|
+
}
|
|
713
|
+
return shouldAdd;
|
|
699
714
|
};
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
715
|
+
const setStyle = (element, style, flags) => {
|
|
716
|
+
Object.entries(style).forEach(([key, value]) => {
|
|
717
|
+
if (!Number.isNaN(Number(key)))
|
|
718
|
+
return;
|
|
719
|
+
if (value !== null && value !== undefined) {
|
|
720
|
+
renderer.setStyle(element, key, value, flags);
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
renderer.removeStyle(element, key, flags);
|
|
724
|
+
}
|
|
725
|
+
});
|
|
705
726
|
};
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
727
|
+
const removeStyle = (element, style, flags) => {
|
|
728
|
+
if (!Number.isNaN(Number(style)))
|
|
729
|
+
return;
|
|
730
|
+
renderer.removeStyle(element, style, flags);
|
|
709
731
|
};
|
|
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 });
|
|
732
|
+
const removeStyles = (element, ...styles) => {
|
|
733
|
+
styles.forEach((style) => removeStyle(element, style));
|
|
765
734
|
};
|
|
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;
|
|
735
|
+
const setCssProperty = (element, name, value) => {
|
|
736
|
+
if (value !== null && value !== undefined) {
|
|
737
|
+
renderer.setStyle(element, name, value, RendererStyleFlags2.DashCase);
|
|
773
738
|
}
|
|
774
|
-
|
|
775
|
-
|
|
739
|
+
else {
|
|
740
|
+
renderer.removeStyle(element, name, RendererStyleFlags2.DashCase);
|
|
776
741
|
}
|
|
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
742
|
};
|
|
782
|
-
const
|
|
783
|
-
|
|
784
|
-
|
|
743
|
+
const setCssProperties = (element, properties) => {
|
|
744
|
+
Object.entries(properties).forEach(([name, value]) => {
|
|
745
|
+
setCssProperty(element, name, value);
|
|
746
|
+
});
|
|
747
|
+
};
|
|
748
|
+
const setAttribute = (element, name, value, namespace) => {
|
|
749
|
+
if (value !== null && value !== undefined) {
|
|
750
|
+
renderer.setAttribute(element, name, value, namespace);
|
|
785
751
|
}
|
|
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
|
-
}
|
|
752
|
+
else {
|
|
753
|
+
renderer.removeAttribute(element, name, namespace);
|
|
794
754
|
}
|
|
795
|
-
|
|
796
|
-
|
|
755
|
+
};
|
|
756
|
+
const removeAttribute = (element, ...names) => {
|
|
757
|
+
names.forEach((name) => renderer.removeAttribute(element, name));
|
|
758
|
+
};
|
|
759
|
+
const setProperty = (element, name, value) => {
|
|
760
|
+
renderer.setProperty(element, name, value);
|
|
761
|
+
};
|
|
762
|
+
const setProperties = (element, properties) => {
|
|
763
|
+
Object.entries(properties).forEach(([name, value]) => {
|
|
764
|
+
renderer.setProperty(element, name, value);
|
|
765
|
+
});
|
|
766
|
+
};
|
|
767
|
+
const createElement = (tagName, namespace) => {
|
|
768
|
+
return renderer.createElement(tagName, namespace);
|
|
769
|
+
};
|
|
770
|
+
const createText = (value) => {
|
|
771
|
+
return renderer.createText(value);
|
|
772
|
+
};
|
|
773
|
+
const createComment = (value) => {
|
|
774
|
+
return renderer.createComment(value);
|
|
775
|
+
};
|
|
776
|
+
const appendChild = (parent, newChild) => {
|
|
777
|
+
renderer.appendChild(parent, newChild);
|
|
778
|
+
};
|
|
779
|
+
const insertBefore = (parent, newChild, refChild) => {
|
|
780
|
+
renderer.insertBefore(parent, newChild, refChild);
|
|
781
|
+
};
|
|
782
|
+
const removeChild = (parent, oldChild, isHostElement) => {
|
|
783
|
+
renderer.removeChild(parent, oldChild, isHostElement);
|
|
784
|
+
};
|
|
785
|
+
const selectRootElement = (selectorOrNode, preserveContent) => {
|
|
786
|
+
return renderer.selectRootElement(selectorOrNode, preserveContent);
|
|
787
|
+
};
|
|
788
|
+
const parentNode = (node) => {
|
|
789
|
+
return renderer.parentNode(node);
|
|
790
|
+
};
|
|
791
|
+
const nextSibling = (node) => {
|
|
792
|
+
return renderer.nextSibling(node);
|
|
793
|
+
};
|
|
794
|
+
const setValue = (node, value) => {
|
|
795
|
+
renderer.setValue(node, value);
|
|
796
|
+
};
|
|
797
|
+
const listen = (target, eventName, callback) => {
|
|
798
|
+
return renderer.listen(target, eventName, callback);
|
|
799
|
+
};
|
|
800
|
+
const destroyNode = (node) => {
|
|
801
|
+
if (renderer.destroyNode) {
|
|
802
|
+
renderer.destroyNode(node);
|
|
797
803
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
804
|
+
};
|
|
805
|
+
const replaceChild = (parent, newChild, oldChild) => {
|
|
806
|
+
renderer.insertBefore(parent, newChild, oldChild);
|
|
807
|
+
renderer.removeChild(parent, oldChild);
|
|
808
|
+
};
|
|
809
|
+
const empty = (element) => {
|
|
810
|
+
while (element.firstChild) {
|
|
811
|
+
renderer.removeChild(element, element.firstChild);
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
const setTextContent = (element, text) => {
|
|
815
|
+
renderer.setProperty(element, 'textContent', text);
|
|
816
|
+
};
|
|
817
|
+
const setInnerHTML = (element, html) => {
|
|
818
|
+
renderer.setProperty(element, 'innerHTML', html);
|
|
819
|
+
};
|
|
820
|
+
const setAttributes = (element, attributes, namespace) => {
|
|
821
|
+
Object.entries(attributes).forEach(([name, value]) => {
|
|
822
|
+
setAttribute(element, name, value, namespace);
|
|
823
|
+
});
|
|
824
|
+
};
|
|
825
|
+
const setDataAttributes = (element, data) => {
|
|
826
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
827
|
+
setAttribute(element, `data-${key}`, value);
|
|
828
|
+
});
|
|
829
|
+
};
|
|
830
|
+
const moveBefore = (config) => {
|
|
831
|
+
const { newParent, child, before = null } = config;
|
|
832
|
+
if ('moveBefore' in newParent) {
|
|
833
|
+
try {
|
|
834
|
+
newParent.moveBefore(child, before);
|
|
801
835
|
}
|
|
802
|
-
|
|
803
|
-
|
|
836
|
+
catch {
|
|
837
|
+
newParent.insertBefore(child, before);
|
|
804
838
|
}
|
|
805
839
|
}
|
|
806
|
-
|
|
840
|
+
else {
|
|
841
|
+
newParent.insertBefore(child, before);
|
|
842
|
+
}
|
|
807
843
|
};
|
|
808
844
|
return {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
845
|
+
addClass,
|
|
846
|
+
removeClass,
|
|
847
|
+
toggleClass,
|
|
848
|
+
setStyle,
|
|
849
|
+
removeStyle,
|
|
850
|
+
removeStyles,
|
|
851
|
+
setAttribute,
|
|
852
|
+
removeAttribute,
|
|
853
|
+
setProperty,
|
|
854
|
+
setProperties,
|
|
855
|
+
createElement,
|
|
856
|
+
createText,
|
|
857
|
+
createComment,
|
|
858
|
+
appendChild,
|
|
859
|
+
insertBefore,
|
|
860
|
+
removeChild,
|
|
861
|
+
selectRootElement,
|
|
862
|
+
parentNode,
|
|
863
|
+
nextSibling,
|
|
864
|
+
setValue,
|
|
865
|
+
listen,
|
|
866
|
+
destroyNode,
|
|
867
|
+
replaceChild,
|
|
868
|
+
empty,
|
|
869
|
+
setTextContent,
|
|
870
|
+
setInnerHTML,
|
|
871
|
+
setAttributes,
|
|
872
|
+
setDataAttributes,
|
|
873
|
+
setCssProperty,
|
|
874
|
+
setCssProperties,
|
|
875
|
+
moveBefore,
|
|
876
|
+
renderer,
|
|
815
877
|
};
|
|
816
|
-
}, { name: '
|
|
878
|
+
}, { name: 'Angular Renderer' });
|
|
817
879
|
|
|
818
880
|
const isElementSignal = (el) => {
|
|
819
881
|
if (!isSignal(el))
|
|
@@ -1255,6 +1317,251 @@ const injectScrollbarDimensions = memoizeSignal(() => {
|
|
|
1255
1317
|
});
|
|
1256
1318
|
});
|
|
1257
1319
|
|
|
1320
|
+
/**
|
|
1321
|
+
* Smoothly animates an element's `block-size` when its content changes size (e.g. a list filtering,
|
|
1322
|
+
* a loading state resolving). Set up once in an injection context; it observes the given content
|
|
1323
|
+
* element(s) and animates the host from its previous height to its new natural height.
|
|
1324
|
+
*
|
|
1325
|
+
* Robustness notes: the baseline height is captured on the first render (via `afterNextRender`) and
|
|
1326
|
+
* the effect only animates after that, so the initial layout settling never plays as a grow-from-0.
|
|
1327
|
+
* A change that interrupts an in-flight animation continues from the current animated height, and
|
|
1328
|
+
* zero/pre-layout measurements are ignored. Respects `prefers-reduced-motion`.
|
|
1329
|
+
*/
|
|
1330
|
+
const injectAnimatedBlockSize = (config) => {
|
|
1331
|
+
const hostBinding = config.host ?? inject(ElementRef);
|
|
1332
|
+
const renderer = injectRenderer();
|
|
1333
|
+
const prefersReducedMotion = injectPrefersReducedMotion();
|
|
1334
|
+
const destroyRef = inject(DestroyRef);
|
|
1335
|
+
const hostSignal = firstElementSignal(buildElementSignal(hostBinding));
|
|
1336
|
+
const observeTargets = Array.isArray(config.observe) ? config.observe : [config.observe];
|
|
1337
|
+
const dimensionSignals = observeTargets.map((target) => signalElementDimensions(target));
|
|
1338
|
+
const duration = config.duration ?? 160;
|
|
1339
|
+
const easing = config.easing ?? 'ease';
|
|
1340
|
+
let lastBlockSize = null;
|
|
1341
|
+
let animation = null;
|
|
1342
|
+
let ready = false;
|
|
1343
|
+
const hostElement = () => hostSignal().currentElement;
|
|
1344
|
+
const run = () => {
|
|
1345
|
+
const host = hostElement();
|
|
1346
|
+
if (!host)
|
|
1347
|
+
return;
|
|
1348
|
+
// continue from the current animated height when interrupting a running animation
|
|
1349
|
+
const from = animation?.playState === 'running' ? host.getBoundingClientRect().height : lastBlockSize;
|
|
1350
|
+
animation?.cancel();
|
|
1351
|
+
animation = null;
|
|
1352
|
+
const to = host.getBoundingClientRect().height;
|
|
1353
|
+
// never record or animate a zero / pre-layout height
|
|
1354
|
+
if (to === 0)
|
|
1355
|
+
return;
|
|
1356
|
+
lastBlockSize = to;
|
|
1357
|
+
if (from === null || from < 1 || Math.abs(from - to) < 1 || prefersReducedMotion()) {
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
if (config.resizingClass && renderer) {
|
|
1361
|
+
renderer.addClass(host, config.resizingClass);
|
|
1362
|
+
}
|
|
1363
|
+
const nextAnimation = host.animate([{ blockSize: `${from}px` }, { blockSize: `${to}px` }], { duration, easing });
|
|
1364
|
+
const cleanup = () => {
|
|
1365
|
+
if (config.resizingClass && renderer) {
|
|
1366
|
+
renderer.removeClass(host, config.resizingClass);
|
|
1367
|
+
}
|
|
1368
|
+
if (animation === nextAnimation) {
|
|
1369
|
+
animation = null;
|
|
1370
|
+
}
|
|
1371
|
+
};
|
|
1372
|
+
nextAnimation.finished.then(cleanup).catch(cleanup);
|
|
1373
|
+
animation = nextAnimation;
|
|
1374
|
+
};
|
|
1375
|
+
// Capture the settled height of the first render as the baseline, then only animate later changes.
|
|
1376
|
+
afterNextRender(() => {
|
|
1377
|
+
const host = hostElement();
|
|
1378
|
+
lastBlockSize = host ? host.getBoundingClientRect().height || null : null;
|
|
1379
|
+
ready = true;
|
|
1380
|
+
});
|
|
1381
|
+
effect(() => {
|
|
1382
|
+
// track every observed element's size
|
|
1383
|
+
dimensionSignals.forEach((dimensions) => dimensions());
|
|
1384
|
+
if (!ready)
|
|
1385
|
+
return;
|
|
1386
|
+
untracked(() => run());
|
|
1387
|
+
});
|
|
1388
|
+
destroyRef.onDestroy(() => animation?.cancel());
|
|
1389
|
+
};
|
|
1390
|
+
|
|
1391
|
+
/**
|
|
1392
|
+
* Creates a signal that animates the transition of a numeric signal's value over time.
|
|
1393
|
+
*/
|
|
1394
|
+
const signalAnimatedNumber = (source, options = {}) => {
|
|
1395
|
+
const ngZone = inject(NgZone);
|
|
1396
|
+
const duration = options.duration ?? 1000;
|
|
1397
|
+
const easing = options.easing ?? easeOut;
|
|
1398
|
+
const round = options.round ?? ((v) => Math.round(v));
|
|
1399
|
+
const initialValue = options.initialValue ?? 0;
|
|
1400
|
+
const animatedValue = signal(initialValue, /* @ts-ignore */
|
|
1401
|
+
...(ngDevMode ? [{ debugName: "animatedValue" }] : /* istanbul ignore next */ []));
|
|
1402
|
+
let rafId = null;
|
|
1403
|
+
let currentAnimatedValue = initialValue;
|
|
1404
|
+
const cancelAnimation = () => {
|
|
1405
|
+
if (rafId !== null) {
|
|
1406
|
+
cancelAnimationFrame(rafId);
|
|
1407
|
+
rafId = null;
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
const readonlySignal = animatedValue.asReadonly();
|
|
1411
|
+
const play = () => {
|
|
1412
|
+
const targetValue = isSignal(source) ? source() : source;
|
|
1413
|
+
cancelAnimation();
|
|
1414
|
+
const startValue = currentAnimatedValue;
|
|
1415
|
+
const startTime = performance.now();
|
|
1416
|
+
const delta = targetValue - startValue;
|
|
1417
|
+
// Skip animation if no change
|
|
1418
|
+
if (delta === 0) {
|
|
1419
|
+
options.onAnimationEnd?.();
|
|
1420
|
+
return readonlySignal;
|
|
1421
|
+
}
|
|
1422
|
+
options.onAnimationStart?.();
|
|
1423
|
+
ngZone.runOutsideAngular(() => {
|
|
1424
|
+
const animate = (timestamp) => {
|
|
1425
|
+
const elapsed = timestamp - startTime;
|
|
1426
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
1427
|
+
const easedProgress = easing(progress);
|
|
1428
|
+
const value = startValue + delta * easedProgress;
|
|
1429
|
+
currentAnimatedValue = value;
|
|
1430
|
+
untracked(() => {
|
|
1431
|
+
animatedValue.set(round(value));
|
|
1432
|
+
});
|
|
1433
|
+
if (progress < 1) {
|
|
1434
|
+
rafId = requestAnimationFrame(animate);
|
|
1435
|
+
}
|
|
1436
|
+
else {
|
|
1437
|
+
rafId = null;
|
|
1438
|
+
ngZone.run(() => {
|
|
1439
|
+
options.onAnimationEnd?.();
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
};
|
|
1443
|
+
rafId = requestAnimationFrame(animate);
|
|
1444
|
+
});
|
|
1445
|
+
return readonlySignal;
|
|
1446
|
+
};
|
|
1447
|
+
const reset = () => {
|
|
1448
|
+
cancelAnimation();
|
|
1449
|
+
currentAnimatedValue = initialValue;
|
|
1450
|
+
animatedValue.set(initialValue);
|
|
1451
|
+
return readonlySignal;
|
|
1452
|
+
};
|
|
1453
|
+
const stop = () => {
|
|
1454
|
+
cancelAnimation();
|
|
1455
|
+
return readonlySignal;
|
|
1456
|
+
};
|
|
1457
|
+
readonlySignal.play = play;
|
|
1458
|
+
readonlySignal.reset = reset;
|
|
1459
|
+
readonlySignal.stop = stop;
|
|
1460
|
+
return readonlySignal;
|
|
1461
|
+
};
|
|
1462
|
+
/* Linear easing function */
|
|
1463
|
+
const easeLinear = (t) => t;
|
|
1464
|
+
/* Ease in (slow start) */
|
|
1465
|
+
const easeIn = (t) => t * t;
|
|
1466
|
+
/* Ease out (slow end) */
|
|
1467
|
+
const easeOut = (t) => t * (2 - t);
|
|
1468
|
+
/* Ease in-out (slow start and end) */
|
|
1469
|
+
const easeInOut = (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t);
|
|
1470
|
+
/** Elastic easing function (overshoots) */
|
|
1471
|
+
const easeElastic = (t) => {
|
|
1472
|
+
const c4 = (2 * Math.PI) / 3;
|
|
1473
|
+
return t === 0 ? 0 : t === 1 ? 1 : Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
|
|
1474
|
+
};
|
|
1475
|
+
/** Back easing function (overshoots slightly then settles) */
|
|
1476
|
+
const easeOutBack = (t) => {
|
|
1477
|
+
const c1 = 1.70158;
|
|
1478
|
+
const c3 = c1 + 1;
|
|
1479
|
+
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
|
1480
|
+
};
|
|
1481
|
+
/** Back easing function with more overshoot */
|
|
1482
|
+
const easeOutBackStrong = (t) => {
|
|
1483
|
+
const c1 = 2.5;
|
|
1484
|
+
const c3 = c1 + 1;
|
|
1485
|
+
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
|
1486
|
+
};
|
|
1487
|
+
|
|
1488
|
+
const BREAKPOINT_ORDER = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];
|
|
1489
|
+
/**
|
|
1490
|
+
* Default viewport config based on Tailwind CSS.
|
|
1491
|
+
* @see https://tailwindcss.com/docs/screens
|
|
1492
|
+
*/
|
|
1493
|
+
const DEFAULT_VIEWPORT_CONFIG = {
|
|
1494
|
+
breakpoints: {
|
|
1495
|
+
xs: [0, 639],
|
|
1496
|
+
sm: [640, 767],
|
|
1497
|
+
md: [768, 1023],
|
|
1498
|
+
lg: [1024, 1279],
|
|
1499
|
+
xl: [1280, 1535],
|
|
1500
|
+
'2xl': [1536, Infinity],
|
|
1501
|
+
},
|
|
1502
|
+
};
|
|
1503
|
+
const [provideViewportConfig, injectViewportConfig] = createStaticRootProvider(DEFAULT_VIEWPORT_CONFIG, {
|
|
1504
|
+
name: 'Viewport Config',
|
|
1505
|
+
});
|
|
1506
|
+
const [provideBreakpointObserver, injectBreakpointObserver] = createRootProvider(() => {
|
|
1507
|
+
const breakpointObserver = inject(BreakpointObserver);
|
|
1508
|
+
const viewportConfig = injectViewportConfig();
|
|
1509
|
+
const isMediaQueryMatched = (mediaQuery) => breakpointObserver.isMatched(mediaQuery);
|
|
1510
|
+
const observeMediaQuery = (mediaQuery) => {
|
|
1511
|
+
return toSignal(breakpointObserver.observe(mediaQuery).pipe(map((x) => x.matches), startWith(isMediaQueryMatched(mediaQuery))), { requireSync: true });
|
|
1512
|
+
};
|
|
1513
|
+
const observeBreakpoint = (options) => observeMediaQuery(buildMediaQueryString(options));
|
|
1514
|
+
const isBreakpointMatched = (options) => isMediaQueryMatched(buildMediaQueryString(options));
|
|
1515
|
+
const getBreakpointSize = (type, option) => {
|
|
1516
|
+
const index = option === 'min' ? 0 : 1;
|
|
1517
|
+
const size = viewportConfig.breakpoints[type][index];
|
|
1518
|
+
if (size === Infinity || size === 0) {
|
|
1519
|
+
return size;
|
|
1520
|
+
}
|
|
1521
|
+
if (option === 'min') {
|
|
1522
|
+
return size;
|
|
1523
|
+
}
|
|
1524
|
+
// Due to scaling, the actual size of the viewport may be a decimal number.
|
|
1525
|
+
// Eg. on Windows 11 with 150% scaling, the viewport size may be 1535.33px
|
|
1526
|
+
// and thus not matching any of the default breakpoints.
|
|
1527
|
+
return size + 0.9;
|
|
1528
|
+
};
|
|
1529
|
+
const buildMediaQueryString = (options) => {
|
|
1530
|
+
if (!options.min && !options.max) {
|
|
1531
|
+
throw new Error('At least one of min or max must be defined');
|
|
1532
|
+
}
|
|
1533
|
+
const mediaQueryParts = [];
|
|
1534
|
+
if (options.min) {
|
|
1535
|
+
if (typeof options.min === 'number') {
|
|
1536
|
+
mediaQueryParts.push(`(min-width: ${options.min}px)`);
|
|
1537
|
+
}
|
|
1538
|
+
else {
|
|
1539
|
+
mediaQueryParts.push(`(min-width: ${getBreakpointSize(options.min, 'min')}px)`);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
if (options.min && options.max) {
|
|
1543
|
+
mediaQueryParts.push('and');
|
|
1544
|
+
}
|
|
1545
|
+
if (options.max) {
|
|
1546
|
+
if (typeof options.max === 'number') {
|
|
1547
|
+
mediaQueryParts.push(`(max-width: ${options.max}px)`);
|
|
1548
|
+
}
|
|
1549
|
+
else {
|
|
1550
|
+
mediaQueryParts.push(`(max-width: ${getBreakpointSize(options.max, 'max')}px)`);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
return mediaQueryParts.join(' ');
|
|
1554
|
+
};
|
|
1555
|
+
return {
|
|
1556
|
+
observeBreakpoint,
|
|
1557
|
+
isBreakpointMatched,
|
|
1558
|
+
getBreakpointSize,
|
|
1559
|
+
buildMediaQueryString,
|
|
1560
|
+
observeMediaQuery,
|
|
1561
|
+
isMediaQueryMatched,
|
|
1562
|
+
};
|
|
1563
|
+
}, { name: 'Breakpoint Observer' });
|
|
1564
|
+
|
|
1258
1565
|
const BREAKPOINT_KEY_SET = new Set(BREAKPOINT_ORDER);
|
|
1259
1566
|
const isBreakpointMap = (value) => {
|
|
1260
1567
|
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
@@ -2643,6 +2950,20 @@ const createLogger = (config) => {
|
|
|
2643
2950
|
};
|
|
2644
2951
|
|
|
2645
2952
|
const escapeHtml = (str) => str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
2953
|
+
/** Escapes HTML-special characters but leaves existing entity references intact — for input that
|
|
2954
|
+
* may already contain entities (e.g. Markdown serialized from the editor's DOM, where `&` is
|
|
2955
|
+
* stored as `&`), where plain escaping would double-escape them. */
|
|
2956
|
+
const escapeHtmlPreservingEntities = (str) => str
|
|
2957
|
+
.replace(/&(?![a-z]+;|#\d+;|#x[0-9a-f]+;)/gi, '&')
|
|
2958
|
+
.replace(/</g, '<')
|
|
2959
|
+
.replace(/>/g, '>')
|
|
2960
|
+
.replace(/"/g, '"');
|
|
2961
|
+
/** Rejects URL schemes that execute script when navigated. Whitespace/control characters are
|
|
2962
|
+
* stripped before checking — browsers ignore them inside URLs, so `java\tscript:` would
|
|
2963
|
+
* otherwise slip through. */
|
|
2964
|
+
const isSafeUrl = (url) =>
|
|
2965
|
+
// eslint-disable-next-line no-control-regex -- stripping control characters is the point: browsers ignore them inside URLs
|
|
2966
|
+
!/^(javascript|data|vbscript):/i.test(url.replace(/[\s\u0000-\u001f]/g, ''));
|
|
2646
2967
|
const unescapeHtml = (str) => str
|
|
2647
2968
|
.replace(/&/g, '&')
|
|
2648
2969
|
.replace(/</g, '<')
|
|
@@ -2656,30 +2977,158 @@ const parseTableRow = (line) => line
|
|
|
2656
2977
|
.slice(1, -1)
|
|
2657
2978
|
.map((cell) => cell.trim());
|
|
2658
2979
|
const isTableSeparatorLine = (line) => /^\|?(\s*:?-+:?\s*\|)+\s*$/.test(line);
|
|
2980
|
+
/** Column alignments from a GFM separator line: `:---` left, `:---:` center, `---:` right. */
|
|
2981
|
+
const parseSeparatorAligns = (line) => parseTableRow(line).map((cell) => {
|
|
2982
|
+
const left = cell.startsWith(':');
|
|
2983
|
+
const right = cell.endsWith(':');
|
|
2984
|
+
return left && right ? 'center' : right ? 'right' : left ? 'left' : null;
|
|
2985
|
+
});
|
|
2986
|
+
/** The GFM separator token for a column alignment. */
|
|
2987
|
+
const separatorFor = (align) => align === 'center' ? ':---:' : align === 'right' ? '---:' : align === 'left' ? ':---' : '---';
|
|
2988
|
+
const alignStyle = (align) => (align ? ` style="text-align: ${align}"` : '');
|
|
2659
2989
|
const makePlaceholder = (kind, idx) => `\u{E000}${kind}${idx}\u{E001}`;
|
|
2660
2990
|
const placeholderRe = (kind) => new RegExp(`\u{E000}${kind}(\\d+)\u{E001}`, 'gu');
|
|
2661
2991
|
const isPlaceholder = (kind, str) => new RegExp(`^\u{E000}${kind}\\d+\u{E001}$`, 'u').test(str);
|
|
2992
|
+
/** Tags allowed to survive inside an aligned block's raw-HTML passthrough. */
|
|
2993
|
+
const SAFE_INLINE_TAGS = new Set(['strong', 'em', 'b', 'i', 'del', 's', 'u', 'code', 'br']);
|
|
2994
|
+
/** Reduces raw inline HTML to the editor's own vocabulary: allowed tags lose all their attributes
|
|
2995
|
+
* (`<a>` keeps a safe `href`), everything else — including any event-handler attribute — is
|
|
2996
|
+
* dropped, and the remaining text is escaped. */
|
|
2997
|
+
const sanitizeInlineHtml = (html) => {
|
|
2998
|
+
const kept = [];
|
|
2999
|
+
const stashed = html.replace(/<\s*(\/?)\s*([a-z][a-z0-9]*)\b[^>]*>/gi, (full, closing, tag) => {
|
|
3000
|
+
const name = tag.toLowerCase();
|
|
3001
|
+
if (name === 'a') {
|
|
3002
|
+
const href = closing ? null : /\bhref\s*=\s*"([^"]*)"/i.exec(full)?.[1];
|
|
3003
|
+
const anchor = closing ? '</a>' : href && isSafeUrl(href) ? `<a href="${href}">` : '<a>';
|
|
3004
|
+
return makePlaceholder('TAG', kept.push(anchor) - 1);
|
|
3005
|
+
}
|
|
3006
|
+
if (!SAFE_INLINE_TAGS.has(name))
|
|
3007
|
+
return '';
|
|
3008
|
+
return makePlaceholder('TAG', kept.push(closing ? `</${name}>` : `<${name}>`) - 1);
|
|
3009
|
+
});
|
|
3010
|
+
return escapeHtmlPreservingEntities(stashed).replace(placeholderRe('TAG'), (_, i) => kept[+i] ?? '');
|
|
3011
|
+
};
|
|
2662
3012
|
const processInline = (text) => {
|
|
2663
3013
|
const inlineCodes = [];
|
|
2664
3014
|
text = text.replace(/`([^`]+)`/g, (_, code) => {
|
|
2665
3015
|
const idx = inlineCodes.push(`<code>${escapeHtml(code)}</code>`) - 1;
|
|
2666
3016
|
return makePlaceholder('IC', idx);
|
|
2667
3017
|
});
|
|
2668
|
-
//
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
text = text.replace(
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
text = text.replace(
|
|
2676
|
-
text = text.replace(
|
|
3018
|
+
// Raw HTML in Markdown text is escaped, not rendered — the editor writes this HTML straight
|
|
3019
|
+
// into the DOM via innerHTML, so anything else would let a crafted value inject markup. `<u>`
|
|
3020
|
+
// is the one deliberate exception: underline has no Markdown form and round-trips as raw <u>.
|
|
3021
|
+
text = escapeHtmlPreservingEntities(text).replace(/<(\/?)u>/gi, '<$1u>');
|
|
3022
|
+
// Bold + italic — *** before ** before *. A delimiter flanking whitespace doesn't open/close a
|
|
3023
|
+
// run (CommonMark's flanking rule), so literal asterisks as in `2 * 3 * 4` stay text.
|
|
3024
|
+
text = text.replace(/\*\*\*([^\s*](?:.*?[^\s*])?)\*\*\*/g, '<strong><em>$1</em></strong>');
|
|
3025
|
+
text = text.replace(/\*\*([^\s*](?:.*?[^\s*])?)\*\*/g, '<strong>$1</strong>');
|
|
3026
|
+
text = text.replace(/__([^\s_](?:.*?[^\s_])?)__/g, '<strong>$1</strong>');
|
|
3027
|
+
text = text.replace(/\*([^\s*](?:.*?[^\s*])?)\*/g, '<em>$1</em>');
|
|
3028
|
+
// Single-underscore emphasis must not fire inside a word (snake_case identifiers).
|
|
3029
|
+
text = text.replace(/(?<![\w*_])_([^\s_](?:.*?[^\s_])?)_(?![\w_])/g, '<em>$1</em>');
|
|
3030
|
+
text = text.replace(/~~([^\s~](?:.*?[^\s~])?)~~/g, '<del>$1</del>');
|
|
3031
|
+
// Images before links (order matters); URLs with script-running schemes stay literal text
|
|
3032
|
+
text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => isSafeUrl(src) ? `<img src="${src}" alt="${alt}">` : match);
|
|
3033
|
+
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, label, href) => isSafeUrl(href) ? `<a href="${href}">${label}</a>` : match);
|
|
2677
3034
|
return text.replace(placeholderRe('IC'), (_, i) => inlineCodes[+i] ?? '');
|
|
2678
3035
|
};
|
|
3036
|
+
// --- Nested list helpers (regex can't match balanced nesting, so these scan by tag depth) ---
|
|
3037
|
+
/** The first top-level `<ul>`/`<ol>` in `html`, matched with balanced nesting. */
|
|
3038
|
+
const findList = (html) => {
|
|
3039
|
+
const open = /<(ul|ol)\b[^>]*>/i.exec(html);
|
|
3040
|
+
if (!open)
|
|
3041
|
+
return null;
|
|
3042
|
+
const innerStart = open.index + open[0].length;
|
|
3043
|
+
const tag = /<(\/?)(?:ul|ol)\b[^>]*>/gi;
|
|
3044
|
+
tag.lastIndex = innerStart;
|
|
3045
|
+
let depth = 1;
|
|
3046
|
+
let match;
|
|
3047
|
+
while ((match = tag.exec(html))) {
|
|
3048
|
+
depth += match[1] ? -1 : 1;
|
|
3049
|
+
if (depth === 0) {
|
|
3050
|
+
return {
|
|
3051
|
+
start: open.index,
|
|
3052
|
+
end: tag.lastIndex,
|
|
3053
|
+
ordered: open[1].toLowerCase() === 'ol',
|
|
3054
|
+
inner: html.slice(innerStart, match.index),
|
|
3055
|
+
};
|
|
3056
|
+
}
|
|
3057
|
+
}
|
|
3058
|
+
return null;
|
|
3059
|
+
};
|
|
3060
|
+
/** The inner HTML of each direct `<li>` child of a list's inner HTML (balanced over nested lists). */
|
|
3061
|
+
const findListItems = (inner) => {
|
|
3062
|
+
const items = [];
|
|
3063
|
+
const tag = /<(\/?)li\b[^>]*>/gi;
|
|
3064
|
+
let depth = 0;
|
|
3065
|
+
let start = -1;
|
|
3066
|
+
let match;
|
|
3067
|
+
while ((match = tag.exec(inner))) {
|
|
3068
|
+
if (match[1]) {
|
|
3069
|
+
depth--;
|
|
3070
|
+
if (depth === 0 && start >= 0) {
|
|
3071
|
+
items.push(inner.slice(start, match.index));
|
|
3072
|
+
start = -1;
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
else {
|
|
3076
|
+
if (depth === 0)
|
|
3077
|
+
start = match.index + match[0].length;
|
|
3078
|
+
depth++;
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
return items;
|
|
3082
|
+
};
|
|
3083
|
+
/** Serializes a list's inner HTML to Markdown, indenting nested lists two spaces per level. */
|
|
3084
|
+
const listToMarkdown = (inner, ordered, depth) => {
|
|
3085
|
+
const indent = ' '.repeat(depth);
|
|
3086
|
+
let n = 1;
|
|
3087
|
+
return findListItems(inner)
|
|
3088
|
+
.map((itemInner) => {
|
|
3089
|
+
let content = itemInner;
|
|
3090
|
+
let nestedMarkdown = '';
|
|
3091
|
+
for (let nested = findList(content); nested; nested = findList(content)) {
|
|
3092
|
+
nestedMarkdown += `\n${listToMarkdown(nested.inner, nested.ordered, depth + 1)}`;
|
|
3093
|
+
content = content.slice(0, nested.start) + content.slice(nested.end);
|
|
3094
|
+
}
|
|
3095
|
+
const marker = ordered ? `${n++}. ` : '- ';
|
|
3096
|
+
// list items are single-line in this serializer, so a <br> degrades to a space
|
|
3097
|
+
return `${indent}${marker}${stripTags(content.replace(/<br\s*\/?>/gi, ' ')).trim()}${nestedMarkdown}`;
|
|
3098
|
+
})
|
|
3099
|
+
.join('\n');
|
|
3100
|
+
};
|
|
3101
|
+
/** Parses list lines into (indent-level, ordered, text) — two leading spaces per nesting level. */
|
|
3102
|
+
const parseListLines = (lines) => lines
|
|
3103
|
+
.map((line) => {
|
|
3104
|
+
const match = /^(\s*)([-*+]|\d+\.)\s+(.*)$/.exec(line);
|
|
3105
|
+
return match
|
|
3106
|
+
? { indent: Math.floor((match[1] ?? '').length / 2), ordered: /\d/.test(match[2] ?? ''), text: match[3] ?? '' }
|
|
3107
|
+
: null;
|
|
3108
|
+
})
|
|
3109
|
+
.filter((line) => line !== null);
|
|
3110
|
+
/** Builds nested `<ul>`/`<ol>` HTML from parsed list lines starting at `start`, for one indent level. */
|
|
3111
|
+
const buildListHtml = (lines, start, baseIndent) => {
|
|
3112
|
+
const ordered = lines[start]?.ordered ?? false;
|
|
3113
|
+
let items = '';
|
|
3114
|
+
let i = start;
|
|
3115
|
+
while (i < lines.length && (lines[i]?.indent ?? -1) === baseIndent) {
|
|
3116
|
+
let item = processInline(lines[i].text);
|
|
3117
|
+
i++;
|
|
3118
|
+
if (i < lines.length && (lines[i]?.indent ?? -1) > baseIndent) {
|
|
3119
|
+
const nested = buildListHtml(lines, i, lines[i].indent);
|
|
3120
|
+
item += nested.html;
|
|
3121
|
+
i = nested.next;
|
|
3122
|
+
}
|
|
3123
|
+
items += `<li>${item}</li>`;
|
|
3124
|
+
}
|
|
3125
|
+
const tag = ordered ? 'ol' : 'ul';
|
|
3126
|
+
return { html: `<${tag}>${items}</${tag}>`, next: i };
|
|
3127
|
+
};
|
|
2679
3128
|
/**
|
|
2680
3129
|
* Converts a markdown string to HTML.
|
|
2681
3130
|
* Covers headings, bold, italic, strikethrough, inline code, fenced code blocks,
|
|
2682
|
-
* links, images, block quotes, unordered/ordered lists, tables, horizontal rules, and paragraphs.
|
|
3131
|
+
* links, images, block quotes, unordered/ordered (and nested) lists, tables, horizontal rules, and paragraphs.
|
|
2683
3132
|
*/
|
|
2684
3133
|
const markdownToHtml = (markdown) => {
|
|
2685
3134
|
if (!markdown)
|
|
@@ -2704,6 +3153,16 @@ const markdownToHtml = (markdown) => {
|
|
|
2704
3153
|
return '';
|
|
2705
3154
|
if (isPlaceholder('CODE', trimmed))
|
|
2706
3155
|
return trimmed;
|
|
3156
|
+
// Aligned block: text-align has no Markdown form, so it round-trips as raw native HTML.
|
|
3157
|
+
// Rebuild it instead of passing it through verbatim: only the alignment survives on the tag
|
|
3158
|
+
// (no other attributes, e.g. event handlers) and the inner markup is reduced to the editor's
|
|
3159
|
+
// own inline vocabulary.
|
|
3160
|
+
const aligned = /^<(p|h[1-6]|div)\b[^>]*\bstyle=["'][^"']*text-align:\s*([a-z]+)[^>]*>([\s\S]*)<\/\1>$/i.exec(trimmed);
|
|
3161
|
+
if (aligned) {
|
|
3162
|
+
const tag = (aligned[1] ?? 'p').toLowerCase();
|
|
3163
|
+
const align = (aligned[2] ?? 'left').toLowerCase();
|
|
3164
|
+
return `<${tag} style="text-align: ${align}">${sanitizeInlineHtml(aligned[3] ?? '')}</${tag}>`;
|
|
3165
|
+
}
|
|
2707
3166
|
// Heading
|
|
2708
3167
|
const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)$/);
|
|
2709
3168
|
if (headingMatch?.[1] && headingMatch?.[2]) {
|
|
@@ -2713,46 +3172,39 @@ const markdownToHtml = (markdown) => {
|
|
|
2713
3172
|
// Horizontal rule
|
|
2714
3173
|
if (/^(---|\*\*\*|___)\s*$/.test(trimmed))
|
|
2715
3174
|
return '<hr>';
|
|
2716
|
-
// Table (GFM)
|
|
3175
|
+
// Table (GFM) — column alignment from the separator line lands as text-align on every cell
|
|
2717
3176
|
const tableLines = trimmed.split('\n');
|
|
2718
3177
|
if (tableLines.length >= 2 && /\|/.test(tableLines[0] ?? '') && isTableSeparatorLine(tableLines[1] ?? '')) {
|
|
3178
|
+
const aligns = parseSeparatorAligns(tableLines[1] ?? '');
|
|
2719
3179
|
const headers = parseTableRow(tableLines[0] ?? '');
|
|
2720
|
-
const thead = `<thead><tr>${headers
|
|
3180
|
+
const thead = `<thead><tr>${headers
|
|
3181
|
+
.map((h, i) => `<th${alignStyle(aligns[i] ?? null)}>${processInline(h)}</th>`)
|
|
3182
|
+
.join('')}</tr></thead>`;
|
|
2721
3183
|
const bodyRows = tableLines.slice(2);
|
|
2722
3184
|
const tbody = bodyRows.length > 0
|
|
2723
3185
|
? `<tbody>${bodyRows
|
|
2724
3186
|
.map((row) => `<tr>${parseTableRow(row)
|
|
2725
|
-
.map((cell) => `<td>${processInline(cell)}</td>`)
|
|
3187
|
+
.map((cell, i) => `<td${alignStyle(aligns[i] ?? null)}>${processInline(cell)}</td>`)
|
|
2726
3188
|
.join('')}</tr>`)
|
|
2727
3189
|
.join('')}</tbody>`
|
|
2728
3190
|
: '';
|
|
2729
3191
|
return `<table>${thead}${tbody}</table>`;
|
|
2730
3192
|
}
|
|
2731
|
-
// Blockquote
|
|
3193
|
+
// Blockquote — each line runs through the inline pass on its own (processInline escapes raw
|
|
3194
|
+
// HTML, so joining first would escape the <br> separators too)
|
|
2732
3195
|
if (/^> /.test(trimmed)) {
|
|
2733
3196
|
const content = trimmed
|
|
2734
3197
|
.split('\n')
|
|
2735
|
-
.map((l) => l.replace(/^>\s?/, ''))
|
|
3198
|
+
.map((l) => processInline(l.replace(/^>\s?/, '')))
|
|
2736
3199
|
.join('<br>');
|
|
2737
|
-
return `<blockquote>${
|
|
3200
|
+
return `<blockquote>${content}</blockquote>`;
|
|
2738
3201
|
}
|
|
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>`;
|
|
3202
|
+
// List (unordered/ordered, with indentation-based nesting)
|
|
3203
|
+
if (/^([-*+]|\d+\.)\s/.test(trimmed)) {
|
|
3204
|
+
const lines = parseListLines(trimmed.split('\n'));
|
|
3205
|
+
if (lines.length > 0) {
|
|
3206
|
+
return buildListHtml(lines, 0, lines[0].indent).html;
|
|
3207
|
+
}
|
|
2756
3208
|
}
|
|
2757
3209
|
// Paragraph — single newlines within a block become <br>
|
|
2758
3210
|
return `<p>${trimmed
|
|
@@ -2773,6 +3225,11 @@ const htmlToMarkdown = (html) => {
|
|
|
2773
3225
|
if (!html)
|
|
2774
3226
|
return '';
|
|
2775
3227
|
let md = html;
|
|
3228
|
+
// Aligned blocks: text-align has no Markdown form, so preserve them verbatim as native HTML (their
|
|
3229
|
+
// inner markup stays HTML) and round-trip via a placeholder — extracted before the block passes
|
|
3230
|
+
// below rewrite them, restored after the final tag-strip.
|
|
3231
|
+
const alignedBlocks = [];
|
|
3232
|
+
md = md.replace(/<(p|h[1-6]|div)\b[^>]*\bstyle="[^"]*text-align[^"]*"[^>]*>[\s\S]*?<\/\1>/gi, (match) => makePlaceholder('ALIGN', alignedBlocks.push(match) - 1));
|
|
2776
3233
|
// Code blocks — process before inline code
|
|
2777
3234
|
md = md.replace(/<pre[^>]*><code[^>]*class="language-([^"]+)"[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (_, lang, code) => `\n\`\`\`${lang}\n${unescapeHtml(code.trim())}\n\`\`\`\n`);
|
|
2778
3235
|
md = md.replace(/<pre[^>]*><code[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (_, code) => `\n\`\`\`\n${unescapeHtml(code.trim())}\n\`\`\`\n`);
|
|
@@ -2795,37 +3252,40 @@ const htmlToMarkdown = (html) => {
|
|
|
2795
3252
|
md = md.replace(/<img[^>]+src="([^"]*)"[^>]*alt="([^"]*)"[^>]*\/?>/gi, '');
|
|
2796
3253
|
md = md.replace(/<img[^>]+alt="([^"]*)"[^>]*src="([^"]*)"[^>]*\/?>/gi, '');
|
|
2797
3254
|
md = md.replace(/<img[^>]+src="([^"]*)"[^>]*\/?>/gi, '');
|
|
2798
|
-
//
|
|
3255
|
+
// Underline has no Markdown form — preserve it as raw <u> so it round-trips. Extract it now (its
|
|
3256
|
+
// inner markup is already converted above) so the block passes below don't strip the tag; the
|
|
3257
|
+
// placeholder is restored after the final tag-strip.
|
|
3258
|
+
const underlines = [];
|
|
3259
|
+
md = md.replace(/<u[^>]*>([\s\S]*?)<\/u>/gi, (_, inner) => makePlaceholder('U', underlines.push(inner) - 1));
|
|
3260
|
+
// Block quotes — <br> line breaks become one quoted line each (stripTags would swallow them)
|
|
2799
3261
|
md = md.replace(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/gi, (_, content) => {
|
|
2800
|
-
return (stripTags(content)
|
|
3262
|
+
return (stripTags(content.replace(/<br\s*\/?>/gi, '\n'))
|
|
2801
3263
|
.trim()
|
|
2802
3264
|
.split('\n')
|
|
2803
3265
|
.map((line) => `> ${line.trim()}`)
|
|
2804
3266
|
.join('\n') + '\n');
|
|
2805
3267
|
});
|
|
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
|
-
});
|
|
3268
|
+
// Lists — replace each top-level list with its recursively-serialized Markdown (handles nesting).
|
|
3269
|
+
for (let list = findList(md); list; list = findList(md)) {
|
|
3270
|
+
md = `${md.slice(0, list.start)}\n${listToMarkdown(list.inner, list.ordered, 0)}\n${md.slice(list.end)}`;
|
|
3271
|
+
}
|
|
2816
3272
|
// Tables
|
|
2817
3273
|
md = md.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_, tableContent) => {
|
|
2818
|
-
|
|
3274
|
+
// GFM cells are single-line, so a <br> inside one degrades to a space (not silently dropped)
|
|
3275
|
+
const extractCells = (row) => [...row.matchAll(/<t[hd][^>]*>([\s\S]*?)<\/t[hd]>/gi)].map((m) => stripTags((m[1] ?? '').replace(/<br\s*\/?>/gi, ' ')).trim());
|
|
3276
|
+
// GFM alignment is per column, read from the header cells' text-align styles
|
|
3277
|
+
const extractAligns = (row) => [...row.matchAll(/<t[hd]([^>]*)>/gi)].map((m) => {
|
|
3278
|
+
const align = /text-align:\s*(left|center|right)/i.exec(m[1] ?? '')?.[1]?.toLowerCase();
|
|
3279
|
+
return align ?? null;
|
|
3280
|
+
});
|
|
2819
3281
|
const theadMatch = tableContent.match(/<thead[^>]*>([\s\S]*?)<\/thead>/i);
|
|
2820
3282
|
const firstTrMatch = tableContent.match(/<tr[^>]*>([\s\S]*?)<\/tr>/i);
|
|
2821
|
-
const
|
|
2822
|
-
|
|
2823
|
-
: firstTrMatch
|
|
2824
|
-
? extractCells(firstTrMatch[1] ?? '')
|
|
2825
|
-
: [];
|
|
3283
|
+
const headerRow = theadMatch?.[1] ?? firstTrMatch?.[1] ?? '';
|
|
3284
|
+
const headerCells = headerRow ? extractCells(headerRow) : [];
|
|
2826
3285
|
if (headerCells.length === 0)
|
|
2827
3286
|
return '';
|
|
2828
|
-
const
|
|
3287
|
+
const aligns = extractAligns(headerRow);
|
|
3288
|
+
const separator = headerCells.map((_cell, i) => separatorFor(aligns[i] ?? null)).join(' | ');
|
|
2829
3289
|
const bodySource = theadMatch
|
|
2830
3290
|
? tableContent.replace(theadMatch[0], '')
|
|
2831
3291
|
: tableContent.replace(firstTrMatch?.[0] ?? '', '');
|
|
@@ -2833,13 +3293,19 @@ const htmlToMarkdown = (html) => {
|
|
|
2833
3293
|
const lines = [`| ${headerCells.join(' | ')} |`, `| ${separator} |`, ...bodyRows.map((r) => `| ${r} |`)];
|
|
2834
3294
|
return `\n${lines.join('\n')}\n`;
|
|
2835
3295
|
});
|
|
2836
|
-
// Block elements
|
|
2837
|
-
|
|
3296
|
+
// Block elements — a <br> inside a paragraph is a soft line break; turn it into a newline before
|
|
3297
|
+
// the tag-strip removes it, so it round-trips (a single newline within a block renders as <br>)
|
|
3298
|
+
md = md.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, (_, content) => `\n${stripTags(content.replace(/<br\s*\/?>/gi, '\n')).trim()}\n`);
|
|
3299
|
+
// A <div> boundary acts as a paragraph boundary — clipboard HTML commonly uses divs as
|
|
3300
|
+
// paragraphs, and silently stripping them would merge adjacent blocks into one.
|
|
3301
|
+
md = md.replace(/<\/div\s*>/gi, '\n\n').replace(/<div[^>]*>/gi, '\n\n');
|
|
2838
3302
|
md = md.replace(/<br\s*\/?>/gi, '\n');
|
|
2839
3303
|
md = md.replace(/<hr\s*\/?>/gi, '\n---\n');
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
3304
|
+
md = unescapeHtml(stripTags(md)).replace(placeholderRe('U'), (_, i) => `<u>${underlines[+i] ?? ''}</u>`);
|
|
3305
|
+
// aligned blocks are block-level, so pad with blank lines before collapsing so they survive as
|
|
3306
|
+
// their own Markdown block
|
|
3307
|
+
md = md.replace(placeholderRe('ALIGN'), (_, i) => `\n\n${alignedBlocks[+i] ?? ''}\n\n`);
|
|
3308
|
+
return md.replace(/\n{3,}/g, '\n\n').trim();
|
|
2843
3309
|
};
|
|
2844
3310
|
|
|
2845
3311
|
const clamp = (value, min = 0, max = 100) => {
|
|
@@ -3145,190 +3611,6 @@ const [provideLocale, injectLocale] = createRootProvider(() => {
|
|
|
3145
3611
|
name: 'Locale',
|
|
3146
3612
|
});
|
|
3147
3613
|
|
|
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
3614
|
const [provideStyleManager, injectStyleManager] = createRootProvider(() => {
|
|
3333
3615
|
const appRef = inject(ApplicationRef);
|
|
3334
3616
|
const destroyRef = inject(DestroyRef);
|
|
@@ -4596,12 +4878,14 @@ const createAnchoredPositionCleanup = (strategy, paneElement, overlayRef, render
|
|
|
4596
4878
|
middleware.push(flip({
|
|
4597
4879
|
fallbackPlacements: strategy.fallbackPlacements ?? undefined,
|
|
4598
4880
|
fallbackAxisSideDirection: 'start',
|
|
4881
|
+
boundary: strategy.boundary,
|
|
4599
4882
|
}));
|
|
4600
4883
|
if (strategy.shift !== false) {
|
|
4601
4884
|
middleware.push(shift({
|
|
4602
4885
|
crossAxis: typeof strategy.shift === 'object' ? (strategy.shift.crossAxis ?? false) : false,
|
|
4603
4886
|
limiter: limitShift(),
|
|
4604
4887
|
padding: strategy.viewportPadding ?? 8,
|
|
4888
|
+
boundary: strategy.boundary,
|
|
4605
4889
|
}));
|
|
4606
4890
|
}
|
|
4607
4891
|
// size must run AFTER shift so the available space is measured from the shifted position —
|
|
@@ -7563,5 +7847,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
|
|
|
7563
7847
|
* Generated bundle index. Do not edit.
|
|
7564
7848
|
*/
|
|
7565
7849
|
|
|
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 };
|
|
7850
|
+
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, 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
7851
|
//# sourceMappingURL=ethlete-core.mjs.map
|