ngx-kit 7.2.0 → 7.3.0
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/angle-selector/README.md +34 -34
- package/box-shadow/README.md +33 -33
- package/color-picker/README.md +52 -52
- package/{shared → core}/README.md +117 -117
- package/data-table/README.md +120 -120
- package/date-picker/README.md +74 -74
- package/dialog/README.md +122 -92
- package/drag-resize/README.md +53 -55
- package/fesm2022/ngx-kit-angle-selector.mjs +9 -26
- package/fesm2022/ngx-kit-angle-selector.mjs.map +1 -1
- package/fesm2022/ngx-kit-box-shadow.mjs +330 -356
- package/fesm2022/ngx-kit-box-shadow.mjs.map +1 -1
- package/fesm2022/ngx-kit-color-picker.mjs +132 -272
- package/fesm2022/ngx-kit-color-picker.mjs.map +1 -1
- package/fesm2022/{ngx-kit-shared.mjs → ngx-kit-core.mjs} +845 -277
- package/fesm2022/ngx-kit-core.mjs.map +1 -0
- package/fesm2022/ngx-kit-data-table.mjs +1 -1
- package/fesm2022/ngx-kit-data-table.mjs.map +1 -1
- package/fesm2022/ngx-kit-date-picker.mjs +27 -21
- package/fesm2022/ngx-kit-date-picker.mjs.map +1 -1
- package/fesm2022/ngx-kit-dialog.mjs +430 -331
- package/fesm2022/ngx-kit-dialog.mjs.map +1 -1
- package/fesm2022/ngx-kit-drag-resize.mjs +126 -45
- package/fesm2022/ngx-kit-drag-resize.mjs.map +1 -1
- package/fesm2022/ngx-kit-drawer-menu.mjs +2 -2
- package/fesm2022/ngx-kit-drawer-menu.mjs.map +1 -1
- package/fesm2022/ngx-kit-dropzone.mjs.map +1 -1
- package/fesm2022/ngx-kit-form-field.mjs.map +1 -1
- package/fesm2022/ngx-kit-gallery.mjs +3 -3
- package/fesm2022/ngx-kit-gallery.mjs.map +1 -1
- package/fesm2022/ngx-kit-gradient-picker.mjs +20 -38
- package/fesm2022/ngx-kit-gradient-picker.mjs.map +1 -1
- package/fesm2022/ngx-kit-grid-layout.mjs +14 -14
- package/fesm2022/ngx-kit-grid-layout.mjs.map +1 -1
- package/fesm2022/ngx-kit-image-editor.mjs +3 -3
- package/fesm2022/ngx-kit-image-editor.mjs.map +1 -1
- package/fesm2022/ngx-kit-menu.mjs +1 -1
- package/fesm2022/ngx-kit-menu.mjs.map +1 -1
- package/fesm2022/ngx-kit-message.mjs +1 -1
- package/fesm2022/ngx-kit-message.mjs.map +1 -1
- package/fesm2022/ngx-kit-notify.mjs +1 -1
- package/fesm2022/ngx-kit-notify.mjs.map +1 -1
- package/fesm2022/ngx-kit-time-picker.mjs +1 -1
- package/fesm2022/ngx-kit-time-picker.mjs.map +1 -1
- package/fesm2022/ngx-kit-virtual-scroll.mjs +2 -2
- package/fesm2022/ngx-kit-virtual-scroll.mjs.map +1 -1
- package/gallery/README.md +71 -71
- package/gradient-picker/README.md +41 -41
- package/grid-layout/README.md +64 -64
- package/image-editor/README.md +44 -44
- package/menu/README.md +85 -85
- package/message/README.md +70 -70
- package/notify/README.md +56 -56
- package/package.json +5 -9
- package/time-picker/README.md +89 -89
- package/types/ngx-kit-angle-selector.d.ts +13 -16
- package/types/ngx-kit-box-shadow.d.ts +37 -95
- package/types/ngx-kit-color-picker.d.ts +61 -58
- package/types/{ngx-kit-shared.d.ts → ngx-kit-core.d.ts} +156 -34
- package/types/ngx-kit-data-table.d.ts +2 -2
- package/types/ngx-kit-date-picker.d.ts +1 -1
- package/types/ngx-kit-dialog.d.ts +208 -107
- package/types/ngx-kit-drag-resize.d.ts +33 -6
- package/types/ngx-kit-gradient-picker.d.ts +4 -7
- package/types/ngx-kit-menu.d.ts +1 -1
- package/types/ngx-kit-time-picker.d.ts +1 -1
- package/virtual-scroll/README.md +199 -199
- package/fesm2022/ngx-kit-shared.mjs.map +0 -1
- package/fesm2022/ngx-kit-webflow.mjs +0 -709
- package/fesm2022/ngx-kit-webflow.mjs.map +0 -1
- package/types/ngx-kit-webflow.d.ts +0 -286
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i1 from '@angular/common';
|
|
2
2
|
import { CommonModule, isPlatformBrowser, DOCUMENT } from '@angular/common';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { EventEmitter, forwardRef, HostListener, ViewChild, Output, Input, ChangeDetectionStrategy, Component, inject, ChangeDetectorRef, InjectionToken, PLATFORM_ID, Inject, Injectable, DestroyRef, signal, computed } from '@angular/core';
|
|
4
|
+
import { EventEmitter, forwardRef, HostListener, ViewChild, Output, Input, ChangeDetectionStrategy, Component, inject, ChangeDetectorRef, InjectionToken, PLATFORM_ID, Inject, Injectable, DestroyRef, signal, computed, ApplicationRef, EnvironmentInjector, createComponent } from '@angular/core';
|
|
5
5
|
import { NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl, Validators } from '@angular/forms';
|
|
6
6
|
|
|
7
7
|
function getOffsetPosition(evt, parent) {
|
|
@@ -672,40 +672,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
|
|
|
672
672
|
args: ['window:resize']
|
|
673
673
|
}] } });
|
|
674
674
|
|
|
675
|
-
function isPlainObject(value) {
|
|
676
|
-
return (value !== null && typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype);
|
|
677
|
-
}
|
|
678
|
-
function mergeConfig(base, override) {
|
|
679
|
-
if (override == null) {
|
|
680
|
-
return structuredClone(base);
|
|
681
|
-
}
|
|
682
|
-
return merge(base, override);
|
|
683
|
-
}
|
|
684
|
-
function merge(base, override) {
|
|
685
|
-
if (override === undefined) {
|
|
686
|
-
return base;
|
|
687
|
-
}
|
|
688
|
-
if (base === undefined) {
|
|
689
|
-
return override;
|
|
690
|
-
}
|
|
691
|
-
if (Array.isArray(base) && Array.isArray(override)) {
|
|
692
|
-
return [...override];
|
|
693
|
-
}
|
|
694
|
-
if (isPlainObject(base) && isPlainObject(override)) {
|
|
695
|
-
const result = {};
|
|
696
|
-
const keys = new Set([...Object.keys(base), ...Object.keys(override)]);
|
|
697
|
-
for (const key of keys) {
|
|
698
|
-
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
|
699
|
-
continue;
|
|
700
|
-
}
|
|
701
|
-
result[key] = merge(base[key], override[key]);
|
|
702
|
-
}
|
|
703
|
-
return result;
|
|
704
|
-
}
|
|
705
|
-
// Primitive / Function / Class / Date / RegExp ...
|
|
706
|
-
return override;
|
|
707
|
-
}
|
|
708
|
-
|
|
709
675
|
const WINDOW = new InjectionToken('WINDOW', {
|
|
710
676
|
factory: () => (typeof window !== 'undefined' ? window : null),
|
|
711
677
|
});
|
|
@@ -865,6 +831,268 @@ function filterNodes(nodes, filterFn, childrenKeyName) {
|
|
|
865
831
|
return result;
|
|
866
832
|
}
|
|
867
833
|
|
|
834
|
+
const COLOR_FUNCTION_REGEX = /\b(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color)\(\s*[^)]*\)/gi;
|
|
835
|
+
const HEX_COLOR_REGEX = /#[a-f\d]{3,4}(?:[a-f\d]{2}){0,2}\b/gi;
|
|
836
|
+
/**
|
|
837
|
+
* CSS named colors.
|
|
838
|
+
*
|
|
839
|
+
* Includes all standard CSS color keywords plus:
|
|
840
|
+
* - transparent
|
|
841
|
+
* - currentcolor
|
|
842
|
+
*/
|
|
843
|
+
const CSS_COLOR_NAMES = {
|
|
844
|
+
aliceblue: '#f0f8ff',
|
|
845
|
+
antiquewhite: '#faebd7',
|
|
846
|
+
aqua: '#00ffff',
|
|
847
|
+
aquamarine: '#7fffd4',
|
|
848
|
+
azure: '#f0ffff',
|
|
849
|
+
beige: '#f5f5dc',
|
|
850
|
+
bisque: '#ffe4c4',
|
|
851
|
+
black: '#000000',
|
|
852
|
+
blanchedalmond: '#ffebcd',
|
|
853
|
+
blue: '#0000ff',
|
|
854
|
+
blueviolet: '#8a2be2',
|
|
855
|
+
brown: '#a52a2a',
|
|
856
|
+
burlywood: '#deb887',
|
|
857
|
+
cadetblue: '#5f9ea0',
|
|
858
|
+
chartreuse: '#7fff00',
|
|
859
|
+
chocolate: '#d2691e',
|
|
860
|
+
coral: '#ff7f50',
|
|
861
|
+
cornflowerblue: '#6495ed',
|
|
862
|
+
cornsilk: '#fff8dc',
|
|
863
|
+
crimson: '#dc143c',
|
|
864
|
+
cyan: '#00ffff',
|
|
865
|
+
darkblue: '#00008b',
|
|
866
|
+
darkcyan: '#008b8b',
|
|
867
|
+
darkgoldenrod: '#b8860b',
|
|
868
|
+
darkgray: '#a9a9a9',
|
|
869
|
+
darkgreen: '#006400',
|
|
870
|
+
darkgrey: '#a9a9a9',
|
|
871
|
+
darkkhaki: '#bdb76b',
|
|
872
|
+
darkmagenta: '#8b008b',
|
|
873
|
+
darkolivegreen: '#556b2f',
|
|
874
|
+
darkorange: '#ff8c00',
|
|
875
|
+
darkorchid: '#9932cc',
|
|
876
|
+
darkred: '#8b0000',
|
|
877
|
+
darksalmon: '#e9967a',
|
|
878
|
+
darkseagreen: '#8fbc8f',
|
|
879
|
+
darkslateblue: '#483d8b',
|
|
880
|
+
darkslategray: '#2f4f4f',
|
|
881
|
+
darkslategrey: '#2f4f4f',
|
|
882
|
+
darkturquoise: '#00ced1',
|
|
883
|
+
darkviolet: '#9400d3',
|
|
884
|
+
deeppink: '#ff1493',
|
|
885
|
+
deepskyblue: '#00bfff',
|
|
886
|
+
dimgray: '#696969',
|
|
887
|
+
dimgrey: '#696969',
|
|
888
|
+
dodgerblue: '#1e90ff',
|
|
889
|
+
firebrick: '#b22222',
|
|
890
|
+
floralwhite: '#fffaf0',
|
|
891
|
+
forestgreen: '#228b22',
|
|
892
|
+
fuchsia: '#ff00ff',
|
|
893
|
+
gainsboro: '#dcdcdc',
|
|
894
|
+
ghostwhite: '#f8f8ff',
|
|
895
|
+
gold: '#ffd700',
|
|
896
|
+
goldenrod: '#daa520',
|
|
897
|
+
gray: '#808080',
|
|
898
|
+
green: '#008000',
|
|
899
|
+
greenyellow: '#adff2f',
|
|
900
|
+
grey: '#808080',
|
|
901
|
+
honeydew: '#f0fff0',
|
|
902
|
+
hotpink: '#ff69b4',
|
|
903
|
+
indianred: '#cd5c5c',
|
|
904
|
+
indigo: '#4b0082',
|
|
905
|
+
ivory: '#fffff0',
|
|
906
|
+
khaki: '#f0e68c',
|
|
907
|
+
lavender: '#e6e6fa',
|
|
908
|
+
lavenderblush: '#fff0f5',
|
|
909
|
+
lawngreen: '#7cfc00',
|
|
910
|
+
lemonchiffon: '#fffacd',
|
|
911
|
+
lightblue: '#add8e6',
|
|
912
|
+
lightcoral: '#f08080',
|
|
913
|
+
lightcyan: '#e0ffff',
|
|
914
|
+
lightgoldenrodyellow: '#fafad2',
|
|
915
|
+
lightgray: '#d3d3d3',
|
|
916
|
+
lightgreen: '#90ee90',
|
|
917
|
+
lightgrey: '#d3d3d3',
|
|
918
|
+
lightpink: '#ffb6c1',
|
|
919
|
+
lightsalmon: '#ffa07a',
|
|
920
|
+
lightseagreen: '#20b2aa',
|
|
921
|
+
lightskyblue: '#87cefa',
|
|
922
|
+
lightslategray: '#778899',
|
|
923
|
+
lightslategrey: '#778899',
|
|
924
|
+
lightsteelblue: '#b0c4de',
|
|
925
|
+
lightyellow: '#ffffe0',
|
|
926
|
+
lime: '#00ff00',
|
|
927
|
+
limegreen: '#32cd32',
|
|
928
|
+
linen: '#faf0e6',
|
|
929
|
+
magenta: '#ff00ff',
|
|
930
|
+
maroon: '#800000',
|
|
931
|
+
mediumaquamarine: '#66cdaa',
|
|
932
|
+
mediumblue: '#0000cd',
|
|
933
|
+
mediumorchid: '#ba55d3',
|
|
934
|
+
mediumpurple: '#9370db',
|
|
935
|
+
mediumseagreen: '#3cb371',
|
|
936
|
+
mediumslateblue: '#7b68ee',
|
|
937
|
+
mediumspringgreen: '#00fa9a',
|
|
938
|
+
mediumturquoise: '#48d1cc',
|
|
939
|
+
mediumvioletred: '#c71585',
|
|
940
|
+
midnightblue: '#191970',
|
|
941
|
+
mintcream: '#f5fffa',
|
|
942
|
+
mistyrose: '#ffe4e1',
|
|
943
|
+
moccasin: '#ffe4b5',
|
|
944
|
+
navajowhite: '#ffdead',
|
|
945
|
+
navy: '#000080',
|
|
946
|
+
oldlace: '#fdf5e6',
|
|
947
|
+
olive: '#808000',
|
|
948
|
+
olivedrab: '#6b8e23',
|
|
949
|
+
orange: '#ffa500',
|
|
950
|
+
orangered: '#ff4500',
|
|
951
|
+
orchid: '#da70d6',
|
|
952
|
+
palegoldenrod: '#eee8aa',
|
|
953
|
+
palegreen: '#98fb98',
|
|
954
|
+
paleturquoise: '#afeeee',
|
|
955
|
+
palevioletred: '#db7093',
|
|
956
|
+
papayawhip: '#ffefd5',
|
|
957
|
+
peachpuff: '#ffdab9',
|
|
958
|
+
peru: '#cd853f',
|
|
959
|
+
pink: '#ffc0cb',
|
|
960
|
+
plum: '#dda0dd',
|
|
961
|
+
powderblue: '#b0e0e6',
|
|
962
|
+
purple: '#800080',
|
|
963
|
+
rebeccapurple: '#663399',
|
|
964
|
+
red: '#ff0000',
|
|
965
|
+
rosybrown: '#bc8f8f',
|
|
966
|
+
royalblue: '#4169e1',
|
|
967
|
+
saddlebrown: '#8b4513',
|
|
968
|
+
salmon: '#fa8072',
|
|
969
|
+
sandybrown: '#f4a460',
|
|
970
|
+
seagreen: '#2e8b57',
|
|
971
|
+
seashell: '#fff5ee',
|
|
972
|
+
sienna: '#a0522d',
|
|
973
|
+
silver: '#c0c0c0',
|
|
974
|
+
skyblue: '#87ceeb',
|
|
975
|
+
slateblue: '#6a5acd',
|
|
976
|
+
slategray: '#708090',
|
|
977
|
+
slategrey: '#708090',
|
|
978
|
+
snow: '#fffafa',
|
|
979
|
+
springgreen: '#00ff7f',
|
|
980
|
+
steelblue: '#4682b4',
|
|
981
|
+
tan: '#d2b48c',
|
|
982
|
+
teal: '#008080',
|
|
983
|
+
thistle: '#d8bfd8',
|
|
984
|
+
tomato: '#ff6347',
|
|
985
|
+
turquoise: '#40e0d0',
|
|
986
|
+
violet: '#ee82ee',
|
|
987
|
+
wheat: '#f5deb3',
|
|
988
|
+
white: '#ffffff',
|
|
989
|
+
whitesmoke: '#f5f5f5',
|
|
990
|
+
yellow: '#ffff00',
|
|
991
|
+
yellowgreen: '#9acd32',
|
|
992
|
+
};
|
|
993
|
+
/**
|
|
994
|
+
* Extract all CSS colors from an arbitrary CSS string.
|
|
995
|
+
*
|
|
996
|
+
* Supports:
|
|
997
|
+
* - Hex: #fff, #ffff, #ffffff, #ffffffff
|
|
998
|
+
* - rgb()
|
|
999
|
+
* - rgba()
|
|
1000
|
+
* - hsl()
|
|
1001
|
+
* - hsla()
|
|
1002
|
+
* - hwb()
|
|
1003
|
+
* - lab()
|
|
1004
|
+
* - lch()
|
|
1005
|
+
* - oklab()
|
|
1006
|
+
* - oklch()
|
|
1007
|
+
* - color()
|
|
1008
|
+
* - CSS named colors
|
|
1009
|
+
* - transparent
|
|
1010
|
+
* - currentcolor
|
|
1011
|
+
*
|
|
1012
|
+
* Works with values such as:
|
|
1013
|
+
*
|
|
1014
|
+
* box-shadow: 0 0 10px red, 0 0 20px #00ff00;
|
|
1015
|
+
* linear-gradient(red, rgba(0, 0, 0, .5));
|
|
1016
|
+
* radial-gradient(circle, blue 0%, transparent 100%);
|
|
1017
|
+
* conic-gradient(from 90deg, yellow, #ff0000);
|
|
1018
|
+
*/
|
|
1019
|
+
function extractCssColors(value) {
|
|
1020
|
+
if (!value) {
|
|
1021
|
+
return [];
|
|
1022
|
+
}
|
|
1023
|
+
const colors = [];
|
|
1024
|
+
const seen = new Set();
|
|
1025
|
+
const addColor = (color) => {
|
|
1026
|
+
const normalized = color.trim();
|
|
1027
|
+
if (!normalized) {
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
const key = normalized.toLowerCase();
|
|
1031
|
+
if (seen.has(key)) {
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
seen.add(key);
|
|
1035
|
+
colors.push(normalized);
|
|
1036
|
+
};
|
|
1037
|
+
// ---------------------------------------------------------
|
|
1038
|
+
// 1. Hex colors
|
|
1039
|
+
// ---------------------------------------------------------
|
|
1040
|
+
for (const match of value.matchAll(HEX_COLOR_REGEX)) {
|
|
1041
|
+
addColor(match[0]);
|
|
1042
|
+
}
|
|
1043
|
+
// ---------------------------------------------------------
|
|
1044
|
+
// 2. CSS color functions
|
|
1045
|
+
// ---------------------------------------------------------
|
|
1046
|
+
for (const match of value.matchAll(COLOR_FUNCTION_REGEX)) {
|
|
1047
|
+
const candidate = match[0];
|
|
1048
|
+
if (isValidCssColor(candidate)) {
|
|
1049
|
+
addColor(candidate);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
// ---------------------------------------------------------
|
|
1053
|
+
// 3. Named colors
|
|
1054
|
+
// ---------------------------------------------------------
|
|
1055
|
+
/**
|
|
1056
|
+
* We intentionally extract words instead of putting all named
|
|
1057
|
+
* colors into one huge regex.
|
|
1058
|
+
*/
|
|
1059
|
+
const words = value.match(/\b[a-z][a-z0-9-]*\b/gi) ?? [];
|
|
1060
|
+
for (const word of words) {
|
|
1061
|
+
const normalized = word.toLowerCase();
|
|
1062
|
+
if (!CSS_COLOR_NAMES[normalized]) {
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
if (isValidCssColor(word)) {
|
|
1066
|
+
addColor(word);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
return colors;
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Uses the browser's CSS parser to validate a color.
|
|
1073
|
+
*
|
|
1074
|
+
* This is much safer than trying to validate every possible
|
|
1075
|
+
* CSS color syntax manually.
|
|
1076
|
+
*/
|
|
1077
|
+
function isValidCssColor(value) {
|
|
1078
|
+
if (typeof CSS !== 'undefined' && typeof CSS.supports === 'function') {
|
|
1079
|
+
return CSS.supports('color', value);
|
|
1080
|
+
}
|
|
1081
|
+
// SSR / Node fallback.
|
|
1082
|
+
return isFallbackCssColor(value);
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Minimal validation for environments without CSS.supports(),
|
|
1086
|
+
* such as SSR or Node tests.
|
|
1087
|
+
*/
|
|
1088
|
+
function isFallbackCssColor(value) {
|
|
1089
|
+
const normalized = value.trim().toLowerCase();
|
|
1090
|
+
if (CSS_COLOR_NAMES[normalized]) {
|
|
1091
|
+
return true;
|
|
1092
|
+
}
|
|
1093
|
+
return /^#[a-f\d]{3,4}(?:[a-f\d]{2}){0,2}$/i.test(normalized);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
868
1096
|
class BrowserService {
|
|
869
1097
|
platformId;
|
|
870
1098
|
constructor(platformId) {
|
|
@@ -948,341 +1176,681 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
|
|
|
948
1176
|
}], ctorParameters: () => [] });
|
|
949
1177
|
|
|
950
1178
|
class OverlayRef {
|
|
951
|
-
|
|
952
|
-
|
|
1179
|
+
element;
|
|
1180
|
+
closeFn;
|
|
953
1181
|
componentRef;
|
|
954
|
-
|
|
955
|
-
constructor(
|
|
956
|
-
this.
|
|
957
|
-
this.
|
|
1182
|
+
template;
|
|
1183
|
+
constructor(element, closeFn, componentRef, template) {
|
|
1184
|
+
this.element = element;
|
|
1185
|
+
this.closeFn = closeFn;
|
|
958
1186
|
this.componentRef = componentRef;
|
|
959
|
-
this.
|
|
1187
|
+
this.template = template;
|
|
960
1188
|
}
|
|
961
1189
|
close() {
|
|
962
|
-
this.
|
|
1190
|
+
this.closeFn();
|
|
963
1191
|
}
|
|
964
1192
|
get nativeElement() {
|
|
965
|
-
return this.
|
|
1193
|
+
return this.element;
|
|
1194
|
+
}
|
|
1195
|
+
static noop() {
|
|
1196
|
+
return new OverlayRef(null, () => { });
|
|
966
1197
|
}
|
|
967
1198
|
}
|
|
968
1199
|
|
|
969
|
-
const FOCUSABLE_SELECTOR =
|
|
1200
|
+
const FOCUSABLE_SELECTOR = [
|
|
1201
|
+
'button:not([disabled])',
|
|
1202
|
+
'[href]',
|
|
1203
|
+
'input:not([disabled])',
|
|
1204
|
+
'select:not([disabled])',
|
|
1205
|
+
'textarea:not([disabled])',
|
|
1206
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
1207
|
+
].join(',');
|
|
970
1208
|
const DIALOG_OVERLAY_CLASSNAME = 'ngx-ui-overlay';
|
|
1209
|
+
const OVERLAY_HOST_CLASSNAME = 'ngx-ui-overlay-host';
|
|
1210
|
+
const BASE_Z_INDEX = 1000;
|
|
971
1211
|
class OverlayService {
|
|
972
|
-
|
|
973
|
-
listenersAttached = false;
|
|
974
|
-
resizeRafPending = false;
|
|
975
|
-
doc = inject(DOCUMENT);
|
|
976
|
-
abortController = new AbortController();
|
|
1212
|
+
document = inject(DOCUMENT);
|
|
977
1213
|
directionService = inject(DirectionService);
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1214
|
+
platformId = inject(PLATFORM_ID);
|
|
1215
|
+
applicationRef = inject(ApplicationRef);
|
|
1216
|
+
environmentInjector = inject(EnvironmentInjector);
|
|
1217
|
+
overlayStack = [];
|
|
1218
|
+
globalListenersAttached = false;
|
|
1219
|
+
layoutRaf = null;
|
|
1220
|
+
bodyScrollLockCount = 0;
|
|
1221
|
+
previousBodyOverflow = null;
|
|
1222
|
+
previousBodyPaddingRight = null;
|
|
1223
|
+
get isBrowser() {
|
|
1224
|
+
return isPlatformBrowser(this.platformId);
|
|
1225
|
+
}
|
|
1226
|
+
/** Number of overlays currently open. */
|
|
1227
|
+
get openCount() {
|
|
1228
|
+
return this.overlayStack.length;
|
|
981
1229
|
}
|
|
982
1230
|
open(options) {
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1231
|
+
if (!this.isBrowser) {
|
|
1232
|
+
return OverlayRef.noop();
|
|
1233
|
+
}
|
|
1234
|
+
const { component, viewContainerRef, configure, onClosed } = options;
|
|
1235
|
+
const instance = this.createComponentInstance(options, component, viewContainerRef);
|
|
1236
|
+
this.pushInstance(instance);
|
|
1237
|
+
this.renderHost(instance);
|
|
1238
|
+
this.attachGlobalListeners();
|
|
1239
|
+
instance.onClosed = onClosed;
|
|
1240
|
+
configure?.(instance.componentRef.instance, new OverlayRef(instance.element, instance.cleanup, instance.componentRef));
|
|
1241
|
+
this.scheduleInitialLayout(instance);
|
|
1242
|
+
return new OverlayRef(instance.element, instance.cleanup, instance.componentRef);
|
|
1243
|
+
}
|
|
1244
|
+
openTemplate(options) {
|
|
1245
|
+
if (!this.isBrowser) {
|
|
1246
|
+
return OverlayRef.noop();
|
|
986
1247
|
}
|
|
1248
|
+
const { template, appRef, configure, onClosed } = options;
|
|
1249
|
+
const instance = this.createTemplateInstance(options, template, appRef ?? this.applicationRef);
|
|
1250
|
+
this.pushInstance(instance);
|
|
1251
|
+
this.renderHost(instance);
|
|
987
1252
|
this.attachGlobalListeners();
|
|
988
|
-
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
|
|
1253
|
+
instance.onClosed = onClosed;
|
|
1254
|
+
const ref = new OverlayRef(instance.element, instance.cleanup, undefined, template);
|
|
1255
|
+
configure?.(instance, ref);
|
|
1256
|
+
this.scheduleInitialLayout(instance);
|
|
1257
|
+
return ref;
|
|
1258
|
+
}
|
|
1259
|
+
closeAll() {
|
|
1260
|
+
if (!this.isBrowser) {
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
[...this.overlayStack].forEach((instance) => {
|
|
1264
|
+
instance.cleanup();
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
// ---------------------------------------------------------------------------
|
|
1268
|
+
// instance creation
|
|
1269
|
+
// ---------------------------------------------------------------------------
|
|
1270
|
+
createComponentInstance(options, component, viewContainerRef) {
|
|
1271
|
+
const host = this.createHostElement(options);
|
|
1272
|
+
const backdrop = this.createBackdropElement(options);
|
|
1273
|
+
const element = this.createOverlayElement(options);
|
|
1274
|
+
host.appendChild(backdrop);
|
|
1275
|
+
host.appendChild(element);
|
|
1276
|
+
let componentRef;
|
|
1277
|
+
let attachedToAppRef = false;
|
|
1278
|
+
if (viewContainerRef) {
|
|
1279
|
+
componentRef = options.injector
|
|
1280
|
+
? viewContainerRef.createComponent(component, { injector: options.injector })
|
|
1281
|
+
: viewContainerRef.createComponent(component);
|
|
992
1282
|
}
|
|
1283
|
+
else {
|
|
1284
|
+
// No host ViewContainerRef available (e.g. a global service opening an
|
|
1285
|
+
// overlay outside of any component template) - create and attach the
|
|
1286
|
+
// component directly against the application, like a root component.
|
|
1287
|
+
componentRef = createComponent(component, {
|
|
1288
|
+
environmentInjector: this.environmentInjector,
|
|
1289
|
+
elementInjector: options.injector,
|
|
1290
|
+
});
|
|
1291
|
+
this.applicationRef.attachView(componentRef.hostView);
|
|
1292
|
+
attachedToAppRef = true;
|
|
1293
|
+
}
|
|
1294
|
+
element.appendChild(componentRef.location.nativeElement);
|
|
993
1295
|
const instance = {
|
|
1296
|
+
host,
|
|
1297
|
+
backdrop,
|
|
994
1298
|
element,
|
|
995
|
-
anchor,
|
|
996
|
-
point,
|
|
997
|
-
placementConfig: this.resolvePlacementConfig(placement, alignment, margin),
|
|
1299
|
+
anchor: options.anchor,
|
|
1300
|
+
point: options.point,
|
|
1301
|
+
placementConfig: this.resolvePlacementConfig(options.placement, options.alignment, options.margin),
|
|
1302
|
+
usePopover: this.resolveUsePopover(options),
|
|
1303
|
+
closeOnEscape: options.closeOnEscape ?? true,
|
|
1304
|
+
closeOnOutsideClick: options.closeOnOutsideClick ?? true,
|
|
1305
|
+
canClose: options.canClose,
|
|
1306
|
+
autoFocus: options.autoFocus ?? true,
|
|
1307
|
+
restoreFocus: options.restoreFocus ?? true,
|
|
1308
|
+
lockBodyScroll: options.lockBodyScroll ?? false,
|
|
998
1309
|
componentRef,
|
|
999
|
-
|
|
1310
|
+
attachedToAppRef,
|
|
1311
|
+
previousActiveElement: this.getActiveElement(),
|
|
1000
1312
|
rafId: null,
|
|
1001
1313
|
focusTimeoutId: null,
|
|
1002
1314
|
cleanup: () => { },
|
|
1003
1315
|
};
|
|
1004
1316
|
instance.cleanup = () => this.destroyInstance(instance);
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
this.
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
const { anchor, point, template, appRef, configure, onClosed, placement, alignment, margin } = options;
|
|
1014
|
-
this.attachGlobalListeners();
|
|
1015
|
-
const element = this.createDialogElement();
|
|
1317
|
+
return instance;
|
|
1318
|
+
}
|
|
1319
|
+
createTemplateInstance(options, template, appRef) {
|
|
1320
|
+
const host = this.createHostElement(options);
|
|
1321
|
+
const backdrop = this.createBackdropElement(options);
|
|
1322
|
+
const element = this.createOverlayElement(options);
|
|
1323
|
+
host.appendChild(backdrop);
|
|
1324
|
+
host.appendChild(element);
|
|
1016
1325
|
const view = template.createEmbeddedView({});
|
|
1017
1326
|
appRef.attachView(view);
|
|
1018
1327
|
element.append(...view.rootNodes);
|
|
1019
1328
|
const instance = {
|
|
1329
|
+
host,
|
|
1330
|
+
backdrop,
|
|
1020
1331
|
element,
|
|
1021
|
-
anchor,
|
|
1022
|
-
point,
|
|
1023
|
-
placementConfig: this.resolvePlacementConfig(placement, alignment, margin),
|
|
1332
|
+
anchor: options.anchor,
|
|
1333
|
+
point: options.point,
|
|
1334
|
+
placementConfig: this.resolvePlacementConfig(options.placement, options.alignment, options.margin),
|
|
1335
|
+
usePopover: this.resolveUsePopover(options),
|
|
1336
|
+
closeOnEscape: options.closeOnEscape ?? true,
|
|
1337
|
+
closeOnOutsideClick: options.closeOnOutsideClick ?? true,
|
|
1338
|
+
canClose: options.canClose,
|
|
1339
|
+
autoFocus: options.autoFocus ?? true,
|
|
1340
|
+
restoreFocus: options.restoreFocus ?? true,
|
|
1341
|
+
lockBodyScroll: options.lockBodyScroll ?? false,
|
|
1024
1342
|
embeddedView: view,
|
|
1025
1343
|
appRef,
|
|
1026
|
-
|
|
1344
|
+
previousActiveElement: this.getActiveElement(),
|
|
1027
1345
|
rafId: null,
|
|
1028
1346
|
focusTimeoutId: null,
|
|
1029
1347
|
cleanup: () => { },
|
|
1030
1348
|
};
|
|
1031
1349
|
instance.cleanup = () => this.destroyInstance(instance);
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
}
|
|
1043
|
-
// ---------------------------------------------------------------------
|
|
1044
|
-
// lifecycle
|
|
1045
|
-
// ---------------------------------------------------------------------
|
|
1046
|
-
resolvePlacementConfig(placement, alignment, margin) {
|
|
1047
|
-
return {
|
|
1048
|
-
placement: placement ?? 'auto',
|
|
1049
|
-
alignment: alignment ?? 'start',
|
|
1050
|
-
margin: margin ?? 8,
|
|
1051
|
-
};
|
|
1350
|
+
return instance;
|
|
1351
|
+
}
|
|
1352
|
+
pushInstance(instance) {
|
|
1353
|
+
this.overlayStack.push(instance);
|
|
1354
|
+
// Only matters as a fallback for browsers without the Popover API -
|
|
1355
|
+
// real popovers stack by top-layer show order regardless of z-index.
|
|
1356
|
+
instance.host.style.zIndex = String(BASE_Z_INDEX + this.overlayStack.length);
|
|
1357
|
+
if (instance.lockBodyScroll) {
|
|
1358
|
+
this.lockBodyScroll();
|
|
1359
|
+
}
|
|
1052
1360
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1361
|
+
// ---------------------------------------------------------------------------
|
|
1362
|
+
// DOM
|
|
1363
|
+
// ---------------------------------------------------------------------------
|
|
1364
|
+
createHostElement(options) {
|
|
1365
|
+
const usePopover = this.resolveUsePopover(options);
|
|
1366
|
+
const host = this.document.createElement('div');
|
|
1367
|
+
host.className = OVERLAY_HOST_CLASSNAME;
|
|
1368
|
+
host.setAttribute('data-ngx-overlay', '');
|
|
1369
|
+
host.style.position = 'fixed';
|
|
1370
|
+
host.style.inset = '0';
|
|
1371
|
+
// Neutralize the browser's default `[popover]` UA stylesheet
|
|
1372
|
+
// (margin: auto; width/height: fit-content; border: solid; padding: .25em;
|
|
1373
|
+
// background: Canvas; overflow: auto) so the host reliably fills the
|
|
1374
|
+
// viewport whether or not it ends up being a real popover.
|
|
1375
|
+
host.style.margin = '0';
|
|
1376
|
+
host.style.padding = '0';
|
|
1377
|
+
host.style.border = 'none';
|
|
1378
|
+
host.style.width = '100%';
|
|
1379
|
+
host.style.height = '100%';
|
|
1380
|
+
host.style.maxWidth = 'none';
|
|
1381
|
+
host.style.maxHeight = 'none';
|
|
1382
|
+
host.style.overflow = 'visible';
|
|
1383
|
+
host.style.background = 'transparent';
|
|
1384
|
+
host.style.color = 'inherit';
|
|
1385
|
+
host.style.zIndex = String(BASE_Z_INDEX);
|
|
1386
|
+
host.style.pointerEvents = 'auto';
|
|
1387
|
+
// Popover goes on the ROOT element, not the inner panel. This puts the
|
|
1388
|
+
// whole overlay (backdrop + panel) in the top layer as a single unit,
|
|
1389
|
+
// so stacking "just works" above everything on the page - including
|
|
1390
|
+
// ancestors with `overflow: hidden`, `transform`, or their own z-index
|
|
1391
|
+
// stacking contexts, which regular `position: fixed` cannot escape.
|
|
1392
|
+
// The inner `element` stays a normal `position: fixed` div and keeps
|
|
1393
|
+
// computing against the viewport, so all the placement math is unchanged.
|
|
1394
|
+
if (usePopover) {
|
|
1395
|
+
host.setAttribute('popover', 'manual');
|
|
1396
|
+
}
|
|
1397
|
+
return host;
|
|
1398
|
+
}
|
|
1399
|
+
createBackdropElement(options) {
|
|
1400
|
+
const backdrop = this.document.createElement('div');
|
|
1401
|
+
backdrop.className = 'ngx-ui-overlay-backdrop';
|
|
1402
|
+
backdrop.style.position = 'absolute';
|
|
1403
|
+
backdrop.style.inset = '0';
|
|
1404
|
+
backdrop.style.pointerEvents = 'auto';
|
|
1405
|
+
backdrop.style.background = 'transparent';
|
|
1406
|
+
this.addClasses(backdrop, options.backdropClass);
|
|
1407
|
+
return backdrop;
|
|
1408
|
+
}
|
|
1409
|
+
createOverlayElement(options) {
|
|
1410
|
+
const element = this.document.createElement('div');
|
|
1055
1411
|
element.className = DIALOG_OVERLAY_CLASSNAME;
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1412
|
+
element.setAttribute('role', options.role ?? 'dialog');
|
|
1413
|
+
if (options.ariaModal ?? true) {
|
|
1414
|
+
element.setAttribute('aria-modal', 'true');
|
|
1415
|
+
}
|
|
1416
|
+
if (options.ariaLabel) {
|
|
1417
|
+
element.setAttribute('aria-label', options.ariaLabel);
|
|
1418
|
+
}
|
|
1419
|
+
if (options.ariaLabelledby) {
|
|
1420
|
+
element.setAttribute('aria-labelledby', options.ariaLabelledby);
|
|
1421
|
+
}
|
|
1422
|
+
if (options.ariaDescribedby) {
|
|
1423
|
+
element.setAttribute('aria-describedby', options.ariaDescribedby);
|
|
1424
|
+
}
|
|
1425
|
+
element.setAttribute('tabindex', '-1');
|
|
1426
|
+
element.style.position = 'fixed';
|
|
1427
|
+
element.style.pointerEvents = 'auto';
|
|
1428
|
+
element.style.boxSizing = 'border-box';
|
|
1429
|
+
element.style.maxWidth = '100dvw'; // 'calc(100vw - 16px)';
|
|
1430
|
+
element.style.maxHeight = '100dvh'; // 'calc(100vh - 16px)';
|
|
1431
|
+
element.style.outline = 'none';
|
|
1432
|
+
element.style.border = 'none';
|
|
1433
|
+
element.style.padding = '0';
|
|
1434
|
+
element.style.margin = '0';
|
|
1435
|
+
element.style.overflow = 'auto';
|
|
1436
|
+
element.style.transformOrigin = 'left top';
|
|
1437
|
+
element.style.background = 'none';
|
|
1438
|
+
element.style.boxShadow = '0 0 20px light-dark(#79797955, #020202bd)';
|
|
1439
|
+
element.style.borderRadius = 'var(--ngx-dialog-corner-radius,1rem)';
|
|
1440
|
+
this.addClasses(element, options.panelClass);
|
|
1068
1441
|
return element;
|
|
1069
1442
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
}
|
|
1443
|
+
addClasses(element, classes) {
|
|
1444
|
+
if (!classes) {
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
const list = Array.isArray(classes) ? classes : classes.split(' ');
|
|
1448
|
+
for (const c of list) {
|
|
1449
|
+
if (c.trim()) {
|
|
1450
|
+
element.classList.add(c.trim());
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1080
1453
|
}
|
|
1454
|
+
renderHost(instance) {
|
|
1455
|
+
this.document.body.appendChild(instance.host);
|
|
1456
|
+
// Must show the popover (enter the top layer) BEFORE we ever measure
|
|
1457
|
+
// `instance.element`'s rect - while a popover host is closed it's
|
|
1458
|
+
// `display: none`, and everything inside it (including the panel)
|
|
1459
|
+
// would measure as a 0x0 box.
|
|
1460
|
+
this.showHostPopover(instance);
|
|
1461
|
+
instance.element.style.visibility = 'hidden';
|
|
1462
|
+
instance.element.style.position = 'fixed';
|
|
1463
|
+
instance.element.style.top = '0';
|
|
1464
|
+
instance.element.style.left = '0';
|
|
1465
|
+
}
|
|
1466
|
+
// ---------------------------------------------------------------------------
|
|
1467
|
+
// lifecycle
|
|
1468
|
+
// ---------------------------------------------------------------------------
|
|
1081
1469
|
destroyInstance(instance) {
|
|
1082
|
-
const index = this.
|
|
1083
|
-
if (index === -1)
|
|
1084
|
-
return;
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
if (instance.rafId !== null)
|
|
1088
|
-
cancelAnimationFrame(instance.rafId);
|
|
1089
|
-
|
|
1470
|
+
const index = this.overlayStack.indexOf(instance);
|
|
1471
|
+
if (index === -1) {
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
this.overlayStack.splice(index, 1);
|
|
1475
|
+
if (instance.rafId !== null) {
|
|
1476
|
+
this.cancelAnimationFrame(instance.rafId);
|
|
1477
|
+
instance.rafId = null;
|
|
1478
|
+
}
|
|
1479
|
+
if (instance.focusTimeoutId !== null) {
|
|
1090
1480
|
clearTimeout(instance.focusTimeoutId);
|
|
1481
|
+
instance.focusTimeoutId = null;
|
|
1482
|
+
}
|
|
1091
1483
|
if (instance.componentRef) {
|
|
1092
|
-
instance.
|
|
1484
|
+
if (instance.attachedToAppRef) {
|
|
1485
|
+
this.applicationRef.detachView(instance.componentRef.hostView);
|
|
1486
|
+
}
|
|
1093
1487
|
instance.componentRef.destroy();
|
|
1488
|
+
instance.componentRef = undefined;
|
|
1094
1489
|
}
|
|
1095
1490
|
if (instance.embeddedView && instance.appRef) {
|
|
1096
1491
|
instance.appRef.detachView(instance.embeddedView);
|
|
1097
1492
|
instance.embeddedView.destroy();
|
|
1493
|
+
instance.embeddedView = undefined;
|
|
1494
|
+
}
|
|
1495
|
+
this.hideHostPopover(instance);
|
|
1496
|
+
instance.host.remove();
|
|
1497
|
+
if (instance.lockBodyScroll) {
|
|
1498
|
+
this.unlockBodyScroll();
|
|
1499
|
+
}
|
|
1500
|
+
instance.onClosed?.();
|
|
1501
|
+
// Only tear down the shared/global listeners once *every* overlay is
|
|
1502
|
+
// closed - previously this ran unconditionally on every close, which
|
|
1503
|
+
// silently broke Escape/outside-click/reposition for any overlays that
|
|
1504
|
+
// were still open underneath.
|
|
1505
|
+
if (this.overlayStack.length === 0) {
|
|
1506
|
+
this.detachGlobalListeners();
|
|
1507
|
+
}
|
|
1508
|
+
if (instance.restoreFocus) {
|
|
1509
|
+
this.restoreFocus(instance.previousActiveElement);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
// ---------------------------------------------------------------------------
|
|
1513
|
+
// popover helpers
|
|
1514
|
+
// ---------------------------------------------------------------------------
|
|
1515
|
+
resolveUsePopover(options) {
|
|
1516
|
+
return (options.usePopover ?? true) && this.supportsPopover();
|
|
1517
|
+
}
|
|
1518
|
+
showHostPopover(instance) {
|
|
1519
|
+
if (!instance.usePopover) {
|
|
1520
|
+
return;
|
|
1098
1521
|
}
|
|
1099
|
-
// قبل از remove()، دیالوگ رو با متد استاندارد close() میبندیم؛ این کار دو
|
|
1100
|
-
// فایده داره که با remove() ساده به دست نمیاومدن:
|
|
1101
|
-
// ۱) فوکوس بهصورت خودکار به همون المنتی که قبل از باز شدن دیالوگ فوکوس
|
|
1102
|
-
// داشت برمیگرده (رفتار استاندارد close() روی <dialog>) — با remove()
|
|
1103
|
-
// ساده این اتفاق نمیافته و فوکوس معمولاً روی body گم میشه.
|
|
1104
|
-
// ۲) رویداد 'close' استاندارد شلیک میشه، برای کدی که ممکنه به اون گوش بده.
|
|
1105
|
-
// در try/catch گذاشتیمش چون در محیط تست (jsdom) این متدها mock هستن و ممکنه
|
|
1106
|
-
// رفتار متفاوتی داشته باشن.
|
|
1107
1522
|
try {
|
|
1108
|
-
|
|
1109
|
-
instance.element.close();
|
|
1523
|
+
instance.host.showPopover();
|
|
1110
1524
|
}
|
|
1111
1525
|
catch {
|
|
1112
|
-
|
|
1526
|
+
// Already open, or environment doesn't fully support it - ignore.
|
|
1113
1527
|
}
|
|
1114
|
-
instance.element.remove();
|
|
1115
|
-
instance.onClosed?.();
|
|
1116
1528
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1529
|
+
hideHostPopover(instance) {
|
|
1530
|
+
if (!instance.usePopover) {
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
try {
|
|
1534
|
+
if (instance.host.matches(':popover-open')) {
|
|
1535
|
+
instance.host.hidePopover();
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
catch {
|
|
1539
|
+
// Not open, or already removed from the DOM - ignore.
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
supportsPopover() {
|
|
1543
|
+
return (typeof HTMLElement !== 'undefined' &&
|
|
1544
|
+
'popover' in HTMLElement.prototype &&
|
|
1545
|
+
typeof HTMLElement.prototype.showPopover === 'function' &&
|
|
1546
|
+
typeof HTMLElement.prototype.hidePopover === 'function');
|
|
1547
|
+
}
|
|
1548
|
+
// ---------------------------------------------------------------------------
|
|
1549
|
+
// body scroll lock
|
|
1550
|
+
// ---------------------------------------------------------------------------
|
|
1551
|
+
lockBodyScroll() {
|
|
1552
|
+
this.bodyScrollLockCount++;
|
|
1553
|
+
if (this.bodyScrollLockCount > 1) {
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
const body = this.document.body;
|
|
1557
|
+
this.previousBodyOverflow = body.style.overflow;
|
|
1558
|
+
this.previousBodyPaddingRight = body.style.paddingRight;
|
|
1559
|
+
const scrollbarWidth = window.innerWidth - this.document.documentElement.clientWidth;
|
|
1560
|
+
body.style.overflow = 'hidden';
|
|
1561
|
+
if (scrollbarWidth > 0) {
|
|
1562
|
+
const currentPaddingRight = parseFloat(window.getComputedStyle(body).paddingRight) || 0;
|
|
1563
|
+
body.style.paddingRight = `${currentPaddingRight + scrollbarWidth}px`;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
unlockBodyScroll() {
|
|
1567
|
+
if (this.bodyScrollLockCount === 0) {
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
this.bodyScrollLockCount--;
|
|
1571
|
+
if (this.bodyScrollLockCount > 0) {
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
const body = this.document.body;
|
|
1575
|
+
body.style.overflow = this.previousBodyOverflow ?? '';
|
|
1576
|
+
body.style.paddingRight = this.previousBodyPaddingRight ?? '';
|
|
1577
|
+
this.previousBodyOverflow = null;
|
|
1578
|
+
this.previousBodyPaddingRight = null;
|
|
1579
|
+
}
|
|
1580
|
+
// ---------------------------------------------------------------------------
|
|
1581
|
+
// global listeners
|
|
1582
|
+
// ---------------------------------------------------------------------------
|
|
1120
1583
|
attachGlobalListeners() {
|
|
1121
|
-
if (this.
|
|
1584
|
+
if (!this.isBrowser || this.globalListenersAttached) {
|
|
1122
1585
|
return;
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
this.
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1586
|
+
}
|
|
1587
|
+
// Flip the flag synchronously to avoid a race: if two overlays open in
|
|
1588
|
+
// the same tick, the second call must see the flag already set instead
|
|
1589
|
+
// of scheduling a second (duplicate) round of addEventListener calls.
|
|
1590
|
+
this.globalListenersAttached = true;
|
|
1591
|
+
setTimeout(() => {
|
|
1592
|
+
this.document.addEventListener('keydown', this.onDocumentKeydown);
|
|
1593
|
+
this.document.addEventListener('click', this.onDocumentClick);
|
|
1594
|
+
this.document.addEventListener('contextmenu', this.onDocumentContextMenu);
|
|
1595
|
+
this.document.addEventListener('scroll', this.onDocumentScroll, true);
|
|
1596
|
+
window.addEventListener('resize', this.onWindowResize);
|
|
1597
|
+
}, 0);
|
|
1598
|
+
}
|
|
1599
|
+
detachGlobalListeners() {
|
|
1600
|
+
if (!this.isBrowser || !this.globalListenersAttached) {
|
|
1132
1601
|
return;
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1602
|
+
}
|
|
1603
|
+
this.globalListenersAttached = false;
|
|
1604
|
+
this.document.removeEventListener('keydown', this.onDocumentKeydown);
|
|
1605
|
+
this.document.removeEventListener('click', this.onDocumentClick);
|
|
1606
|
+
this.document.removeEventListener('contextmenu', this.onDocumentContextMenu);
|
|
1607
|
+
this.document.removeEventListener('scroll', this.onDocumentScroll, true);
|
|
1608
|
+
window.removeEventListener('resize', this.onWindowResize);
|
|
1609
|
+
}
|
|
1610
|
+
onDocumentKeydown = (event) => {
|
|
1611
|
+
const instance = this.getLastInstance();
|
|
1612
|
+
if (!instance) {
|
|
1138
1613
|
return;
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1614
|
+
}
|
|
1615
|
+
if (event.key === 'Escape') {
|
|
1616
|
+
if (!instance.closeOnEscape || (instance.canClose && !instance.canClose())) {
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
event.preventDefault();
|
|
1620
|
+
instance.cleanup();
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
if (event.key === 'Tab') {
|
|
1624
|
+
this.trapFocus(event, instance);
|
|
1625
|
+
}
|
|
1144
1626
|
};
|
|
1145
|
-
|
|
1146
|
-
this.
|
|
1627
|
+
onDocumentClick = (event) => {
|
|
1628
|
+
const instance = this.getLastInstance();
|
|
1629
|
+
if (!instance) {
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
const target = event.target;
|
|
1633
|
+
if (!target) {
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
if (instance.element.contains(target)) {
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
if (instance.anchor && instance.anchor.contains(target)) {
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
if (!instance.closeOnOutsideClick || (instance.canClose && !instance.canClose())) {
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
if (event.target !== instance.backdrop) {
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
instance.cleanup();
|
|
1147
1649
|
};
|
|
1148
|
-
|
|
1149
|
-
const
|
|
1150
|
-
if (!
|
|
1650
|
+
onDocumentContextMenu = (event) => {
|
|
1651
|
+
const instance = this.getLastInstance();
|
|
1652
|
+
if (!instance) {
|
|
1151
1653
|
return;
|
|
1152
|
-
|
|
1153
|
-
if (
|
|
1654
|
+
}
|
|
1655
|
+
if (instance.point && instance.anchor && instance.anchor.contains(event.target)) {
|
|
1656
|
+
event.preventDefault();
|
|
1657
|
+
instance.point = {
|
|
1658
|
+
x: event.clientX,
|
|
1659
|
+
y: event.clientY,
|
|
1660
|
+
};
|
|
1661
|
+
this.scheduleReposition();
|
|
1154
1662
|
return;
|
|
1155
|
-
|
|
1156
|
-
if (
|
|
1663
|
+
}
|
|
1664
|
+
if (instance.element.contains(event.target)) {
|
|
1157
1665
|
return;
|
|
1158
|
-
const rect = lastDialog.element.getBoundingClientRect();
|
|
1159
|
-
const clickWasInsideDialog = rect.top <= event.clientY &&
|
|
1160
|
-
event.clientY <= rect.bottom &&
|
|
1161
|
-
rect.left <= event.clientX &&
|
|
1162
|
-
event.clientX <= rect.right;
|
|
1163
|
-
const anchRect = lastDialog.anchor.getBoundingClientRect();
|
|
1164
|
-
const clickWasInsideAnchor = anchRect.top <= event.clientY &&
|
|
1165
|
-
event.clientY <= anchRect.bottom &&
|
|
1166
|
-
anchRect.left <= event.clientX &&
|
|
1167
|
-
event.clientX <= anchRect.right;
|
|
1168
|
-
if (!clickWasInsideDialog) {
|
|
1169
|
-
if (event.type == 'contextmenu' && lastDialog.point && clickWasInsideAnchor) {
|
|
1170
|
-
event.preventDefault();
|
|
1171
|
-
lastDialog.point = {
|
|
1172
|
-
x: event.clientX,
|
|
1173
|
-
y: event.clientY,
|
|
1174
|
-
};
|
|
1175
|
-
this.positionDialog(lastDialog);
|
|
1176
|
-
}
|
|
1177
|
-
else {
|
|
1178
|
-
lastDialog.cleanup();
|
|
1179
|
-
}
|
|
1180
1666
|
}
|
|
1667
|
+
if (!instance.closeOnOutsideClick || (instance.canClose && !instance.canClose())) {
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
if (event.target !== instance.backdrop) {
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
instance.cleanup();
|
|
1674
|
+
};
|
|
1675
|
+
onDocumentScroll = () => {
|
|
1676
|
+
this.scheduleReposition();
|
|
1677
|
+
};
|
|
1678
|
+
onWindowResize = () => {
|
|
1679
|
+
this.scheduleReposition();
|
|
1680
|
+
};
|
|
1681
|
+
scheduleReposition() {
|
|
1682
|
+
if (this.layoutRaf !== null) {
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
this.layoutRaf = this.requestAnimationFrame(() => {
|
|
1686
|
+
this.layoutRaf = null;
|
|
1687
|
+
const instances = [...this.overlayStack];
|
|
1688
|
+
for (const instance of instances) {
|
|
1689
|
+
this.positionOverlay(instance);
|
|
1690
|
+
}
|
|
1691
|
+
});
|
|
1181
1692
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
this.positionDialog(instance);
|
|
1693
|
+
// ---------------------------------------------------------------------------
|
|
1694
|
+
// focus
|
|
1695
|
+
// ---------------------------------------------------------------------------
|
|
1696
|
+
scheduleInitialLayout(instance) {
|
|
1697
|
+
instance.rafId = this.requestAnimationFrame(() => {
|
|
1698
|
+
instance.rafId = null;
|
|
1699
|
+
this.positionOverlay(instance);
|
|
1700
|
+
if (!instance.autoFocus) {
|
|
1701
|
+
return;
|
|
1192
1702
|
}
|
|
1703
|
+
instance.focusTimeoutId = setTimeout(() => {
|
|
1704
|
+
instance.focusTimeoutId = null;
|
|
1705
|
+
this.focusInitialElement(instance);
|
|
1706
|
+
}, 0);
|
|
1193
1707
|
});
|
|
1194
1708
|
}
|
|
1195
|
-
|
|
1196
|
-
const
|
|
1197
|
-
|
|
1198
|
-
|
|
1709
|
+
focusInitialElement(instance) {
|
|
1710
|
+
const focusable = instance.element.querySelector(FOCUSABLE_SELECTOR);
|
|
1711
|
+
focusable?.focus() ?? instance.element.focus();
|
|
1712
|
+
}
|
|
1713
|
+
trapFocus(event, instance) {
|
|
1714
|
+
const elements = Array.from(instance.element.querySelectorAll(FOCUSABLE_SELECTOR));
|
|
1715
|
+
if (!elements.length) {
|
|
1716
|
+
event.preventDefault();
|
|
1717
|
+
instance.element.focus();
|
|
1199
1718
|
return;
|
|
1200
|
-
let anchorRect;
|
|
1201
|
-
if (point) {
|
|
1202
|
-
anchorRect = new DOMRect(point.x, point.y, 0, 0);
|
|
1203
1719
|
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1720
|
+
const first = elements[0];
|
|
1721
|
+
const last = elements[elements.length - 1];
|
|
1722
|
+
const active = this.getActiveElement();
|
|
1723
|
+
if (!event.shiftKey && active === last) {
|
|
1724
|
+
event.preventDefault();
|
|
1725
|
+
first.focus();
|
|
1726
|
+
return;
|
|
1206
1727
|
}
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
const isRTL = this.directionService.isRtl();
|
|
1211
|
-
let top = 0;
|
|
1212
|
-
let left = 'auto';
|
|
1213
|
-
let right = 'auto';
|
|
1214
|
-
const spaceBelow = vh - anchorRect.bottom;
|
|
1215
|
-
const spaceAbove = anchorRect.top;
|
|
1216
|
-
let verticalPos = 'above';
|
|
1217
|
-
if (placement === 'bottom' ||
|
|
1218
|
-
(placement === 'auto' && spaceBelow >= dialogRect.height + margin)) {
|
|
1219
|
-
top = anchorRect.bottom + margin;
|
|
1220
|
-
verticalPos = 'above';
|
|
1221
|
-
if (top + dialogRect.height > vh - margin) {
|
|
1222
|
-
top = vh - dialogRect.height - margin;
|
|
1223
|
-
verticalPos = 'bottom';
|
|
1224
|
-
}
|
|
1728
|
+
if (event.shiftKey && active === first) {
|
|
1729
|
+
event.preventDefault();
|
|
1730
|
+
last.focus();
|
|
1225
1731
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
}
|
|
1732
|
+
}
|
|
1733
|
+
restoreFocus(element) {
|
|
1734
|
+
if (element && element.isConnected) {
|
|
1735
|
+
setTimeout(() => {
|
|
1736
|
+
element.focus();
|
|
1737
|
+
}, 0);
|
|
1233
1738
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1739
|
+
}
|
|
1740
|
+
getActiveElement() {
|
|
1741
|
+
const active = this.document.activeElement;
|
|
1742
|
+
return active instanceof HTMLElement ? active : null;
|
|
1743
|
+
}
|
|
1744
|
+
// ---------------------------------------------------------------------------
|
|
1745
|
+
// positioning
|
|
1746
|
+
// ---------------------------------------------------------------------------
|
|
1747
|
+
positionOverlay(instance) {
|
|
1748
|
+
if (!instance.host.isConnected || !instance.element.isConnected) {
|
|
1749
|
+
return;
|
|
1236
1750
|
}
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1751
|
+
const viewportWidth = window.innerWidth;
|
|
1752
|
+
const viewportHeight = window.innerHeight;
|
|
1753
|
+
const anchorRect = this.getAnchorRect(instance);
|
|
1754
|
+
const overlay = instance.element;
|
|
1755
|
+
const overlayRect = overlay.getBoundingClientRect();
|
|
1756
|
+
const { placement, alignment, margin } = instance.placementConfig;
|
|
1757
|
+
const isRTL = this.directionService.isRtl();
|
|
1758
|
+
let top;
|
|
1759
|
+
if (placement === 'center') {
|
|
1760
|
+
top = anchorRect.top + anchorRect.height / 2 - overlayRect.height / 2;
|
|
1244
1761
|
}
|
|
1245
|
-
else
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
left = anchorRect.left;
|
|
1762
|
+
else {
|
|
1763
|
+
const spaceBelow = viewportHeight - anchorRect.bottom;
|
|
1764
|
+
const placeBelow = placement === 'bottom' ||
|
|
1765
|
+
(placement === 'auto' && spaceBelow >= overlayRect.height + margin);
|
|
1766
|
+
if (placeBelow) {
|
|
1767
|
+
top = anchorRect.bottom + margin;
|
|
1252
1768
|
}
|
|
1253
1769
|
else {
|
|
1254
|
-
|
|
1770
|
+
top = anchorRect.top - overlayRect.height - margin;
|
|
1255
1771
|
}
|
|
1772
|
+
overlay.classList.toggle('tips-below', placeBelow);
|
|
1773
|
+
overlay.classList.toggle('tips-above', !placeBelow);
|
|
1774
|
+
}
|
|
1775
|
+
if (top + overlayRect.height > viewportHeight - margin) {
|
|
1776
|
+
top = viewportHeight - overlayRect.height - margin;
|
|
1256
1777
|
}
|
|
1257
|
-
if (
|
|
1258
|
-
|
|
1778
|
+
if (top < margin) {
|
|
1779
|
+
top = margin;
|
|
1780
|
+
}
|
|
1781
|
+
let left;
|
|
1782
|
+
switch (alignment) {
|
|
1783
|
+
case 'center':
|
|
1784
|
+
left = anchorRect.left + anchorRect.width / 2 - overlayRect.width / 2;
|
|
1785
|
+
break;
|
|
1786
|
+
case 'end':
|
|
1787
|
+
left = isRTL ? anchorRect.left : anchorRect.right - overlayRect.width;
|
|
1788
|
+
break;
|
|
1789
|
+
case 'start':
|
|
1790
|
+
default:
|
|
1791
|
+
left = isRTL ? anchorRect.right - overlayRect.width : anchorRect.left;
|
|
1792
|
+
break;
|
|
1793
|
+
}
|
|
1794
|
+
left = Math.min(Math.max(left, margin), Math.max(margin, viewportWidth - overlayRect.width - margin));
|
|
1795
|
+
overlay.style.top = `${Math.round(top)}px`;
|
|
1796
|
+
overlay.style.left = `${Math.round(left)}px`;
|
|
1797
|
+
overlay.style.right = 'auto';
|
|
1798
|
+
overlay.style.bottom = 'auto';
|
|
1799
|
+
overlay.style.visibility = 'visible';
|
|
1800
|
+
// NOTE: showPopover() is intentionally NOT called here. It already
|
|
1801
|
+
// happened once in renderHost(); calling it again on every reposition
|
|
1802
|
+
// (scroll/resize) throws InvalidStateError since the popover is
|
|
1803
|
+
// already open.
|
|
1804
|
+
}
|
|
1805
|
+
getAnchorRect(instance) {
|
|
1806
|
+
if (instance.point) {
|
|
1807
|
+
return new DOMRect(instance.point.x, instance.point.y, 0, 0);
|
|
1808
|
+
}
|
|
1809
|
+
if (instance.anchor && instance.anchor.isConnected) {
|
|
1810
|
+
return instance.anchor.getBoundingClientRect();
|
|
1811
|
+
}
|
|
1812
|
+
return new DOMRect(window.innerWidth / 2, window.innerHeight / 2, 0, 0);
|
|
1813
|
+
}
|
|
1814
|
+
resolvePlacementConfig(placement, alignment, margin) {
|
|
1815
|
+
return {
|
|
1816
|
+
placement: placement ?? 'auto',
|
|
1817
|
+
alignment: alignment ?? 'start',
|
|
1818
|
+
margin: margin ?? 8,
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
// ---------------------------------------------------------------------------
|
|
1822
|
+
// utilities
|
|
1823
|
+
// ---------------------------------------------------------------------------
|
|
1824
|
+
getLastInstance() {
|
|
1825
|
+
return this.overlayStack[this.overlayStack.length - 1];
|
|
1826
|
+
}
|
|
1827
|
+
requestAnimationFrame(callback) {
|
|
1828
|
+
if (typeof window.requestAnimationFrame === 'function') {
|
|
1829
|
+
return window.requestAnimationFrame(callback);
|
|
1259
1830
|
}
|
|
1260
|
-
|
|
1261
|
-
|
|
1831
|
+
return window.setTimeout(() => callback(Date.now()), 16);
|
|
1832
|
+
}
|
|
1833
|
+
cancelAnimationFrame(id) {
|
|
1834
|
+
if (typeof window.cancelAnimationFrame === 'function') {
|
|
1835
|
+
window.cancelAnimationFrame(id);
|
|
1262
1836
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1837
|
+
else {
|
|
1838
|
+
clearTimeout(id);
|
|
1265
1839
|
}
|
|
1266
|
-
dialog.className = `${DIALOG_OVERLAY_CLASSNAME} tips-${verticalPos}`;
|
|
1267
|
-
dialog.style.top = `${top + window.scrollY}px`;
|
|
1268
|
-
dialog.style.left = left !== 'auto' ? `${left + window.scrollX}px` : 'auto';
|
|
1269
|
-
dialog.style.right = right !== 'auto' ? `${right + window.scrollX}px` : 'auto';
|
|
1270
|
-
dialog.style.transformOrigin = verticalPos == 'above' ? 'left top' : 'left bottom';
|
|
1271
|
-
// dialog.style.transform = 'none';
|
|
1272
|
-
dialog.style.visibility = 'visible';
|
|
1273
|
-
dialog.style.animation = 'menu-enter 120ms cubic-bezier(0, 0, 0.2, 1)';
|
|
1274
1840
|
}
|
|
1275
1841
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: OverlayService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1276
1842
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: OverlayService, providedIn: 'root' });
|
|
1277
1843
|
}
|
|
1278
1844
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: OverlayService, decorators: [{
|
|
1279
1845
|
type: Injectable,
|
|
1280
|
-
args: [{
|
|
1846
|
+
args: [{
|
|
1847
|
+
providedIn: 'root',
|
|
1848
|
+
}]
|
|
1281
1849
|
}] });
|
|
1282
1850
|
|
|
1283
1851
|
/**
|
|
1284
1852
|
* Generated bundle index. Do not edit.
|
|
1285
1853
|
*/
|
|
1286
1854
|
|
|
1287
|
-
export { BrowserService, DIALOG_OVERLAY_CLASSNAME, DirectionService, FOCUSABLE_SELECTOR, OverlayRef, OverlayService, RangeSliderComponent, SaturationComponent, SliderComponent, ValueModel, WINDOW, filterTreeList, generateSequentialGuid, getOffsetPosition, makeConfetti,
|
|
1288
|
-
//# sourceMappingURL=ngx-kit-
|
|
1855
|
+
export { BrowserService, CSS_COLOR_NAMES, DIALOG_OVERLAY_CLASSNAME, DirectionService, FOCUSABLE_SELECTOR, OVERLAY_HOST_CLASSNAME, OverlayRef, OverlayService, RangeSliderComponent, SaturationComponent, SliderComponent, ValueModel, WINDOW, extractCssColors, filterTreeList, generateSequentialGuid, getOffsetPosition, makeConfetti, mergeDeep, randomStrnig, startDragSession };
|
|
1856
|
+
//# sourceMappingURL=ngx-kit-core.mjs.map
|