@moatamed-sce/ui-components 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -0
- package/fesm2022/moatamed-sce-ui-components-maps.mjs +1326 -0
- package/fesm2022/moatamed-sce-ui-components-maps.mjs.map +1 -0
- package/fesm2022/moatamed-sce-ui-components.mjs +5409 -3181
- package/fesm2022/moatamed-sce-ui-components.mjs.map +1 -1
- package/package.json +8 -4
- package/types/moatamed-sce-ui-components-maps.d.ts +220 -0
- package/types/moatamed-sce-ui-components.d.ts +2007 -315
|
@@ -0,0 +1,1326 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, output, viewChild, afterNextRender, effect, ChangeDetectionStrategy, Component, inject, computed, ViewChild, ViewEncapsulation, DestroyRef, signal } from '@angular/core';
|
|
3
|
+
import * as i1 from '@moatamed-sce/core';
|
|
4
|
+
import { fixLeafletDefaultIcon, scheduleLeafletResize, SceVariantDirective, MAP_CONFIG, LanguageService, createLeafletDefaultIcon, isGeoLookupRestricted, JEDDAH_MAP_CENTER, NominatimService, GeoMapPositionValidationService, ToastService, mapPositionValidationMessage, resolveInitialCenter, SCE_MIRROR_LABEL_AS_PLACEHOLDER, DIALOG_SELECT_OVERLAY_Z_INDEX, inferInputTextDirection, BaladyGisService, BaladyGisError } from '@moatamed-sce/core';
|
|
5
|
+
import { TranslateService, TranslatePipe } from '@ngx-translate/core';
|
|
6
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
7
|
+
import * as i2 from '@angular/forms';
|
|
8
|
+
import { FormsModule } from '@angular/forms';
|
|
9
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
10
|
+
import { CommonModule } from '@angular/common';
|
|
11
|
+
import { Select } from 'primeng/select';
|
|
12
|
+
import { HugeiconsIconComponent } from '@hugeicons/angular';
|
|
13
|
+
import { ArrowDown01Icon, AlertCircleIcon, RefreshIcon } from '@hugeicons/core-free-icons';
|
|
14
|
+
|
|
15
|
+
/** OpenStreetMap Leaflet map that lazily loads Leaflet, renders the given markers and emits map clicks and marker drag-ends; setting `interactive` to false disables panning, zooming, the zoom control and click events. */
|
|
16
|
+
class SceOsmLeafletMapComponent {
|
|
17
|
+
latitude = input(24.7136, ...(ngDevMode ? [{ debugName: "latitude" }] : /* istanbul ignore next */ []));
|
|
18
|
+
longitude = input(46.6753, ...(ngDevMode ? [{ debugName: "longitude" }] : /* istanbul ignore next */ []));
|
|
19
|
+
zoom = input(6, ...(ngDevMode ? [{ debugName: "zoom" }] : /* istanbul ignore next */ []));
|
|
20
|
+
markers = input([], ...(ngDevMode ? [{ debugName: "markers" }] : /* istanbul ignore next */ []));
|
|
21
|
+
interactive = input(true, ...(ngDevMode ? [{ debugName: "interactive" }] : /* istanbul ignore next */ []));
|
|
22
|
+
mapClick = output();
|
|
23
|
+
markerDragEnd = output();
|
|
24
|
+
mapEl = viewChild('mapEl', ...(ngDevMode ? [{ debugName: "mapEl" }] : /* istanbul ignore next */ []));
|
|
25
|
+
map = null;
|
|
26
|
+
markerLayer = null;
|
|
27
|
+
L = null;
|
|
28
|
+
cancelLeafletResize = null;
|
|
29
|
+
async initMap() {
|
|
30
|
+
const el = this.mapEl()?.nativeElement;
|
|
31
|
+
if (!el) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const leafletModule = await import('leaflet');
|
|
35
|
+
// CJS/ESM interop: in the esbuild bundle the Leaflet API lands on .default, in dev it may not.
|
|
36
|
+
const L = (leafletModule.default ??
|
|
37
|
+
leafletModule);
|
|
38
|
+
this.L = L;
|
|
39
|
+
fixLeafletDefaultIcon(L);
|
|
40
|
+
this.map = L.map(el, {
|
|
41
|
+
center: [this.latitude(), this.longitude()],
|
|
42
|
+
zoom: this.zoom(),
|
|
43
|
+
zoomControl: false,
|
|
44
|
+
dragging: this.interactive(),
|
|
45
|
+
scrollWheelZoom: false,
|
|
46
|
+
doubleClickZoom: this.interactive(),
|
|
47
|
+
touchZoom: this.interactive(),
|
|
48
|
+
});
|
|
49
|
+
if (this.interactive()) {
|
|
50
|
+
const isRtl = getComputedStyle(el).direction === 'rtl';
|
|
51
|
+
L.control.zoom({ position: isRtl ? 'bottomright' : 'bottomleft' }).addTo(this.map);
|
|
52
|
+
}
|
|
53
|
+
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
54
|
+
maxZoom: 19,
|
|
55
|
+
}).addTo(this.map);
|
|
56
|
+
this.markerLayer = L.layerGroup().addTo(this.map);
|
|
57
|
+
if (this.interactive()) {
|
|
58
|
+
this.map.on('click', (e) => {
|
|
59
|
+
this.mapClick.emit({ lat: e.latlng.lat, lng: e.latlng.lng });
|
|
60
|
+
});
|
|
61
|
+
el.addEventListener('wheel', (e) => {
|
|
62
|
+
if (!this.map || !(e.ctrlKey || e.metaKey)) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
if (e.deltaY < 0) {
|
|
67
|
+
this.map.zoomIn();
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
this.map.zoomOut();
|
|
71
|
+
}
|
|
72
|
+
}, { passive: false });
|
|
73
|
+
}
|
|
74
|
+
this.cancelLeafletResize?.();
|
|
75
|
+
this.cancelLeafletResize = scheduleLeafletResize(this.map);
|
|
76
|
+
this.renderMarkers(this.markers());
|
|
77
|
+
}
|
|
78
|
+
renderMarkers(markers) {
|
|
79
|
+
if (!this.markerLayer || !this.L) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this.markerLayer.clearLayers();
|
|
83
|
+
for (let i = 0; i < markers.length; i++) {
|
|
84
|
+
const m = markers[i];
|
|
85
|
+
const marker = this.L.marker([m.lat, m.lng], {
|
|
86
|
+
draggable: m.draggable ?? false,
|
|
87
|
+
});
|
|
88
|
+
if (m.popupHtml) {
|
|
89
|
+
marker.bindPopup(m.popupHtml);
|
|
90
|
+
}
|
|
91
|
+
if (m.draggable) {
|
|
92
|
+
const index = i;
|
|
93
|
+
marker.on('dragend', () => {
|
|
94
|
+
const pos = marker.getLatLng();
|
|
95
|
+
this.markerDragEnd.emit({ lat: pos.lat, lng: pos.lng, index });
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
marker.addTo(this.markerLayer);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
ngOnDestroy() {
|
|
102
|
+
this.cancelLeafletResize?.();
|
|
103
|
+
this.cancelLeafletResize = null;
|
|
104
|
+
this.map?.remove();
|
|
105
|
+
this.map = null;
|
|
106
|
+
}
|
|
107
|
+
bindHost = (() => {
|
|
108
|
+
afterNextRender(() => {
|
|
109
|
+
this.initMap();
|
|
110
|
+
});
|
|
111
|
+
effect(() => {
|
|
112
|
+
const mkrs = this.markers();
|
|
113
|
+
this.renderMarkers(mkrs);
|
|
114
|
+
});
|
|
115
|
+
effect(() => {
|
|
116
|
+
const lat = this.latitude();
|
|
117
|
+
const lng = this.longitude();
|
|
118
|
+
const zm = this.zoom();
|
|
119
|
+
if (this.map) {
|
|
120
|
+
this.map.setView([lat, lng], zm);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
})();
|
|
124
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceOsmLeafletMapComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
125
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.17", type: SceOsmLeafletMapComponent, isStandalone: true, selector: "sce-osm-leaflet-map", inputs: { latitude: { classPropertyName: "latitude", publicName: "latitude", isSignal: true, isRequired: false, transformFunction: null }, longitude: { classPropertyName: "longitude", publicName: "longitude", isSignal: true, isRequired: false, transformFunction: null }, zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null }, markers: { classPropertyName: "markers", publicName: "markers", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { mapClick: "mapClick", markerDragEnd: "markerDragEnd" }, host: { classAttribute: "sce-osm-leaflet-map" }, viewQueries: [{ propertyName: "mapEl", first: true, predicate: ["mapEl"], descendants: true, isSignal: true }], hostDirectives: [{ directive: i1.SceVariantDirective, inputs: ["sceVariant", "sceVariant", "sceDensity", "sceDensity"] }], ngImport: i0, template: "<div #mapEl class=\"mapRoot\"></div>\n", styles: ["@charset \"UTF-8\";:host.sce-osm-leaflet-map{display:block}.mapRoot{width:100%;height:100%;min-height:0;background:var(--color-neutral-200)}.mapRoot.mapRoot.leaflet-container{width:100%;height:100%;font-family:inherit}.mapRoot.mapRoot img.leaflet-tile,.mapRoot.mapRoot img.leaflet-marker-icon,.mapRoot.mapRoot img.leaflet-marker-shadow{max-width:none;max-height:none}.mapRoot.mapRoot .leaflet-control-attribution,.mapRoot.mapRoot .leaflet-control-container.leaflet-bottom{display:none}.mapRoot .leaflet-pane,.mapRoot .leaflet-map-pane,.mapRoot .leaflet-tile-pane{width:100%;height:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
126
|
+
}
|
|
127
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceOsmLeafletMapComponent, decorators: [{
|
|
128
|
+
type: Component,
|
|
129
|
+
args: [{ hostDirectives: [{ directive: SceVariantDirective, inputs: ['sceVariant', 'sceDensity'] }], host: { class: 'sce-osm-leaflet-map' }, selector: 'sce-osm-leaflet-map', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div #mapEl class=\"mapRoot\"></div>\n", styles: ["@charset \"UTF-8\";:host.sce-osm-leaflet-map{display:block}.mapRoot{width:100%;height:100%;min-height:0;background:var(--color-neutral-200)}.mapRoot.mapRoot.leaflet-container{width:100%;height:100%;font-family:inherit}.mapRoot.mapRoot img.leaflet-tile,.mapRoot.mapRoot img.leaflet-marker-icon,.mapRoot.mapRoot img.leaflet-marker-shadow{max-width:none;max-height:none}.mapRoot.mapRoot .leaflet-control-attribution,.mapRoot.mapRoot .leaflet-control-container.leaflet-bottom{display:none}.mapRoot .leaflet-pane,.mapRoot .leaflet-map-pane,.mapRoot .leaflet-tile-pane{width:100%;height:100%}\n"] }]
|
|
130
|
+
}], propDecorators: { latitude: [{ type: i0.Input, args: [{ isSignal: true, alias: "latitude", required: false }] }], longitude: [{ type: i0.Input, args: [{ isSignal: true, alias: "longitude", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }], markers: [{ type: i0.Input, args: [{ isSignal: true, alias: "markers", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], mapClick: [{ type: i0.Output, args: ["mapClick"] }], markerDragEnd: [{ type: i0.Output, args: ["markerDragEnd"] }], mapEl: [{ type: i0.ViewChild, args: ['mapEl', { isSignal: true }] }] } });
|
|
131
|
+
|
|
132
|
+
const TILE_URL$1 = MAP_CONFIG.tileUrl;
|
|
133
|
+
const DEFAULT_CENTER = [24.7136, 46.6753];
|
|
134
|
+
const DEFAULT_ZOOM = 6;
|
|
135
|
+
const SINGLE_OFFICE_ZOOM = 12;
|
|
136
|
+
function isPlottableOffice(office) {
|
|
137
|
+
return (Number.isFinite(office.latitude) &&
|
|
138
|
+
Number.isFinite(office.longitude) &&
|
|
139
|
+
Math.abs(office.latitude) <= 90 &&
|
|
140
|
+
Math.abs(office.longitude) <= 180);
|
|
141
|
+
}
|
|
142
|
+
function markerTooltipLabel(office, translate) {
|
|
143
|
+
if (!office.approximate) {
|
|
144
|
+
return office.name;
|
|
145
|
+
}
|
|
146
|
+
const suffix = translate?.instant('map.approximateLocation') ?? 'موقع تقريبي (مركز المدينة)';
|
|
147
|
+
return `${office.name}\n${suffix}`;
|
|
148
|
+
}
|
|
149
|
+
/** React: google-maps/osm-offices-map.tsx */
|
|
150
|
+
class SceOsmOfficesMapComponent {
|
|
151
|
+
language = inject(LanguageService);
|
|
152
|
+
mapRoot;
|
|
153
|
+
translate = inject(TranslateService);
|
|
154
|
+
offices = input([], ...(ngDevMode ? [{ debugName: "offices" }] : /* istanbul ignore next */ []));
|
|
155
|
+
selectedOfficeId = input(...(ngDevMode ? [undefined, { debugName: "selectedOfficeId" }] : /* istanbul ignore next */ []));
|
|
156
|
+
showTooltip = input(true, ...(ngDevMode ? [{ debugName: "showTooltip" }] : /* istanbul ignore next */ []));
|
|
157
|
+
ariaLabelInput = input('', ...(ngDevMode ? [{ debugName: "ariaLabelInput" }] : /* istanbul ignore next */ []));
|
|
158
|
+
ariaLabel = computed(() => {
|
|
159
|
+
// Re-resolve the default label when the active locale changes.
|
|
160
|
+
this.language.translationsRevision();
|
|
161
|
+
return this.ariaLabelInput() || this.translate.instant('map.officesMap');
|
|
162
|
+
}, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
163
|
+
selectOffice = output();
|
|
164
|
+
ready = output();
|
|
165
|
+
mapError = output();
|
|
166
|
+
map = null;
|
|
167
|
+
markers = [];
|
|
168
|
+
markerByOfficeId = new Map();
|
|
169
|
+
mapReady = false;
|
|
170
|
+
disposed = false;
|
|
171
|
+
icon = null;
|
|
172
|
+
cancelLeafletResize = null;
|
|
173
|
+
ngAfterViewInit() {
|
|
174
|
+
void this.bootMap();
|
|
175
|
+
}
|
|
176
|
+
ngOnChanges(changes) {
|
|
177
|
+
if (changes['offices'] && this.mapReady) {
|
|
178
|
+
void this.syncMarkers();
|
|
179
|
+
}
|
|
180
|
+
if (changes['selectedOfficeId'] && this.mapReady && this.selectedOfficeId() != null) {
|
|
181
|
+
this.focusSelectedOffice(this.selectedOfficeId());
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
ngOnDestroy() {
|
|
185
|
+
this.disposed = true;
|
|
186
|
+
this.mapReady = false;
|
|
187
|
+
this.cancelLeafletResize?.();
|
|
188
|
+
this.cancelLeafletResize = null;
|
|
189
|
+
this.clearMarkers();
|
|
190
|
+
this.map?.remove();
|
|
191
|
+
this.map = null;
|
|
192
|
+
const el = this.mapRoot?.nativeElement;
|
|
193
|
+
if (el) {
|
|
194
|
+
delete el._leaflet_id;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async bootMap() {
|
|
198
|
+
const container = this.mapRoot?.nativeElement;
|
|
199
|
+
if (!container) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const leafletModule = await import('leaflet');
|
|
204
|
+
// CJS/ESM interop: in the esbuild bundle the Leaflet API lands on .default, in dev it may not.
|
|
205
|
+
const L = (leafletModule.default ??
|
|
206
|
+
leafletModule);
|
|
207
|
+
if (this.disposed) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
this.icon = createLeafletDefaultIcon(L);
|
|
211
|
+
const map = L.map(container, {
|
|
212
|
+
center: DEFAULT_CENTER,
|
|
213
|
+
zoom: DEFAULT_ZOOM,
|
|
214
|
+
scrollWheelZoom: true,
|
|
215
|
+
preferCanvas: false,
|
|
216
|
+
attributionControl: false,
|
|
217
|
+
});
|
|
218
|
+
L.tileLayer(TILE_URL$1, { attribution: '', maxZoom: MAP_CONFIG.maxZoom }).addTo(map);
|
|
219
|
+
this.map = map;
|
|
220
|
+
this.queueLeafletResize(map);
|
|
221
|
+
window.setTimeout(() => {
|
|
222
|
+
if (!this.disposed) {
|
|
223
|
+
this.queueLeafletResize(map);
|
|
224
|
+
}
|
|
225
|
+
}, 400);
|
|
226
|
+
if (!this.disposed) {
|
|
227
|
+
this.mapReady = true;
|
|
228
|
+
this.ready.emit();
|
|
229
|
+
await this.syncMarkers();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
if (!this.disposed) {
|
|
234
|
+
this.mapError.emit();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async syncMarkers() {
|
|
239
|
+
const map = this.map;
|
|
240
|
+
if (!map) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const leafletModule = await import('leaflet');
|
|
244
|
+
// CJS/ESM interop: in the esbuild bundle the Leaflet API lands on .default, in dev it may not.
|
|
245
|
+
const L = (leafletModule.default ??
|
|
246
|
+
leafletModule);
|
|
247
|
+
this.clearMarkers();
|
|
248
|
+
const plottable = this.offices().filter(isPlottableOffice);
|
|
249
|
+
if (plottable.length === 0) {
|
|
250
|
+
map.setView(DEFAULT_CENTER, DEFAULT_ZOOM);
|
|
251
|
+
this.queueLeafletResize(map);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (!this.icon) {
|
|
255
|
+
this.icon = createLeafletDefaultIcon(L);
|
|
256
|
+
}
|
|
257
|
+
const bounds = L.latLngBounds([]);
|
|
258
|
+
for (const office of plottable) {
|
|
259
|
+
const marker = L.marker([office.latitude, office.longitude], {
|
|
260
|
+
icon: this.icon || undefined,
|
|
261
|
+
}).addTo(map);
|
|
262
|
+
if (this.showTooltip()) {
|
|
263
|
+
marker.bindTooltip(markerTooltipLabel(office, this.translate), {
|
|
264
|
+
direction: 'top',
|
|
265
|
+
opacity: 0.95,
|
|
266
|
+
sticky: true,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
marker.on('click', () => this.selectOffice.emit(office.id));
|
|
270
|
+
this.markers.push(marker);
|
|
271
|
+
this.markerByOfficeId.set(office.id, marker);
|
|
272
|
+
bounds.extend([office.latitude, office.longitude]);
|
|
273
|
+
}
|
|
274
|
+
const focusMarker = () => {
|
|
275
|
+
if (!this.map || this.disposed) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (plottable.length > 1) {
|
|
279
|
+
map.fitBounds(bounds, { padding: [48, 48], maxZoom: 12 });
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
map.setView([plottable[0].latitude, plottable[0].longitude], SINGLE_OFFICE_ZOOM, {
|
|
283
|
+
animate: false,
|
|
284
|
+
});
|
|
285
|
+
if (this.showTooltip()) {
|
|
286
|
+
const marker = this.markerByOfficeId.get(plottable[0].id);
|
|
287
|
+
marker?.openTooltip();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
this.queueLeafletResize(map);
|
|
291
|
+
};
|
|
292
|
+
focusMarker();
|
|
293
|
+
window.setTimeout(() => {
|
|
294
|
+
if (!this.disposed) {
|
|
295
|
+
focusMarker();
|
|
296
|
+
}
|
|
297
|
+
}, 120);
|
|
298
|
+
window.setTimeout(() => {
|
|
299
|
+
if (!this.disposed) {
|
|
300
|
+
focusMarker();
|
|
301
|
+
}
|
|
302
|
+
}, 450);
|
|
303
|
+
}
|
|
304
|
+
queueLeafletResize(map) {
|
|
305
|
+
this.cancelLeafletResize?.();
|
|
306
|
+
this.cancelLeafletResize = scheduleLeafletResize(map);
|
|
307
|
+
}
|
|
308
|
+
focusSelectedOffice(officeId) {
|
|
309
|
+
const map = this.map;
|
|
310
|
+
if (!map) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const marker = this.markerByOfficeId.get(officeId);
|
|
314
|
+
if (!marker) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const latLng = marker.getLatLng();
|
|
318
|
+
map.setView(latLng, Math.max(map.getZoom(), SINGLE_OFFICE_ZOOM), { animate: true });
|
|
319
|
+
if (this.showTooltip()) {
|
|
320
|
+
marker.openTooltip();
|
|
321
|
+
}
|
|
322
|
+
this.queueLeafletResize(map);
|
|
323
|
+
}
|
|
324
|
+
clearMarkers() {
|
|
325
|
+
this.markers.forEach(marker => marker.remove());
|
|
326
|
+
this.markers = [];
|
|
327
|
+
this.markerByOfficeId.clear();
|
|
328
|
+
}
|
|
329
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceOsmOfficesMapComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
330
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: SceOsmOfficesMapComponent, isStandalone: true, selector: "sce-osm-offices-map", inputs: { offices: { classPropertyName: "offices", publicName: "offices", isSignal: true, isRequired: false, transformFunction: null }, selectedOfficeId: { classPropertyName: "selectedOfficeId", publicName: "selectedOfficeId", isSignal: true, isRequired: false, transformFunction: null }, showTooltip: { classPropertyName: "showTooltip", publicName: "showTooltip", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelInput: { classPropertyName: "ariaLabelInput", publicName: "ariaLabelInput", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectOffice: "selectOffice", ready: "ready", mapError: "mapError" }, host: { classAttribute: "sce-osm-offices-map" }, viewQueries: [{ propertyName: "mapRoot", first: true, predicate: ["mapRoot"], descendants: true }], usesOnChanges: true, hostDirectives: [{ directive: i1.SceVariantDirective, inputs: ["sceVariant", "sceVariant", "sceDensity", "sceDensity"] }], ngImport: i0, template: "<div #mapRoot class=\"mapRoot\" role=\"presentation\" [attr.aria-label]=\"ariaLabel()\"></div>\n", styles: ["@charset \"UTF-8\";.mapRoot{width:100%;height:100%;min-height:var(--size-600);z-index:0}.mapRoot.leaflet-container{width:100%;height:100%;min-height:var(--size-600);font-family:var(--font-family-arabic-primary),Arial,sans-serif}.mapRoot.mapRoot img.leaflet-tile,.mapRoot.mapRoot img.leaflet-marker-icon,.mapRoot.mapRoot img.leaflet-marker-shadow{max-width:none;max-height:none}.mapRoot.mapRoot .leaflet-control-attribution,.mapRoot.mapRoot .leaflet-control-container.leaflet-bottom{display:none}.mapRoot .leaflet-pane,.mapRoot .leaflet-map-pane,.mapRoot .leaflet-tile-pane{width:100%;height:100%}.popup{font-size:var(--font-size-md);font-weight:600;color:var(--text-text-primary-900);padding:var(--space-2) 0}.mapRoot .sce-map-popup{font-size:var(--font-size-md);font-weight:600;color:var(--text-text-primary-900);padding:var(--space-2) 0;font-family:var(--font-family-arabic-primary),Arial,sans-serif}.mapRoot .sce-map-popup-approx{margin-block-start:var(--space-4);font-size:var(--font-size-xs);font-weight:500;color:var(--text-text-secondary-700)}.mapRoot .leaflet-marker-pane{z-index:650}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
331
|
+
}
|
|
332
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceOsmOfficesMapComponent, decorators: [{
|
|
333
|
+
type: Component,
|
|
334
|
+
args: [{ hostDirectives: [{ directive: SceVariantDirective, inputs: ['sceVariant', 'sceDensity'] }], host: { class: 'sce-osm-offices-map' }, selector: 'sce-osm-offices-map', standalone: true, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div #mapRoot class=\"mapRoot\" role=\"presentation\" [attr.aria-label]=\"ariaLabel()\"></div>\n", styles: ["@charset \"UTF-8\";.mapRoot{width:100%;height:100%;min-height:var(--size-600);z-index:0}.mapRoot.leaflet-container{width:100%;height:100%;min-height:var(--size-600);font-family:var(--font-family-arabic-primary),Arial,sans-serif}.mapRoot.mapRoot img.leaflet-tile,.mapRoot.mapRoot img.leaflet-marker-icon,.mapRoot.mapRoot img.leaflet-marker-shadow{max-width:none;max-height:none}.mapRoot.mapRoot .leaflet-control-attribution,.mapRoot.mapRoot .leaflet-control-container.leaflet-bottom{display:none}.mapRoot .leaflet-pane,.mapRoot .leaflet-map-pane,.mapRoot .leaflet-tile-pane{width:100%;height:100%}.popup{font-size:var(--font-size-md);font-weight:600;color:var(--text-text-primary-900);padding:var(--space-2) 0}.mapRoot .sce-map-popup{font-size:var(--font-size-md);font-weight:600;color:var(--text-text-primary-900);padding:var(--space-2) 0;font-family:var(--font-family-arabic-primary),Arial,sans-serif}.mapRoot .sce-map-popup-approx{margin-block-start:var(--space-4);font-size:var(--font-size-xs);font-weight:500;color:var(--text-text-secondary-700)}.mapRoot .leaflet-marker-pane{z-index:650}\n"] }]
|
|
335
|
+
}], propDecorators: { mapRoot: [{
|
|
336
|
+
type: ViewChild,
|
|
337
|
+
args: ['mapRoot']
|
|
338
|
+
}], offices: [{ type: i0.Input, args: [{ isSignal: true, alias: "offices", required: false }] }], selectedOfficeId: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedOfficeId", required: false }] }], showTooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTooltip", required: false }] }], ariaLabelInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelInput", required: false }] }], selectOffice: [{ type: i0.Output, args: ["selectOffice"] }], ready: [{ type: i0.Output, args: ["ready"] }], mapError: [{ type: i0.Output, args: ["mapError"] }] } });
|
|
339
|
+
|
|
340
|
+
/** Loading placeholder in one of several shape variants, optionally repeated `count` times with a shortened last line. */
|
|
341
|
+
class SceSkeleton {
|
|
342
|
+
/** Shape variant of the skeleton */
|
|
343
|
+
variant = input('text', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
344
|
+
/** Animation style */
|
|
345
|
+
animation = input('wave', ...(ngDevMode ? [{ debugName: "animation" }] : /* istanbul ignore next */ []));
|
|
346
|
+
/** Width -- accepts any CSS value (px, %, rem, etc.). Defaults to 100% for most variants. */
|
|
347
|
+
width = input('', ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
|
|
348
|
+
/** Height -- accepts any CSS value. Auto-calculated for text variant if not set. */
|
|
349
|
+
height = input('', ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
|
|
350
|
+
/** Border radius -- override the variant's default radius */
|
|
351
|
+
borderRadius = input('', ...(ngDevMode ? [{ debugName: "borderRadius" }] : /* istanbul ignore next */ []));
|
|
352
|
+
/** Number of skeleton lines to render (useful for paragraph placeholders) */
|
|
353
|
+
count = input(1, ...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
|
|
354
|
+
/** Gap between repeated lines */
|
|
355
|
+
gap = input('12px', ...(ngDevMode ? [{ debugName: "gap" }] : /* istanbul ignore next */ []));
|
|
356
|
+
/** Whether the last line in a multi-line skeleton should be shorter */
|
|
357
|
+
shrinkLastLine = input(true, ...(ngDevMode ? [{ debugName: "shrinkLastLine" }] : /* istanbul ignore next */ []));
|
|
358
|
+
/** Custom aspect ratio (e.g. '16/9', '1/1') -- sets height automatically via CSS */
|
|
359
|
+
aspectRatio = input('', ...(ngDevMode ? [{ debugName: "aspectRatio" }] : /* istanbul ignore next */ []));
|
|
360
|
+
/** Whether this is a multi-line skeleton */
|
|
361
|
+
isMultiLine = computed(() => this.count() > 1, ...(ngDevMode ? [{ debugName: "isMultiLine" }] : /* istanbul ignore next */ []));
|
|
362
|
+
/** Lines derived from count, each carrying whether it is the shrinking last line */
|
|
363
|
+
lines = computed(() => {
|
|
364
|
+
const count = this.count();
|
|
365
|
+
const shrinksLast = this.shrinkLastLine() && this.isMultiLine();
|
|
366
|
+
return Array.from({ length: count }, (_, index) => ({
|
|
367
|
+
index,
|
|
368
|
+
isLast: shrinksLast && index === count - 1,
|
|
369
|
+
}));
|
|
370
|
+
}, ...(ngDevMode ? [{ debugName: "lines" }] : /* istanbul ignore next */ []));
|
|
371
|
+
/** Computed inline styles for the skeleton element */
|
|
372
|
+
skeletonStyles = computed(() => {
|
|
373
|
+
const styles = {};
|
|
374
|
+
const w = this.width();
|
|
375
|
+
const h = this.height();
|
|
376
|
+
const br = this.borderRadius();
|
|
377
|
+
const ar = this.aspectRatio();
|
|
378
|
+
if (w) {
|
|
379
|
+
styles['width'] = w;
|
|
380
|
+
}
|
|
381
|
+
if (h) {
|
|
382
|
+
styles['height'] = h;
|
|
383
|
+
}
|
|
384
|
+
if (br) {
|
|
385
|
+
styles['border-radius'] = br;
|
|
386
|
+
}
|
|
387
|
+
if (ar) {
|
|
388
|
+
styles['aspect-ratio'] = ar;
|
|
389
|
+
// Variants like `image`/`card` set a fixed height which would defeat the
|
|
390
|
+
// ratio — release it so the ratio drives the height unless one is given.
|
|
391
|
+
if (!h) {
|
|
392
|
+
styles['height'] = 'auto';
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return styles;
|
|
396
|
+
}, ...(ngDevMode ? [{ debugName: "skeletonStyles" }] : /* istanbul ignore next */ []));
|
|
397
|
+
/** Computed container styles (gap for multi-line) */
|
|
398
|
+
containerStyles = computed(() => {
|
|
399
|
+
const styles = {};
|
|
400
|
+
if (this.isMultiLine()) {
|
|
401
|
+
styles['gap'] = this.gap();
|
|
402
|
+
}
|
|
403
|
+
return styles;
|
|
404
|
+
}, ...(ngDevMode ? [{ debugName: "containerStyles" }] : /* istanbul ignore next */ []));
|
|
405
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceSkeleton, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
406
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: SceSkeleton, isStandalone: true, selector: "sce-skeleton", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, animation: { classPropertyName: "animation", publicName: "animation", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, borderRadius: { classPropertyName: "borderRadius", publicName: "borderRadius", isSignal: true, isRequired: false, transformFunction: null }, count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, shrinkLastLine: { classPropertyName: "shrinkLastLine", publicName: "shrinkLastLine", isSignal: true, isRequired: false, transformFunction: null }, aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "sce-skeleton" }, hostDirectives: [{ directive: i1.SceVariantDirective, inputs: ["sceVariant", "sceVariant", "sceDensity", "sceDensity"] }], ngImport: i0, template: "<div\n class=\"skeleton-container\"\n [class.skeleton-container--multi]=\"isMultiLine()\"\n [style]=\"containerStyles()\"\n role=\"status\"\n [attr.aria-label]=\"'ARIA.LOADING_CONTENT' | translate\"\n>\n @for (line of lines(); track line.index) {\n <span\n class=\"skeleton\"\n [class.skeleton--text]=\"variant() === 'text'\"\n [class.skeleton--circular]=\"variant() === 'circular'\"\n [class.skeleton--rectangular]=\"variant() === 'rectangular'\"\n [class.skeleton--rounded]=\"variant() === 'rounded'\"\n [class.skeleton--avatar]=\"variant() === 'avatar'\"\n [class.skeleton--button]=\"variant() === 'button'\"\n [class.skeleton--chip]=\"variant() === 'chip'\"\n [class.skeleton--card]=\"variant() === 'card'\"\n [class.skeleton--image]=\"variant() === 'image'\"\n [class.skeleton--header]=\"variant() === 'header'\"\n [class.skeleton--paragraph]=\"variant() === 'paragraph'\"\n [class.skeleton--divider]=\"variant() === 'divider'\"\n [class.skeleton--badge]=\"variant() === 'badge'\"\n [class.skeleton--input]=\"variant() === 'input'\"\n [class.skeleton--pulse]=\"animation() === 'pulse'\"\n [class.skeleton--wave]=\"animation() === 'wave'\"\n [class.skeleton--no-animation]=\"animation() === 'none'\"\n [class.skeleton--last]=\"line.isLast\"\n [style]=\"skeletonStyles()\"\n aria-hidden=\"true\"\n ></span>\n }\n <span class=\"sr-only\">{{ 'COMMON.LOADING' | translate }}</span>\n</div>\n", styles: [":host{display:block;--skeleton-bg: rgba(31, 42, 55, .08);--skeleton-wave-color-start: transparent;--skeleton-wave-color-mid-light: rgba(255, 255, 255, .35);--skeleton-wave-color-mid: rgba(255, 255, 255, .6);--skeleton-wave-color-mid-light-end: rgba(255, 255, 255, .35);--skeleton-wave-color-end: transparent;--skeleton-pulse-duration: 1.8s;--skeleton-pulse-opacity-min: .4;--skeleton-wave-duration: 2s;--skeleton-radius-xs: var(--radius-xs);--skeleton-radius-sm: var(--radius-sm);--skeleton-radius-md: var(--radius-sm);--skeleton-radius-lg: var(--radius-md);--skeleton-radius-full: 9999px;--skeleton-radius-circle: 50%;--skeleton-text-height: 1em;--skeleton-text-scale-y: .7;--skeleton-circular-size: 48px;--skeleton-rect-height: 120px;--skeleton-avatar-size: 40px;--skeleton-button-width: 120px;--skeleton-button-height: var(--space-40);--skeleton-chip-width: var(--space-72);--skeleton-chip-height: var(--space-28);--skeleton-card-height: 200px;--skeleton-image-height: 180px;--skeleton-header-width: 60%;--skeleton-header-height: 1.5em;--skeleton-paragraph-height: .9em;--skeleton-paragraph-scale-y: .8;--skeleton-divider-height: 1px;--skeleton-badge-width: var(--space-48);--skeleton-badge-height: var(--space-20);--skeleton-input-height: 44px;--skeleton-last-line-width: 65%}:host-context(html[data-theme=dark]){--skeleton-bg: rgba(255, 255, 255, .1);--skeleton-wave-color-mid-light: rgba(255, 255, 255, .06);--skeleton-wave-color-mid: rgba(255, 255, 255, .12);--skeleton-wave-color-mid-light-end: rgba(255, 255, 255, .06)}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.skeleton-container{display:contents}.skeleton-container--multi{display:flex;flex-direction:column}.skeleton{display:block;background-color:var(--skeleton-bg);overflow:hidden;position:relative}.skeleton--text{width:100%;height:var(--skeleton-text-height);border-radius:var(--skeleton-radius-xs);transform-origin:0 55%;transform:scaleY(var(--skeleton-text-scale-y))}.skeleton--circular{width:var(--skeleton-circular-size);height:var(--skeleton-circular-size);border-radius:var(--skeleton-radius-circle);flex-shrink:0}.skeleton--rectangular{width:100%;height:var(--skeleton-rect-height);border-radius:0}.skeleton--rounded{width:100%;height:var(--skeleton-rect-height);border-radius:var(--skeleton-radius-md)}.skeleton--avatar{width:var(--skeleton-avatar-size);height:var(--skeleton-avatar-size);border-radius:var(--skeleton-radius-circle);flex-shrink:0}.skeleton--button{width:var(--skeleton-button-width);height:var(--skeleton-button-height);border-radius:var(--skeleton-radius-sm);flex-shrink:0}.skeleton--chip{display:inline-block;width:var(--skeleton-chip-width);height:var(--skeleton-chip-height);border-radius:var(--skeleton-radius-full);flex-shrink:0}.skeleton--card{width:100%;height:var(--skeleton-card-height);border-radius:var(--skeleton-radius-lg)}.skeleton--image{width:100%;height:var(--skeleton-image-height);border-radius:var(--skeleton-radius-md)}.skeleton--header{width:var(--skeleton-header-width);height:var(--skeleton-header-height);border-radius:var(--skeleton-radius-xs)}.skeleton--paragraph{width:100%;height:var(--skeleton-paragraph-height);border-radius:var(--skeleton-radius-xs);transform-origin:0 55%;transform:scaleY(var(--skeleton-paragraph-scale-y))}.skeleton--divider{width:100%;height:var(--skeleton-divider-height);border-radius:0}.skeleton--badge{display:inline-block;width:var(--skeleton-badge-width);height:var(--skeleton-badge-height);border-radius:var(--skeleton-radius-xs);flex-shrink:0}.skeleton--input{width:100%;height:var(--skeleton-input-height);border-radius:var(--skeleton-radius-sm)}.skeleton--last{width:var(--skeleton-last-line-width)}.skeleton--pulse{animation:skeleton-pulse var(--skeleton-pulse-duration) ease-in-out infinite}.skeleton--wave:after{content:\"\";position:absolute;inset:0;transform:translate(-100%);background:linear-gradient(90deg,var(--skeleton-wave-color-start) 0%,var(--skeleton-wave-color-mid-light) 30%,var(--skeleton-wave-color-mid) 50%,var(--skeleton-wave-color-mid-light-end) 70%,var(--skeleton-wave-color-end) 100%);animation:skeleton-wave var(--skeleton-wave-duration) ease-in-out infinite}.skeleton--no-animation{animation:none}.skeleton--no-animation:after{display:none}@keyframes skeleton-pulse{0%{opacity:1}50%{opacity:var(--skeleton-pulse-opacity-min)}to{opacity:1}}@keyframes skeleton-wave{0%{transform:translate(-100%)}50%{transform:translate(100%)}to{transform:translate(100%)}}@media(prefers-reduced-motion:reduce){.skeleton--pulse{animation:none}.skeleton--wave:after{animation:none;display:none}}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
407
|
+
}
|
|
408
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceSkeleton, decorators: [{
|
|
409
|
+
type: Component,
|
|
410
|
+
args: [{ hostDirectives: [{ directive: SceVariantDirective, inputs: ['sceVariant', 'sceDensity'] }], host: { class: 'sce-skeleton' }, selector: 'sce-skeleton', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [TranslatePipe], template: "<div\n class=\"skeleton-container\"\n [class.skeleton-container--multi]=\"isMultiLine()\"\n [style]=\"containerStyles()\"\n role=\"status\"\n [attr.aria-label]=\"'ARIA.LOADING_CONTENT' | translate\"\n>\n @for (line of lines(); track line.index) {\n <span\n class=\"skeleton\"\n [class.skeleton--text]=\"variant() === 'text'\"\n [class.skeleton--circular]=\"variant() === 'circular'\"\n [class.skeleton--rectangular]=\"variant() === 'rectangular'\"\n [class.skeleton--rounded]=\"variant() === 'rounded'\"\n [class.skeleton--avatar]=\"variant() === 'avatar'\"\n [class.skeleton--button]=\"variant() === 'button'\"\n [class.skeleton--chip]=\"variant() === 'chip'\"\n [class.skeleton--card]=\"variant() === 'card'\"\n [class.skeleton--image]=\"variant() === 'image'\"\n [class.skeleton--header]=\"variant() === 'header'\"\n [class.skeleton--paragraph]=\"variant() === 'paragraph'\"\n [class.skeleton--divider]=\"variant() === 'divider'\"\n [class.skeleton--badge]=\"variant() === 'badge'\"\n [class.skeleton--input]=\"variant() === 'input'\"\n [class.skeleton--pulse]=\"animation() === 'pulse'\"\n [class.skeleton--wave]=\"animation() === 'wave'\"\n [class.skeleton--no-animation]=\"animation() === 'none'\"\n [class.skeleton--last]=\"line.isLast\"\n [style]=\"skeletonStyles()\"\n aria-hidden=\"true\"\n ></span>\n }\n <span class=\"sr-only\">{{ 'COMMON.LOADING' | translate }}</span>\n</div>\n", styles: [":host{display:block;--skeleton-bg: rgba(31, 42, 55, .08);--skeleton-wave-color-start: transparent;--skeleton-wave-color-mid-light: rgba(255, 255, 255, .35);--skeleton-wave-color-mid: rgba(255, 255, 255, .6);--skeleton-wave-color-mid-light-end: rgba(255, 255, 255, .35);--skeleton-wave-color-end: transparent;--skeleton-pulse-duration: 1.8s;--skeleton-pulse-opacity-min: .4;--skeleton-wave-duration: 2s;--skeleton-radius-xs: var(--radius-xs);--skeleton-radius-sm: var(--radius-sm);--skeleton-radius-md: var(--radius-sm);--skeleton-radius-lg: var(--radius-md);--skeleton-radius-full: 9999px;--skeleton-radius-circle: 50%;--skeleton-text-height: 1em;--skeleton-text-scale-y: .7;--skeleton-circular-size: 48px;--skeleton-rect-height: 120px;--skeleton-avatar-size: 40px;--skeleton-button-width: 120px;--skeleton-button-height: var(--space-40);--skeleton-chip-width: var(--space-72);--skeleton-chip-height: var(--space-28);--skeleton-card-height: 200px;--skeleton-image-height: 180px;--skeleton-header-width: 60%;--skeleton-header-height: 1.5em;--skeleton-paragraph-height: .9em;--skeleton-paragraph-scale-y: .8;--skeleton-divider-height: 1px;--skeleton-badge-width: var(--space-48);--skeleton-badge-height: var(--space-20);--skeleton-input-height: 44px;--skeleton-last-line-width: 65%}:host-context(html[data-theme=dark]){--skeleton-bg: rgba(255, 255, 255, .1);--skeleton-wave-color-mid-light: rgba(255, 255, 255, .06);--skeleton-wave-color-mid: rgba(255, 255, 255, .12);--skeleton-wave-color-mid-light-end: rgba(255, 255, 255, .06)}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.skeleton-container{display:contents}.skeleton-container--multi{display:flex;flex-direction:column}.skeleton{display:block;background-color:var(--skeleton-bg);overflow:hidden;position:relative}.skeleton--text{width:100%;height:var(--skeleton-text-height);border-radius:var(--skeleton-radius-xs);transform-origin:0 55%;transform:scaleY(var(--skeleton-text-scale-y))}.skeleton--circular{width:var(--skeleton-circular-size);height:var(--skeleton-circular-size);border-radius:var(--skeleton-radius-circle);flex-shrink:0}.skeleton--rectangular{width:100%;height:var(--skeleton-rect-height);border-radius:0}.skeleton--rounded{width:100%;height:var(--skeleton-rect-height);border-radius:var(--skeleton-radius-md)}.skeleton--avatar{width:var(--skeleton-avatar-size);height:var(--skeleton-avatar-size);border-radius:var(--skeleton-radius-circle);flex-shrink:0}.skeleton--button{width:var(--skeleton-button-width);height:var(--skeleton-button-height);border-radius:var(--skeleton-radius-sm);flex-shrink:0}.skeleton--chip{display:inline-block;width:var(--skeleton-chip-width);height:var(--skeleton-chip-height);border-radius:var(--skeleton-radius-full);flex-shrink:0}.skeleton--card{width:100%;height:var(--skeleton-card-height);border-radius:var(--skeleton-radius-lg)}.skeleton--image{width:100%;height:var(--skeleton-image-height);border-radius:var(--skeleton-radius-md)}.skeleton--header{width:var(--skeleton-header-width);height:var(--skeleton-header-height);border-radius:var(--skeleton-radius-xs)}.skeleton--paragraph{width:100%;height:var(--skeleton-paragraph-height);border-radius:var(--skeleton-radius-xs);transform-origin:0 55%;transform:scaleY(var(--skeleton-paragraph-scale-y))}.skeleton--divider{width:100%;height:var(--skeleton-divider-height);border-radius:0}.skeleton--badge{display:inline-block;width:var(--skeleton-badge-width);height:var(--skeleton-badge-height);border-radius:var(--skeleton-radius-xs);flex-shrink:0}.skeleton--input{width:100%;height:var(--skeleton-input-height);border-radius:var(--skeleton-radius-sm)}.skeleton--last{width:var(--skeleton-last-line-width)}.skeleton--pulse{animation:skeleton-pulse var(--skeleton-pulse-duration) ease-in-out infinite}.skeleton--wave:after{content:\"\";position:absolute;inset:0;transform:translate(-100%);background:linear-gradient(90deg,var(--skeleton-wave-color-start) 0%,var(--skeleton-wave-color-mid-light) 30%,var(--skeleton-wave-color-mid) 50%,var(--skeleton-wave-color-mid-light-end) 70%,var(--skeleton-wave-color-end) 100%);animation:skeleton-wave var(--skeleton-wave-duration) ease-in-out infinite}.skeleton--no-animation{animation:none}.skeleton--no-animation:after{display:none}@keyframes skeleton-pulse{0%{opacity:1}50%{opacity:var(--skeleton-pulse-opacity-min)}to{opacity:1}}@keyframes skeleton-wave{0%{transform:translate(-100%)}50%{transform:translate(100%)}to{transform:translate(100%)}}@media(prefers-reduced-motion:reduce){.skeleton--pulse{animation:none}.skeleton--wave:after{animation:none;display:none}}\n"] }]
|
|
411
|
+
}], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], animation: [{ type: i0.Input, args: [{ isSignal: true, alias: "animation", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], borderRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "borderRadius", required: false }] }], count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }], shrinkLastLine: [{ type: i0.Input, args: [{ isSignal: true, alias: "shrinkLastLine", required: false }] }], aspectRatio: [{ type: i0.Input, args: [{ isSignal: true, alias: "aspectRatio", required: false }] }] } });
|
|
412
|
+
|
|
413
|
+
/** Location picker pairing an OSM Leaflet map with a Nominatim search box, which validates every clicked, dragged or searched position before placing a draggable pin and emitting it with its reverse-geocoded address. */
|
|
414
|
+
class SceOsmLocationPickerComponent {
|
|
415
|
+
initialLat = input(isGeoLookupRestricted() ? JEDDAH_MAP_CENTER.lat : 24.7136, ...(ngDevMode ? [{ debugName: "initialLat" }] : /* istanbul ignore next */ []));
|
|
416
|
+
initialLng = input(isGeoLookupRestricted() ? JEDDAH_MAP_CENTER.lng : 46.6753, ...(ngDevMode ? [{ debugName: "initialLng" }] : /* istanbul ignore next */ []));
|
|
417
|
+
zoom = input(6, ...(ngDevMode ? [{ debugName: "zoom" }] : /* istanbul ignore next */ []));
|
|
418
|
+
locale = input('ar', ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
|
|
419
|
+
locationSelect = output();
|
|
420
|
+
nominatim = inject(NominatimService);
|
|
421
|
+
mapValidation = inject(GeoMapPositionValidationService);
|
|
422
|
+
toast = inject(ToastService);
|
|
423
|
+
destroyRef = inject(DestroyRef);
|
|
424
|
+
searchQuery = '';
|
|
425
|
+
isSearching = signal(false, ...(ngDevMode ? [{ debugName: "isSearching" }] : /* istanbul ignore next */ []));
|
|
426
|
+
currentLat = signal(isGeoLookupRestricted() ? JEDDAH_MAP_CENTER.lat : 24.7136, ...(ngDevMode ? [{ debugName: "currentLat" }] : /* istanbul ignore next */ []));
|
|
427
|
+
currentLng = signal(isGeoLookupRestricted() ? JEDDAH_MAP_CENTER.lng : 46.6753, ...(ngDevMode ? [{ debugName: "currentLng" }] : /* istanbul ignore next */ []));
|
|
428
|
+
selectedLocation = signal(null, ...(ngDevMode ? [{ debugName: "selectedLocation" }] : /* istanbul ignore next */ []));
|
|
429
|
+
selectedCoordsLabel = computed(() => {
|
|
430
|
+
const loc = this.selectedLocation();
|
|
431
|
+
return loc ? `${loc.lat.toFixed(6)}, ${loc.lng.toFixed(6)}` : '';
|
|
432
|
+
}, ...(ngDevMode ? [{ debugName: "selectedCoordsLabel" }] : /* istanbul ignore next */ []));
|
|
433
|
+
currentMarkers = signal([], ...(ngDevMode ? [{ debugName: "currentMarkers" }] : /* istanbul ignore next */ []));
|
|
434
|
+
onMapClick(coords) {
|
|
435
|
+
void this.validateAndPlacePin(coords.lat, coords.lng);
|
|
436
|
+
}
|
|
437
|
+
onMarkerDragEnd(event) {
|
|
438
|
+
void this.validateAndPlacePin(event.lat, event.lng);
|
|
439
|
+
}
|
|
440
|
+
async validateAndPlacePin(lat, lng) {
|
|
441
|
+
const validation = await this.mapValidation.validateMapPositionForGeoForms(lat, lng, this.locale());
|
|
442
|
+
if (!validation.ok) {
|
|
443
|
+
this.toast.warning('', mapPositionValidationMessage(this.locale(), validation.reason));
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
this.currentLat.set(lat);
|
|
447
|
+
this.currentLng.set(lng);
|
|
448
|
+
this.placePin(lat, lng);
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
placePin(lat, lng) {
|
|
452
|
+
const loc = { lat, lng };
|
|
453
|
+
this.selectedLocation.set(loc);
|
|
454
|
+
this.currentMarkers.set([{ lat, lng, draggable: true }]);
|
|
455
|
+
this.nominatim
|
|
456
|
+
.getDisplayAddress(lat, lng, this.locale())
|
|
457
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
458
|
+
.subscribe({
|
|
459
|
+
next: address => {
|
|
460
|
+
const updated = { lat, lng, address };
|
|
461
|
+
this.selectedLocation.set(updated);
|
|
462
|
+
this.locationSelect.emit(updated);
|
|
463
|
+
},
|
|
464
|
+
error: () => {
|
|
465
|
+
this.locationSelect.emit(loc);
|
|
466
|
+
},
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
onSearch() {
|
|
470
|
+
if (!this.searchQuery.trim()) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
this.isSearching.set(true);
|
|
474
|
+
this.nominatim
|
|
475
|
+
.search(this.searchQuery, this.locale(), 1)
|
|
476
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
477
|
+
.subscribe({
|
|
478
|
+
next: results => {
|
|
479
|
+
if (results.length > 0) {
|
|
480
|
+
const result = results[0];
|
|
481
|
+
void this.validateAndPlacePin(result.lat, result.lng).finally(() => {
|
|
482
|
+
this.isSearching.set(false);
|
|
483
|
+
});
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
this.isSearching.set(false);
|
|
487
|
+
},
|
|
488
|
+
error: () => {
|
|
489
|
+
this.isSearching.set(false);
|
|
490
|
+
},
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
bindHost = (() => {
|
|
494
|
+
const center = resolveInitialCenter(String(this.initialLat()), String(this.initialLng()));
|
|
495
|
+
this.currentLat.set(center.lat);
|
|
496
|
+
this.currentLng.set(center.lng);
|
|
497
|
+
})();
|
|
498
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceOsmLocationPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
499
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: SceOsmLocationPickerComponent, isStandalone: true, selector: "sce-osm-location-picker", inputs: { initialLat: { classPropertyName: "initialLat", publicName: "initialLat", isSignal: true, isRequired: false, transformFunction: null }, initialLng: { classPropertyName: "initialLng", publicName: "initialLng", isSignal: true, isRequired: false, transformFunction: null }, zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { locationSelect: "locationSelect" }, host: { classAttribute: "sce-osm-location-picker" }, hostDirectives: [{ directive: i1.SceVariantDirective, inputs: ["sceVariant", "sceVariant", "sceDensity", "sceDensity"] }], ngImport: i0, template: "<div>\n <div class=\"searchRow\">\n <input\n class=\"searchInput\"\n type=\"text\"\n [placeholder]=\"'MAPS.SEARCH_PLACEHOLDER' | translate\"\n [(ngModel)]=\"searchQuery\"\n (keydown.enter)=\"onSearch()\"\n />\n <button type=\"button\" class=\"searchButton\" [disabled]=\"isSearching()\" (click)=\"onSearch()\">\n @if (isSearching()) {\n <sce-skeleton variant=\"rounded\" width=\"1.25rem\" height=\"1.25rem\" animation=\"wave\" aria-hidden=\"true\" />\n } @else {\n {{ 'MAPS.SEARCH' | translate }}\n }\n </button>\n </div>\n <p class=\"providerNote\">{{ 'MAPS.PROVIDER_NOTE' | translate }}</p>\n\n <div style=\"height: 300px; margin-top: 0.75rem; border-radius: 0.75rem; overflow: hidden\">\n <sce-osm-leaflet-map\n [latitude]=\"currentLat()\"\n [longitude]=\"currentLng()\"\n [zoom]=\"zoom()\"\n [markers]=\"currentMarkers()\"\n [interactive]=\"true\"\n (mapClick)=\"onMapClick($event)\"\n (markerDragEnd)=\"onMarkerDragEnd($event)\"\n />\n </div>\n\n @if (selectedLocation()) {\n <p style=\"margin: 0.5rem 0 0; font-size: 0.8rem; color: var(--color-text-secondary)\">\n <span dir=\"ltr\">{{ selectedCoordsLabel() }}</span>\n @if (selectedLocation()!.address) {\n \u2014 {{ selectedLocation()!.address }}\n }\n </p>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host.sce-osm-location-picker{display:block}.providerNote{margin:var(--space-4) 0 0;font-size:var(--font-size-xs);color:var(--color-neutral-500)}.searchRow{display:flex;gap:var(--space-8);align-items:stretch}.searchRow .searchInput{flex:1;min-width:0}.searchButton{flex-shrink:0;min-height:var(--space-44, var(--size-44));padding:0 1rem;border:0;border-radius:var(--radius-sm);background:var(--button-background-primary-bg-primary);color:var(--color-base-white);font-size:.9rem;font-weight:600;cursor:pointer}.searchButton:disabled{opacity:.55;cursor:not-allowed}.searchFeedback{margin:0;font-size:.8rem;color:var(--color-neutral-600)}.searchResults{margin:0;padding:0;list-style:none;max-height:var(--size-160);overflow:auto;border:1px solid var(--color-neutral-200);border-radius:var(--radius-sm)}.searchResultButton{width:100%;padding:.65rem .85rem;border:0;border-block-end:1px solid var(--color-neutral-100);background:var(--color-base-white);text-align:start;font-size:.85rem;color:var(--text-text-primary-900);cursor:pointer}.searchResultButton:hover{background:var(--color-neutral-50)}.searchResults li:last-child .searchResultButton{border-block-end:0}.mapWrapper{position:relative;width:100%;height:var(--size-360);border-radius:var(--radius-lg);overflow:hidden}.inlineMapWrapper{position:relative;width:100%;border-radius:var(--radius-lg);overflow:hidden;border:1px solid var(--color-neutral-200);background:var(--color-neutral-200)}.mapHost{position:absolute;inset:0;z-index:1}.mapHost .leaflet-container{width:100%;height:100%;font-family:inherit;background:var(--color-neutral-200)}.mapHost .leaflet-container img.leaflet-tile.leaflet-tile,.mapHost .leaflet-container img.leaflet-marker-icon.leaflet-marker-icon,.mapHost .leaflet-container img.leaflet-marker-shadow.leaflet-marker-shadow{max-width:none;max-height:none;width:auto;height:auto}.mapHost .leaflet-pane,.mapHost .leaflet-tile-pane,.mapHost .leaflet-overlay-pane{z-index:auto}.mapLoadingOverlay{position:absolute;inset:0;z-index:2;pointer-events:none;background:linear-gradient(90deg,var(--color-neutral-100) 25%,var(--color-neutral-300) 50%,var(--color-neutral-100) 75%);background-size:200% 100%;animation:mapLoadingShimmer 1.2s ease-in-out infinite}.inlineMapShell{position:relative;width:100%}.fullscreenButton{position:absolute;top:.65rem;inset-inline-start:.65rem;z-index:1001;display:inline-flex;align-items:center;justify-content:center;gap:.35rem;min-width:2.25rem;height:2.25rem;padding:0 .55rem;border:0;border-radius:var(--radius-sm);background:#fffffff2;color:var(--text-text-primary-900);box-shadow:0 2px 10px #0000002e;cursor:pointer;pointer-events:auto}.fullscreenButton:hover{background:var(--color-base-white)}.fullscreenButton:focus-visible{outline:2px solid var(--button-background-primary-bg-primary);outline-offset:2px}.fullscreenButtonLabel{font-size:var(--font-size-xs);font-weight:600;line-height:1}.expandedMapWrapper{position:relative;width:100%;height:min(70vb,var(--size-520));border-radius:var(--radius-lg);overflow:hidden}@keyframes mapLoadingShimmer{0%{background-position:100% 0}to{background-position:-100% 0}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: SceSkeleton, selector: "sce-skeleton", inputs: ["variant", "animation", "width", "height", "borderRadius", "count", "gap", "shrinkLastLine", "aspectRatio"] }, { kind: "component", type: SceOsmLeafletMapComponent, selector: "sce-osm-leaflet-map", inputs: ["latitude", "longitude", "zoom", "markers", "interactive"], outputs: ["mapClick", "markerDragEnd"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
500
|
+
}
|
|
501
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceOsmLocationPickerComponent, decorators: [{
|
|
502
|
+
type: Component,
|
|
503
|
+
args: [{ hostDirectives: [{ directive: SceVariantDirective, inputs: ['sceVariant', 'sceDensity'] }], host: { class: 'sce-osm-location-picker' }, selector: 'sce-osm-location-picker', standalone: true, imports: [FormsModule, TranslatePipe, SceSkeleton, SceOsmLeafletMapComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div>\n <div class=\"searchRow\">\n <input\n class=\"searchInput\"\n type=\"text\"\n [placeholder]=\"'MAPS.SEARCH_PLACEHOLDER' | translate\"\n [(ngModel)]=\"searchQuery\"\n (keydown.enter)=\"onSearch()\"\n />\n <button type=\"button\" class=\"searchButton\" [disabled]=\"isSearching()\" (click)=\"onSearch()\">\n @if (isSearching()) {\n <sce-skeleton variant=\"rounded\" width=\"1.25rem\" height=\"1.25rem\" animation=\"wave\" aria-hidden=\"true\" />\n } @else {\n {{ 'MAPS.SEARCH' | translate }}\n }\n </button>\n </div>\n <p class=\"providerNote\">{{ 'MAPS.PROVIDER_NOTE' | translate }}</p>\n\n <div style=\"height: 300px; margin-top: 0.75rem; border-radius: 0.75rem; overflow: hidden\">\n <sce-osm-leaflet-map\n [latitude]=\"currentLat()\"\n [longitude]=\"currentLng()\"\n [zoom]=\"zoom()\"\n [markers]=\"currentMarkers()\"\n [interactive]=\"true\"\n (mapClick)=\"onMapClick($event)\"\n (markerDragEnd)=\"onMarkerDragEnd($event)\"\n />\n </div>\n\n @if (selectedLocation()) {\n <p style=\"margin: 0.5rem 0 0; font-size: 0.8rem; color: var(--color-text-secondary)\">\n <span dir=\"ltr\">{{ selectedCoordsLabel() }}</span>\n @if (selectedLocation()!.address) {\n \u2014 {{ selectedLocation()!.address }}\n }\n </p>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host.sce-osm-location-picker{display:block}.providerNote{margin:var(--space-4) 0 0;font-size:var(--font-size-xs);color:var(--color-neutral-500)}.searchRow{display:flex;gap:var(--space-8);align-items:stretch}.searchRow .searchInput{flex:1;min-width:0}.searchButton{flex-shrink:0;min-height:var(--space-44, var(--size-44));padding:0 1rem;border:0;border-radius:var(--radius-sm);background:var(--button-background-primary-bg-primary);color:var(--color-base-white);font-size:.9rem;font-weight:600;cursor:pointer}.searchButton:disabled{opacity:.55;cursor:not-allowed}.searchFeedback{margin:0;font-size:.8rem;color:var(--color-neutral-600)}.searchResults{margin:0;padding:0;list-style:none;max-height:var(--size-160);overflow:auto;border:1px solid var(--color-neutral-200);border-radius:var(--radius-sm)}.searchResultButton{width:100%;padding:.65rem .85rem;border:0;border-block-end:1px solid var(--color-neutral-100);background:var(--color-base-white);text-align:start;font-size:.85rem;color:var(--text-text-primary-900);cursor:pointer}.searchResultButton:hover{background:var(--color-neutral-50)}.searchResults li:last-child .searchResultButton{border-block-end:0}.mapWrapper{position:relative;width:100%;height:var(--size-360);border-radius:var(--radius-lg);overflow:hidden}.inlineMapWrapper{position:relative;width:100%;border-radius:var(--radius-lg);overflow:hidden;border:1px solid var(--color-neutral-200);background:var(--color-neutral-200)}.mapHost{position:absolute;inset:0;z-index:1}.mapHost .leaflet-container{width:100%;height:100%;font-family:inherit;background:var(--color-neutral-200)}.mapHost .leaflet-container img.leaflet-tile.leaflet-tile,.mapHost .leaflet-container img.leaflet-marker-icon.leaflet-marker-icon,.mapHost .leaflet-container img.leaflet-marker-shadow.leaflet-marker-shadow{max-width:none;max-height:none;width:auto;height:auto}.mapHost .leaflet-pane,.mapHost .leaflet-tile-pane,.mapHost .leaflet-overlay-pane{z-index:auto}.mapLoadingOverlay{position:absolute;inset:0;z-index:2;pointer-events:none;background:linear-gradient(90deg,var(--color-neutral-100) 25%,var(--color-neutral-300) 50%,var(--color-neutral-100) 75%);background-size:200% 100%;animation:mapLoadingShimmer 1.2s ease-in-out infinite}.inlineMapShell{position:relative;width:100%}.fullscreenButton{position:absolute;top:.65rem;inset-inline-start:.65rem;z-index:1001;display:inline-flex;align-items:center;justify-content:center;gap:.35rem;min-width:2.25rem;height:2.25rem;padding:0 .55rem;border:0;border-radius:var(--radius-sm);background:#fffffff2;color:var(--text-text-primary-900);box-shadow:0 2px 10px #0000002e;cursor:pointer;pointer-events:auto}.fullscreenButton:hover{background:var(--color-base-white)}.fullscreenButton:focus-visible{outline:2px solid var(--button-background-primary-bg-primary);outline-offset:2px}.fullscreenButtonLabel{font-size:var(--font-size-xs);font-weight:600;line-height:1}.expandedMapWrapper{position:relative;width:100%;height:min(70vb,var(--size-520));border-radius:var(--radius-lg);overflow:hidden}@keyframes mapLoadingShimmer{0%{background-position:100% 0}to{background-position:-100% 0}}\n"] }]
|
|
504
|
+
}], propDecorators: { initialLat: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialLat", required: false }] }], initialLng: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialLng", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], locationSelect: [{ type: i0.Output, args: ["locationSelect"] }] } });
|
|
505
|
+
|
|
506
|
+
const TILE_URL = MAP_CONFIG.tileUrl;
|
|
507
|
+
function hasValidCoordinates(lat, lng) {
|
|
508
|
+
return (typeof lat === 'number' &&
|
|
509
|
+
typeof lng === 'number' &&
|
|
510
|
+
Number.isFinite(lat) &&
|
|
511
|
+
Number.isFinite(lng) &&
|
|
512
|
+
Math.abs(lat) <= 90 &&
|
|
513
|
+
Math.abs(lng) <= 180 &&
|
|
514
|
+
!(lat === 0 && lng === 0));
|
|
515
|
+
}
|
|
516
|
+
function buildGoogleEmbedUrl(lat, lng, address) {
|
|
517
|
+
if (hasValidCoordinates(lat, lng)) {
|
|
518
|
+
return `https://www.google.com/maps?q=${lat},${lng}&z=15&output=embed`;
|
|
519
|
+
}
|
|
520
|
+
const query = address?.trim();
|
|
521
|
+
if (query) {
|
|
522
|
+
return `https://www.google.com/maps?q=${encodeURIComponent(query)}&z=14&output=embed`;
|
|
523
|
+
}
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
/** Read-only map of an office's location: a Leaflet map with a marker when valid coordinates are given, a Google Maps embed built from the address when they are not, and a "not specified" fallback when neither is available. */
|
|
527
|
+
class SceOfficeLocationMapComponent {
|
|
528
|
+
sanitizer = inject(DomSanitizer);
|
|
529
|
+
latitude = input(...(ngDevMode ? [undefined, { debugName: "latitude" }] : /* istanbul ignore next */ []));
|
|
530
|
+
longitude = input(...(ngDevMode ? [undefined, { debugName: "longitude" }] : /* istanbul ignore next */ []));
|
|
531
|
+
address = input(...(ngDevMode ? [undefined, { debugName: "address" }] : /* istanbul ignore next */ []));
|
|
532
|
+
emptyLabel = input(...(ngDevMode ? [undefined, { debugName: "emptyLabel" }] : /* istanbul ignore next */ []));
|
|
533
|
+
mapContainer;
|
|
534
|
+
map = null;
|
|
535
|
+
cancelLeafletResize = null;
|
|
536
|
+
hasCoords = computed(() => hasValidCoordinates(this.latitude(), this.longitude()), ...(ngDevMode ? [{ debugName: "hasCoords" }] : /* istanbul ignore next */ []));
|
|
537
|
+
googleEmbedUrl = computed(() => {
|
|
538
|
+
const url = buildGoogleEmbedUrl(this.latitude(), this.longitude(), this.address());
|
|
539
|
+
return url ? this.sanitizer.bypassSecurityTrustResourceUrl(url) : null;
|
|
540
|
+
}, ...(ngDevMode ? [{ debugName: "googleEmbedUrl" }] : /* istanbul ignore next */ []));
|
|
541
|
+
ngAfterViewInit() {
|
|
542
|
+
if (this.hasCoords() && this.mapContainer) {
|
|
543
|
+
void this.initLeaflet();
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
ngOnDestroy() {
|
|
547
|
+
this.cancelLeafletResize?.();
|
|
548
|
+
this.cancelLeafletResize = null;
|
|
549
|
+
if (this.map && typeof this.map.remove === 'function') {
|
|
550
|
+
this.map.remove();
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
async initLeaflet() {
|
|
554
|
+
const container = this.mapContainer?.nativeElement;
|
|
555
|
+
if (!container) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const leafletModule = await import('leaflet');
|
|
559
|
+
// CJS/ESM interop: in the esbuild bundle the Leaflet API lands on .default, in dev it may not.
|
|
560
|
+
const L = (leafletModule.default ??
|
|
561
|
+
leafletModule);
|
|
562
|
+
if (!this.latitude() || !this.longitude()) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
const lat = this.latitude();
|
|
566
|
+
const lng = this.longitude();
|
|
567
|
+
const map = L.map(container, {
|
|
568
|
+
center: [lat, lng],
|
|
569
|
+
zoom: 14,
|
|
570
|
+
scrollWheelZoom: false,
|
|
571
|
+
dragging: true,
|
|
572
|
+
zoomControl: true,
|
|
573
|
+
attributionControl: false,
|
|
574
|
+
});
|
|
575
|
+
L.tileLayer(TILE_URL, { attribution: '', maxZoom: MAP_CONFIG.maxZoom }).addTo(map);
|
|
576
|
+
L.marker([lat, lng], { icon: createLeafletDefaultIcon(L) }).addTo(map);
|
|
577
|
+
this.map = map;
|
|
578
|
+
this.cancelLeafletResize = scheduleLeafletResize(map);
|
|
579
|
+
}
|
|
580
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceOfficeLocationMapComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
581
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: SceOfficeLocationMapComponent, isStandalone: true, selector: "sce-office-location-map", inputs: { latitude: { classPropertyName: "latitude", publicName: "latitude", isSignal: true, isRequired: false, transformFunction: null }, longitude: { classPropertyName: "longitude", publicName: "longitude", isSignal: true, isRequired: false, transformFunction: null }, address: { classPropertyName: "address", publicName: "address", isSignal: true, isRequired: false, transformFunction: null }, emptyLabel: { classPropertyName: "emptyLabel", publicName: "emptyLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "sce-office-location-map" }, viewQueries: [{ propertyName: "mapContainer", first: true, predicate: ["mapContainer"], descendants: true }], hostDirectives: [{ directive: i1.SceVariantDirective, inputs: ["sceVariant", "sceVariant", "sceDensity", "sceDensity"] }], ngImport: i0, template: "@if (hasCoords()) {\n <div class=\"mapShell\">\n <div #mapContainer class=\"mapRoot\" role=\"presentation\"></div>\n </div>\n} @else if (googleEmbedUrl()) {\n <div class=\"mapShell\">\n <iframe\n [title]=\"address()?.trim() || ('map.officeLocation' | translate)\"\n [src]=\"googleEmbedUrl()\"\n class=\"mapFrame\"\n loading=\"lazy\"\n referrerpolicy=\"no-referrer-when-downgrade\"\n allowfullscreen\n ></iframe>\n </div>\n} @else {\n <div class=\"mapShell mapFallback\">\n {{ emptyLabel() || ('common.notSpecified' | translate) }}\n </div>\n}\n", styles: ["@charset \"UTF-8\";.mapShell{width:100%;overflow:hidden;border-radius:var(--radius-lg);background:var(--color-neutral-200)}.mapFrame{display:block;width:100%;height:var(--size-220);border:0}.mapRoot{width:100%;height:var(--size-220);min-height:var(--size-220);background:var(--color-neutral-200)}.mapRoot.mapRoot.leaflet-container{width:100%;height:var(--size-220);font-family:inherit}.mapRoot.mapRoot img.leaflet-tile,.mapRoot.mapRoot img.leaflet-marker-icon,.mapRoot.mapRoot img.leaflet-marker-shadow{max-width:none;max-height:none}.mapRoot.mapRoot .leaflet-control-attribution,.mapRoot.mapRoot .leaflet-control-container.leaflet-bottom{display:none}.mapRoot .leaflet-pane,.mapRoot .leaflet-map-pane,.mapRoot .leaflet-tile-pane{width:100%;height:100%}.mapFallback{display:flex;align-items:center;justify-content:center;min-height:var(--size-220);padding:var(--space-16);text-align:center;font-family:var(--font-family-arabic-primary);font-size:var(--font-size-md);color:var(--text-text-secondary-700)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
582
|
+
}
|
|
583
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceOfficeLocationMapComponent, decorators: [{
|
|
584
|
+
type: Component,
|
|
585
|
+
args: [{ hostDirectives: [{ directive: SceVariantDirective, inputs: ['sceVariant', 'sceDensity'] }], host: { class: 'sce-office-location-map' }, selector: 'sce-office-location-map', standalone: true, imports: [TranslatePipe], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (hasCoords()) {\n <div class=\"mapShell\">\n <div #mapContainer class=\"mapRoot\" role=\"presentation\"></div>\n </div>\n} @else if (googleEmbedUrl()) {\n <div class=\"mapShell\">\n <iframe\n [title]=\"address()?.trim() || ('map.officeLocation' | translate)\"\n [src]=\"googleEmbedUrl()\"\n class=\"mapFrame\"\n loading=\"lazy\"\n referrerpolicy=\"no-referrer-when-downgrade\"\n allowfullscreen\n ></iframe>\n </div>\n} @else {\n <div class=\"mapShell mapFallback\">\n {{ emptyLabel() || ('common.notSpecified' | translate) }}\n </div>\n}\n", styles: ["@charset \"UTF-8\";.mapShell{width:100%;overflow:hidden;border-radius:var(--radius-lg);background:var(--color-neutral-200)}.mapFrame{display:block;width:100%;height:var(--size-220);border:0}.mapRoot{width:100%;height:var(--size-220);min-height:var(--size-220);background:var(--color-neutral-200)}.mapRoot.mapRoot.leaflet-container{width:100%;height:var(--size-220);font-family:inherit}.mapRoot.mapRoot img.leaflet-tile,.mapRoot.mapRoot img.leaflet-marker-icon,.mapRoot.mapRoot img.leaflet-marker-shadow{max-width:none;max-height:none}.mapRoot.mapRoot .leaflet-control-attribution,.mapRoot.mapRoot .leaflet-control-container.leaflet-bottom{display:none}.mapRoot .leaflet-pane,.mapRoot .leaflet-map-pane,.mapRoot .leaflet-tile-pane{width:100%;height:100%}.mapFallback{display:flex;align-items:center;justify-content:center;min-height:var(--size-220);padding:var(--space-16);text-align:center;font-family:var(--font-family-arabic-primary);font-size:var(--font-size-md);color:var(--text-text-secondary-700)}\n"] }]
|
|
586
|
+
}], propDecorators: { latitude: [{ type: i0.Input, args: [{ isSignal: true, alias: "latitude", required: false }] }], longitude: [{ type: i0.Input, args: [{ isSignal: true, alias: "longitude", required: false }] }], address: [{ type: i0.Input, args: [{ isSignal: true, alias: "address", required: false }] }], emptyLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyLabel", required: false }] }], mapContainer: [{
|
|
587
|
+
type: ViewChild,
|
|
588
|
+
args: ['mapContainer']
|
|
589
|
+
}] } });
|
|
590
|
+
|
|
591
|
+
/** Titled card framing the OSM map of branch/office locations. */
|
|
592
|
+
class SceStoreLocationsComponent {
|
|
593
|
+
titleKey = input('storeLocations.title', ...(ngDevMode ? [{ debugName: "titleKey" }] : /* istanbul ignore next */ []));
|
|
594
|
+
showHeader = input(true, ...(ngDevMode ? [{ debugName: "showHeader" }] : /* istanbul ignore next */ []));
|
|
595
|
+
locations = input([], ...(ngDevMode ? [{ debugName: "locations" }] : /* istanbul ignore next */ []));
|
|
596
|
+
selectedLocationId = input(...(ngDevMode ? [undefined, { debugName: "selectedLocationId" }] : /* istanbul ignore next */ []));
|
|
597
|
+
/** Map viewport height, in px. */
|
|
598
|
+
height = input(400, ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
|
|
599
|
+
locationSelect = output();
|
|
600
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceStoreLocationsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
601
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: SceStoreLocationsComponent, isStandalone: true, selector: "sce-store-locations", inputs: { titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, showHeader: { classPropertyName: "showHeader", publicName: "showHeader", isSignal: true, isRequired: false, transformFunction: null }, locations: { classPropertyName: "locations", publicName: "locations", isSignal: true, isRequired: false, transformFunction: null }, selectedLocationId: { classPropertyName: "selectedLocationId", publicName: "selectedLocationId", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { locationSelect: "locationSelect" }, host: { classAttribute: "sce-store-locations" }, hostDirectives: [{ directive: i1.SceVariantDirective, inputs: ["sceVariant", "sceVariant", "sceDensity", "sceDensity"] }], ngImport: i0, template: "@if (showHeader()) {\n <header class=\"sce-store-locations__header\">\n <h3 class=\"sce-store-locations__title\">{{ titleKey() | translate }}</h3>\n </header>\n}\n\n<div class=\"sce-store-locations__map\" [style.block-size.px]=\"height()\">\n <sce-osm-offices-map\n [offices]=\"locations()\"\n [selectedOfficeId]=\"selectedLocationId()\"\n [ariaLabelInput]=\"titleKey() | translate\"\n (selectOffice)=\"locationSelect.emit($event)\"\n />\n</div>\n", styles: [":host{display:flex;flex-direction:column;overflow:hidden;border:var(--border-width-1) solid var(--color-border-subtle);border-radius:var(--radius-lg);background:var(--color-surface-primary)}.sce-store-locations__header{padding:var(--space-16) var(--space-20);border-block-end:var(--border-width-1) solid var(--color-border-light)}.sce-store-locations__title{margin:0;font-size:var(--font-size-lg);font-weight:var(--font-weight-semibold);line-height:var(--line-height-snug);color:var(--color-text-primary);text-align:start}.sce-store-locations__map{position:relative;inline-size:100%}.sce-store-locations__map sce-osm-offices-map{display:block;inline-size:100%;block-size:100%}\n"], dependencies: [{ kind: "component", type: SceOsmOfficesMapComponent, selector: "sce-osm-offices-map", inputs: ["offices", "selectedOfficeId", "showTooltip", "ariaLabelInput"], outputs: ["selectOffice", "ready", "mapError"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
602
|
+
}
|
|
603
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceStoreLocationsComponent, decorators: [{
|
|
604
|
+
type: Component,
|
|
605
|
+
args: [{ hostDirectives: [{ directive: SceVariantDirective, inputs: ['sceVariant', 'sceDensity'] }], selector: 'sce-store-locations', standalone: true, imports: [TranslatePipe, SceOsmOfficesMapComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'sce-store-locations' }, template: "@if (showHeader()) {\n <header class=\"sce-store-locations__header\">\n <h3 class=\"sce-store-locations__title\">{{ titleKey() | translate }}</h3>\n </header>\n}\n\n<div class=\"sce-store-locations__map\" [style.block-size.px]=\"height()\">\n <sce-osm-offices-map\n [offices]=\"locations()\"\n [selectedOfficeId]=\"selectedLocationId()\"\n [ariaLabelInput]=\"titleKey() | translate\"\n (selectOffice)=\"locationSelect.emit($event)\"\n />\n</div>\n", styles: [":host{display:flex;flex-direction:column;overflow:hidden;border:var(--border-width-1) solid var(--color-border-subtle);border-radius:var(--radius-lg);background:var(--color-surface-primary)}.sce-store-locations__header{padding:var(--space-16) var(--space-20);border-block-end:var(--border-width-1) solid var(--color-border-light)}.sce-store-locations__title{margin:0;font-size:var(--font-size-lg);font-weight:var(--font-weight-semibold);line-height:var(--line-height-snug);color:var(--color-text-primary);text-align:start}.sce-store-locations__map{position:relative;inline-size:100%}.sce-store-locations__map sce-osm-offices-map{display:block;inline-size:100%;block-size:100%}\n"] }]
|
|
606
|
+
}], propDecorators: { titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }], showHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHeader", required: false }] }], locations: [{ type: i0.Input, args: [{ isSignal: true, alias: "locations", required: false }] }], selectedLocationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedLocationId", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], locationSelect: [{ type: i0.Output, args: ["locationSelect"] }] } });
|
|
607
|
+
|
|
608
|
+
/** Shared form-field wrapper that renders the label, required marker, projected control, help text and error message for the other sce field components; `id` and `label` are required. */
|
|
609
|
+
class SceFieldComponent {
|
|
610
|
+
id = input.required(...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
611
|
+
label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
612
|
+
required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
|
|
613
|
+
/**
|
|
614
|
+
* Hides the label visually while leaving it in the accessibility tree, for controls whose
|
|
615
|
+
* surroundings already name them — a chat composer, a cell inside a labelled table column, or a
|
|
616
|
+
* field sitting under its own section heading. It is never a licence to ship an unnamed control:
|
|
617
|
+
* `label` stays required precisely so the accessible name cannot be dropped.
|
|
618
|
+
*/
|
|
619
|
+
hideLabel = input(false, ...(ngDevMode ? [{ debugName: "hideLabel" }] : /* istanbul ignore next */ []));
|
|
620
|
+
helpText = input(...(ngDevMode ? [undefined, { debugName: "helpText" }] : /* istanbul ignore next */ []));
|
|
621
|
+
error = input(...(ngDevMode ? [undefined, { debugName: "error" }] : /* istanbul ignore next */ []));
|
|
622
|
+
labelClassName = input(...(ngDevMode ? [undefined, { debugName: "labelClassName" }] : /* istanbul ignore next */ []));
|
|
623
|
+
fieldClassName = input(...(ngDevMode ? [undefined, { debugName: "fieldClassName" }] : /* istanbul ignore next */ []));
|
|
624
|
+
containerClasses = computed(() => {
|
|
625
|
+
const classes = ['sce-field'];
|
|
626
|
+
if (this.fieldClassName()) {
|
|
627
|
+
classes.push(this.fieldClassName());
|
|
628
|
+
}
|
|
629
|
+
return classes.join(' ');
|
|
630
|
+
}, ...(ngDevMode ? [{ debugName: "containerClasses" }] : /* istanbul ignore next */ []));
|
|
631
|
+
labelClasses = computed(() => {
|
|
632
|
+
const classes = ['sce-field__label'];
|
|
633
|
+
if (this.error()) {
|
|
634
|
+
classes.push('sce-field__label--invalid');
|
|
635
|
+
}
|
|
636
|
+
if (this.hideLabel()) {
|
|
637
|
+
classes.push('sce-field__label--hidden');
|
|
638
|
+
}
|
|
639
|
+
if (this.labelClassName()) {
|
|
640
|
+
classes.push(this.labelClassName());
|
|
641
|
+
}
|
|
642
|
+
return classes.join(' ');
|
|
643
|
+
}, ...(ngDevMode ? [{ debugName: "labelClasses" }] : /* istanbul ignore next */ []));
|
|
644
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
645
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: SceFieldComponent, isStandalone: true, selector: "sce-field", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, hideLabel: { classPropertyName: "hideLabel", publicName: "hideLabel", isSignal: true, isRequired: false, transformFunction: null }, helpText: { classPropertyName: "helpText", publicName: "helpText", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, labelClassName: { classPropertyName: "labelClassName", publicName: "labelClassName", isSignal: true, isRequired: false, transformFunction: null }, fieldClassName: { classPropertyName: "fieldClassName", publicName: "fieldClassName", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "sce-field-host" }, hostDirectives: [{ directive: i1.SceVariantDirective, inputs: ["sceVariant", "sceVariant", "sceDensity", "sceDensity"] }], ngImport: i0, template: "<div [class]=\"containerClasses()\">\n <label [attr.id]=\"id() + '-label'\" [attr.for]=\"id()\" [class]=\"labelClasses()\">\n {{ label() }}\n @if (required()) {\n <span class=\"sce-field__asterisk\" aria-hidden=\"true\"> *</span>\n }\n </label>\n <ng-content />\n @if (error()) {\n <!-- id lets the paired control reference this text via aria-describedby. -->\n <div role=\"alert\" class=\"sce-field__error\" [attr.id]=\"id() + '-error'\">{{ error() }}</div>\n }\n @if (helpText()) {\n <div class=\"sce-field__help\">{{ helpText() }}</div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";.sce-field,sce-field{display:flex;flex-direction:column;gap:var(--space-xs, var(--space-8));min-width:0;width:100%}.sce-field__label{display:flex;align-items:baseline;gap:var(--space-xs);font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);color:var(--Form-field-text-label, var(--color-text-default))}.sce-field__label--invalid{color:var(--text-text-error-primary)}.sce-field__label--hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border-width:0}.sce-field__asterisk{color:var(--text-text-error-primary)}.sce-field__error{font-size:var(--font-size-xs);color:var(--text-text-error-primary);overflow-wrap:anywhere}.sce-field__help{font-size:var(--font-size-2xs);color:var(--color-muted)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
646
|
+
}
|
|
647
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceFieldComponent, decorators: [{
|
|
648
|
+
type: Component,
|
|
649
|
+
args: [{ hostDirectives: [{ directive: SceVariantDirective, inputs: ['sceVariant', 'sceDensity'] }], selector: 'sce-field', standalone: true, host: { class: 'sce-field-host' }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div [class]=\"containerClasses()\">\n <label [attr.id]=\"id() + '-label'\" [attr.for]=\"id()\" [class]=\"labelClasses()\">\n {{ label() }}\n @if (required()) {\n <span class=\"sce-field__asterisk\" aria-hidden=\"true\"> *</span>\n }\n </label>\n <ng-content />\n @if (error()) {\n <!-- id lets the paired control reference this text via aria-describedby. -->\n <div role=\"alert\" class=\"sce-field__error\" [attr.id]=\"id() + '-error'\">{{ error() }}</div>\n }\n @if (helpText()) {\n <div class=\"sce-field__help\">{{ helpText() }}</div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";.sce-field,sce-field{display:flex;flex-direction:column;gap:var(--space-xs, var(--space-8));min-width:0;width:100%}.sce-field__label{display:flex;align-items:baseline;gap:var(--space-xs);font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);color:var(--Form-field-text-label, var(--color-text-default))}.sce-field__label--invalid{color:var(--text-text-error-primary)}.sce-field__label--hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border-width:0}.sce-field__asterisk{color:var(--text-text-error-primary)}.sce-field__error{font-size:var(--font-size-xs);color:var(--text-text-error-primary);overflow-wrap:anywhere}.sce-field__help{font-size:var(--font-size-2xs);color:var(--color-muted)}\n"] }]
|
|
650
|
+
}], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], hideLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideLabel", required: false }] }], helpText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helpText", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], labelClassName: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelClassName", required: false }] }], fieldClassName: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldClassName", required: false }] }] } });
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Thin, project-native wrapper around Hugeicons' `hugeicons-icon`.
|
|
654
|
+
*
|
|
655
|
+
* Renders an inline SVG synchronously from icon data imported out of
|
|
656
|
+
* `@hugeicons/core-free-icons` — no HTTP fetch, SSR-safe, and tree-shakeable
|
|
657
|
+
* (import only the icons you use, e.g. `import { Search01Icon } from '@hugeicons/core-free-icons'`).
|
|
658
|
+
*
|
|
659
|
+
* Prefer this wrapper over `hugeicons-icon` directly so project defaults
|
|
660
|
+
* (stroke width, `currentColor`, accessibility, RTL mirroring) stay consistent.
|
|
661
|
+
*
|
|
662
|
+
* @example
|
|
663
|
+
* ```html
|
|
664
|
+
* <!-- decorative -->
|
|
665
|
+
* <sce-hugeicon [icon]="Search01Icon" [size]="20" />
|
|
666
|
+
*
|
|
667
|
+
* <!-- meaningful (icon-only button) -->
|
|
668
|
+
* <button type="button" [attr.aria-label]="'COMMON.DELETE' | translate">
|
|
669
|
+
* <sce-hugeicon [icon]="Delete02Icon" [label]="'COMMON.DELETE' | translate" />
|
|
670
|
+
* </button>
|
|
671
|
+
*
|
|
672
|
+
* <!-- directional glyph mirrored in Arabic -->
|
|
673
|
+
* <sce-hugeicon [icon]="ArrowRight01Icon" [flipRtl]="true" />
|
|
674
|
+
* ```
|
|
675
|
+
*/
|
|
676
|
+
class SceHugeicon {
|
|
677
|
+
/** Icon data to render. Import the constant from `@hugeicons/core-free-icons` (e.g. `Search01Icon`). */
|
|
678
|
+
icon = input.required(...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
679
|
+
/** Alternate icon revealed when `showAlt` is true — swap without remounting to keep sizing stable. */
|
|
680
|
+
altIcon = input(null, ...(ngDevMode ? [{ debugName: "altIcon" }] : /* istanbul ignore next */ []));
|
|
681
|
+
/** Show `altIcon` instead of `icon` (e.g. filled/active state, theme toggles). */
|
|
682
|
+
showAlt = input(false, ...(ngDevMode ? [{ debugName: "showAlt" }] : /* istanbul ignore next */ []));
|
|
683
|
+
/** Square size in pixels. */
|
|
684
|
+
size = input(24, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
685
|
+
/** Any CSS colour. Defaults to `currentColor` so the glyph inherits the surrounding text colour. */
|
|
686
|
+
color = input('currentColor', ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
|
|
687
|
+
/** Stroke thickness for outline glyphs. Keep consistent across a surface. */
|
|
688
|
+
strokeWidth = input(1.5, ...(ngDevMode ? [{ debugName: "strokeWidth" }] : /* istanbul ignore next */ []));
|
|
689
|
+
/**
|
|
690
|
+
* Keep the stroke visually constant when the icon is scaled up/down, instead of scaling with it
|
|
691
|
+
* (mirrors Hugeicons' `absoluteStrokeWidth`). Useful for very large or very small glyphs.
|
|
692
|
+
*/
|
|
693
|
+
absoluteStrokeWidth = input(false, ...(ngDevMode ? [{ debugName: "absoluteStrokeWidth" }] : /* istanbul ignore next */ []));
|
|
694
|
+
/**
|
|
695
|
+
* Continuously rotate the glyph — for spinner/loading affordances. Honours
|
|
696
|
+
* `prefers-reduced-motion` (the animation is disabled when the user asks for reduced motion).
|
|
697
|
+
*/
|
|
698
|
+
spin = input(false, ...(ngDevMode ? [{ debugName: "spin" }] : /* istanbul ignore next */ []));
|
|
699
|
+
/**
|
|
700
|
+
* Accessible name. Set for a *meaningful* icon (renders `role="img"` + `aria-label`);
|
|
701
|
+
* leave unset for a *decorative* icon (rendered `aria-hidden`, so screen readers skip it).
|
|
702
|
+
*/
|
|
703
|
+
label = input(null, ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
704
|
+
/** Mirror horizontally under RTL. Use only for directional glyphs (arrows, chevrons, carets). */
|
|
705
|
+
flipRtl = input(false, ...(ngDevMode ? [{ debugName: "flipRtl" }] : /* istanbul ignore next */ []));
|
|
706
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceHugeicon, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
707
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: SceHugeicon, isStandalone: true, selector: "sce-hugeicon", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: true, transformFunction: null }, altIcon: { classPropertyName: "altIcon", publicName: "altIcon", isSignal: true, isRequired: false, transformFunction: null }, showAlt: { classPropertyName: "showAlt", publicName: "showAlt", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, absoluteStrokeWidth: { classPropertyName: "absoluteStrokeWidth", publicName: "absoluteStrokeWidth", isSignal: true, isRequired: false, transformFunction: null }, spin: { classPropertyName: "spin", publicName: "spin", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, flipRtl: { classPropertyName: "flipRtl", publicName: "flipRtl", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.sce-hugeicon--flip-rtl": "flipRtl()", "class.sce-hugeicon--spin": "spin()", "attr.role": "label() ? 'img' : null", "attr.aria-label": "label() || null", "attr.aria-hidden": "label() ? null : \"true\"" }, classAttribute: "sce-hugeicon" }, hostDirectives: [{ directive: i1.SceVariantDirective, inputs: ["sceVariant", "sceVariant", "sceDensity", "sceDensity"] }], ngImport: i0, template: "<span class=\"sce-hugeicon__glyph\" [class.sce-hugeicon__glyph--flip]=\"flipRtl()\">\n <hugeicons-icon\n [icon]=\"icon()\"\n [altIcon]=\"altIcon() ?? icon()\"\n [showAlt]=\"showAlt()\"\n [size]=\"size()\"\n [color]=\"color()\"\n [strokeWidth]=\"strokeWidth()\"\n [absoluteStrokeWidth]=\"absoluteStrokeWidth()\"\n />\n</span>\n", styles: [":host{display:inline-flex;align-items:center;justify-content:center;line-height:0;color:inherit}.sce-hugeicon__glyph{display:inline-flex;align-items:center;justify-content:center}:host-context([dir=rtl]) .sce-hugeicon__glyph--flip{transform:scaleX(-1)}@keyframes sce-hugeicon-spin{to{transform:rotate(360deg)}}:host(.sce-hugeicon--spin) .sce-hugeicon__glyph{animation:sce-hugeicon-spin 1s linear infinite}@media(prefers-reduced-motion:reduce){:host(.sce-hugeicon--spin) .sce-hugeicon__glyph{animation:none}}\n"], dependencies: [{ kind: "component", type: HugeiconsIconComponent, selector: "hugeicons-icon", inputs: ["size", "strokeWidth", "absoluteStrokeWidth", "icon", "altIcon", "color", "class", "showAlt"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
708
|
+
}
|
|
709
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceHugeicon, decorators: [{
|
|
710
|
+
type: Component,
|
|
711
|
+
args: [{ hostDirectives: [{ directive: SceVariantDirective, inputs: ['sceVariant', 'sceDensity'] }], selector: 'sce-hugeicon', standalone: true, imports: [HugeiconsIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
712
|
+
class: 'sce-hugeicon',
|
|
713
|
+
'[class.sce-hugeicon--flip-rtl]': 'flipRtl()',
|
|
714
|
+
'[class.sce-hugeicon--spin]': 'spin()',
|
|
715
|
+
'[attr.role]': "label() ? 'img' : null",
|
|
716
|
+
'[attr.aria-label]': 'label() || null',
|
|
717
|
+
'[attr.aria-hidden]': 'label() ? null : "true"',
|
|
718
|
+
}, template: "<span class=\"sce-hugeicon__glyph\" [class.sce-hugeicon__glyph--flip]=\"flipRtl()\">\n <hugeicons-icon\n [icon]=\"icon()\"\n [altIcon]=\"altIcon() ?? icon()\"\n [showAlt]=\"showAlt()\"\n [size]=\"size()\"\n [color]=\"color()\"\n [strokeWidth]=\"strokeWidth()\"\n [absoluteStrokeWidth]=\"absoluteStrokeWidth()\"\n />\n</span>\n", styles: [":host{display:inline-flex;align-items:center;justify-content:center;line-height:0;color:inherit}.sce-hugeicon__glyph{display:inline-flex;align-items:center;justify-content:center}:host-context([dir=rtl]) .sce-hugeicon__glyph--flip{transform:scaleX(-1)}@keyframes sce-hugeicon-spin{to{transform:rotate(360deg)}}:host(.sce-hugeicon--spin) .sce-hugeicon__glyph{animation:sce-hugeicon-spin 1s linear infinite}@media(prefers-reduced-motion:reduce){:host(.sce-hugeicon--spin) .sce-hugeicon__glyph{animation:none}}\n"] }]
|
|
719
|
+
}], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: true }] }], altIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "altIcon", required: false }] }], showAlt: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAlt", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], absoluteStrokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "absoluteStrokeWidth", required: false }] }], spin: [{ type: i0.Input, args: [{ isSignal: true, alias: "spin", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], flipRtl: [{ type: i0.Input, args: [{ isSignal: true, alias: "flipRtl", required: false }] }] } });
|
|
720
|
+
|
|
721
|
+
/** Every `SceSelectFieldVariant`, in gallery order. */
|
|
722
|
+
const SCE_SELECT_FIELD_VARIANTS = [
|
|
723
|
+
'default',
|
|
724
|
+
'outlined',
|
|
725
|
+
'filled',
|
|
726
|
+
'underline',
|
|
727
|
+
'soft',
|
|
728
|
+
'bordered',
|
|
729
|
+
'elevated',
|
|
730
|
+
'compact',
|
|
731
|
+
'minimal',
|
|
732
|
+
'pill',
|
|
733
|
+
'addon',
|
|
734
|
+
];
|
|
735
|
+
class SceSelectFieldComponent {
|
|
736
|
+
dropdownIcon = ArrowDown01Icon;
|
|
737
|
+
/** Visual treatment. */
|
|
738
|
+
variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
739
|
+
mirrorLabelAsPlaceholder = inject(SCE_MIRROR_LABEL_AS_PLACEHOLDER, {
|
|
740
|
+
optional: true,
|
|
741
|
+
});
|
|
742
|
+
id = input.required(...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
743
|
+
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
744
|
+
options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
745
|
+
group = input(false, ...(ngDevMode ? [{ debugName: "group" }] : /* istanbul ignore next */ []));
|
|
746
|
+
optionGroupLabel = input('label', ...(ngDevMode ? [{ debugName: "optionGroupLabel" }] : /* istanbul ignore next */ []));
|
|
747
|
+
optionGroupChildren = input('items', ...(ngDevMode ? [{ debugName: "optionGroupChildren" }] : /* istanbul ignore next */ []));
|
|
748
|
+
value = input(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
749
|
+
placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
750
|
+
required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
|
|
751
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
752
|
+
helpText = input(...(ngDevMode ? [undefined, { debugName: "helpText" }] : /* istanbul ignore next */ []));
|
|
753
|
+
error = input(...(ngDevMode ? [undefined, { debugName: "error" }] : /* istanbul ignore next */ []));
|
|
754
|
+
filter = input(false, ...(ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []));
|
|
755
|
+
/**
|
|
756
|
+
* Caps characters in the dropdown filter/search box (PrimeNG has no built-in
|
|
757
|
+
* `maxlength` on that input). Applied when the overlay opens.
|
|
758
|
+
*/
|
|
759
|
+
filterMaxLength = input(...(ngDevMode ? [undefined, { debugName: "filterMaxLength" }] : /* istanbul ignore next */ []));
|
|
760
|
+
showClear = input(false, ...(ngDevMode ? [{ debugName: "showClear" }] : /* istanbul ignore next */ []));
|
|
761
|
+
/**
|
|
762
|
+
* Explicit text direction. Default `null` infers from the content/document
|
|
763
|
+
* locale — a hardcoded `'rtl'` default rendered every unbound select RTL on
|
|
764
|
+
* English pages (and flipped direction when a disabled select became enabled).
|
|
765
|
+
*/
|
|
766
|
+
dir = input(null, ...(ngDevMode ? [{ debugName: "dir" }] : /* istanbul ignore next */ []));
|
|
767
|
+
validationMode = input('onBlur', ...(ngDevMode ? [{ debugName: "validationMode" }] : /* istanbul ignore next */ []));
|
|
768
|
+
validate = input(...(ngDevMode ? [undefined, { debugName: "validate" }] : /* istanbul ignore next */ []));
|
|
769
|
+
validationMessages = input({}, ...(ngDevMode ? [{ debugName: "validationMessages" }] : /* istanbul ignore next */ []));
|
|
770
|
+
fieldClassName = input('', ...(ngDevMode ? [{ debugName: "fieldClassName" }] : /* istanbul ignore next */ []));
|
|
771
|
+
labelClassName = input('', ...(ngDevMode ? [{ debugName: "labelClassName" }] : /* istanbul ignore next */ []));
|
|
772
|
+
inputClassName = input('', ...(ngDevMode ? [{ debugName: "inputClassName" }] : /* istanbul ignore next */ []));
|
|
773
|
+
panelClassName = input('', ...(ngDevMode ? [{ debugName: "panelClassName" }] : /* istanbul ignore next */ []));
|
|
774
|
+
/** When true, hides the trigger border. Default false so `.p-select` keeps a visible border. */
|
|
775
|
+
borderless = input(false, ...(ngDevMode ? [{ debugName: "borderless" }] : /* istanbul ignore next */ []));
|
|
776
|
+
filterPlaceholder = input(...(ngDevMode ? [undefined, { debugName: "filterPlaceholder" }] : /* istanbul ignore next */ []));
|
|
777
|
+
showError = input(true, ...(ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []));
|
|
778
|
+
valueChange = output();
|
|
779
|
+
/** Optional wrapper class. Do not use `root` — file-upload and other None-encap components leak a global `.root`. */
|
|
780
|
+
containerClassName = input('', ...(ngDevMode ? [{ debugName: "containerClassName" }] : /* istanbul ignore next */ []));
|
|
781
|
+
/**
|
|
782
|
+
* Change this value to clear the field's interaction state (touched / blurred).
|
|
783
|
+
* Bump it when a parent form resets programmatically, so a cleared required
|
|
784
|
+
* field doesn't instantly show its "required" error next to a success banner.
|
|
785
|
+
*/
|
|
786
|
+
resetToken = input(undefined, ...(ngDevMode ? [{ debugName: "resetToken" }] : /* istanbul ignore next */ []));
|
|
787
|
+
/** True after any user value change. Prevents required errors on open-and-close. */
|
|
788
|
+
dirty = signal(false, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
|
|
789
|
+
/** True after any interaction (change or blur). Drives `'onTouched'` / `'touched'` mode. */
|
|
790
|
+
touched = signal(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
|
|
791
|
+
/** True only after the first blur. Drives the pure `'onBlur'` mode. */
|
|
792
|
+
blurredOnce = signal(false, ...(ngDevMode ? [{ debugName: "blurredOnce" }] : /* istanbul ignore next */ []));
|
|
793
|
+
clearInteractionOnReset = effect(() => {
|
|
794
|
+
this.resetToken();
|
|
795
|
+
this.dirty.set(false);
|
|
796
|
+
this.touched.set(false);
|
|
797
|
+
this.blurredOnce.set(false);
|
|
798
|
+
}, ...(ngDevMode ? [{ debugName: "clearInteractionOnReset" }] : /* istanbul ignore next */ []));
|
|
799
|
+
requiredMessage = computed(() => this.validationMessages()['required'] ?? (this.dir() === 'rtl' ? 'هذا الحقل مطلوب' : 'This field is required'), ...(ngDevMode ? [{ debugName: "requiredMessage" }] : /* istanbul ignore next */ []));
|
|
800
|
+
validationError = computed(() => {
|
|
801
|
+
const externalError = this.error();
|
|
802
|
+
if (externalError) {
|
|
803
|
+
return externalError;
|
|
804
|
+
}
|
|
805
|
+
const mode = this.validationMode();
|
|
806
|
+
if (mode === 'never') {
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
809
|
+
if (mode === 'onBlur' && (!this.blurredOnce() || !this.dirty())) {
|
|
810
|
+
return null;
|
|
811
|
+
}
|
|
812
|
+
if ((mode === 'onTouched' || mode === 'touched') && (!this.touched() || !this.dirty())) {
|
|
813
|
+
return null;
|
|
814
|
+
}
|
|
815
|
+
const value = this.boundValue();
|
|
816
|
+
if (this.required() && value == null) {
|
|
817
|
+
return this.requiredMessage();
|
|
818
|
+
}
|
|
819
|
+
const validateFn = this.validate();
|
|
820
|
+
if (validateFn) {
|
|
821
|
+
return validateFn(value);
|
|
822
|
+
}
|
|
823
|
+
return null;
|
|
824
|
+
}, ...(ngDevMode ? [{ debugName: "validationError" }] : /* istanbul ignore next */ []));
|
|
825
|
+
displayError = computed(() => (this.showError() ? this.validationError() : null), ...(ngDevMode ? [{ debugName: "displayError" }] : /* istanbul ignore next */ []));
|
|
826
|
+
resolvedPlaceholder = computed(() => {
|
|
827
|
+
const explicit = this.placeholder();
|
|
828
|
+
if (explicit) {
|
|
829
|
+
return explicit;
|
|
830
|
+
}
|
|
831
|
+
if (this.mirrorLabelAsPlaceholder === false) {
|
|
832
|
+
return '';
|
|
833
|
+
}
|
|
834
|
+
return this.label();
|
|
835
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedPlaceholder" }] : /* istanbul ignore next */ []));
|
|
836
|
+
/**
|
|
837
|
+
* Local mirror so clear updates the UI immediately (before the parent round-trips).
|
|
838
|
+
* Empty/`undefined` stay `null` — PrimeNG keeps the clear icon when value is `''`.
|
|
839
|
+
*/
|
|
840
|
+
boundValue = signal(null, ...(ngDevMode ? [{ debugName: "boundValue" }] : /* istanbul ignore next */ []));
|
|
841
|
+
syncBoundValueFromInput = effect(() => {
|
|
842
|
+
this.boundValue.set(this.normalizeValue(this.value()));
|
|
843
|
+
}, ...(ngDevMode ? [{ debugName: "syncBoundValueFromInput" }] : /* istanbul ignore next */ []));
|
|
844
|
+
selectedLabel = computed(() => {
|
|
845
|
+
const current = this.boundValue();
|
|
846
|
+
if (current == null) {
|
|
847
|
+
return '';
|
|
848
|
+
}
|
|
849
|
+
return this.options().find(option => String(option.value) === String(current))?.label ?? '';
|
|
850
|
+
}, ...(ngDevMode ? [{ debugName: "selectedLabel" }] : /* istanbul ignore next */ []));
|
|
851
|
+
/** Clear only when there is a real, matched selection — never for placeholder/orphan ids. */
|
|
852
|
+
clearVisible = computed(() => {
|
|
853
|
+
if (!this.showClear()) {
|
|
854
|
+
return false;
|
|
855
|
+
}
|
|
856
|
+
const current = this.boundValue();
|
|
857
|
+
if (current == null) {
|
|
858
|
+
return false;
|
|
859
|
+
}
|
|
860
|
+
return this.options().some(option => String(option.value) === String(current));
|
|
861
|
+
}, ...(ngDevMode ? [{ debugName: "clearVisible" }] : /* istanbul ignore next */ []));
|
|
862
|
+
selectRef = viewChild(Select, ...(ngDevMode ? [{ debugName: "selectRef" }] : /* istanbul ignore next */ []));
|
|
863
|
+
/**
|
|
864
|
+
* Body-portaled option panels must sit above `sce-popup` / `sce-dialog-shell`
|
|
865
|
+
* (`--z-modal` 2100). `OVERLAY_BASE_Z_INDEX` (1200) paints the menu under the mask.
|
|
866
|
+
*/
|
|
867
|
+
overlayOptions = {
|
|
868
|
+
autoZIndex: true,
|
|
869
|
+
baseZIndex: DIALOG_SELECT_OVERLAY_Z_INDEX,
|
|
870
|
+
};
|
|
871
|
+
resolvedDir = computed(() => {
|
|
872
|
+
const explicit = this.dir();
|
|
873
|
+
if (this.disabled() || explicit === null) {
|
|
874
|
+
return inferInputTextDirection(this.selectedLabel(), { placeholder: this.resolvedPlaceholder() }, { preferDocumentLocale: true });
|
|
875
|
+
}
|
|
876
|
+
return explicit;
|
|
877
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedDir" }] : /* istanbul ignore next */ []));
|
|
878
|
+
onBlur() {
|
|
879
|
+
this.touched.set(true);
|
|
880
|
+
this.blurredOnce.set(true);
|
|
881
|
+
}
|
|
882
|
+
/** PrimeNG Select filter input has no maxlength input — set it on open. */
|
|
883
|
+
onOverlayShow() {
|
|
884
|
+
const max = this.filterMaxLength();
|
|
885
|
+
if (max == null || max <= 0) {
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
const filterEl = this.selectRef()?.filterViewChild?.nativeElement;
|
|
889
|
+
if (filterEl) {
|
|
890
|
+
filterEl.setAttribute('maxlength', String(max));
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
onValueChange(next) {
|
|
894
|
+
const normalized = this.normalizeValue(next);
|
|
895
|
+
this.boundValue.set(normalized);
|
|
896
|
+
this.dirty.set(true);
|
|
897
|
+
if (!this.touched()) {
|
|
898
|
+
this.touched.set(true);
|
|
899
|
+
}
|
|
900
|
+
this.valueChange.emit(normalized);
|
|
901
|
+
}
|
|
902
|
+
/** PrimeNG clear path — force null even if ngModelChange is skipped/racy. */
|
|
903
|
+
onClear() {
|
|
904
|
+
this.boundValue.set(null);
|
|
905
|
+
this.dirty.set(true);
|
|
906
|
+
if (!this.touched()) {
|
|
907
|
+
this.touched.set(true);
|
|
908
|
+
}
|
|
909
|
+
this.valueChange.emit(null);
|
|
910
|
+
}
|
|
911
|
+
normalizeValue(value) {
|
|
912
|
+
return value === '' || value === undefined || value === null ? null : value;
|
|
913
|
+
}
|
|
914
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceSelectFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
915
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.17", type: SceSelectFieldComponent, isStandalone: true, selector: "sce-select-field", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, group: { classPropertyName: "group", publicName: "group", isSignal: true, isRequired: false, transformFunction: null }, optionGroupLabel: { classPropertyName: "optionGroupLabel", publicName: "optionGroupLabel", isSignal: true, isRequired: false, transformFunction: null }, optionGroupChildren: { classPropertyName: "optionGroupChildren", publicName: "optionGroupChildren", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, helpText: { classPropertyName: "helpText", publicName: "helpText", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, filterMaxLength: { classPropertyName: "filterMaxLength", publicName: "filterMaxLength", isSignal: true, isRequired: false, transformFunction: null }, showClear: { classPropertyName: "showClear", publicName: "showClear", isSignal: true, isRequired: false, transformFunction: null }, dir: { classPropertyName: "dir", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, validationMode: { classPropertyName: "validationMode", publicName: "validationMode", isSignal: true, isRequired: false, transformFunction: null }, validate: { classPropertyName: "validate", publicName: "validate", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, fieldClassName: { classPropertyName: "fieldClassName", publicName: "fieldClassName", isSignal: true, isRequired: false, transformFunction: null }, labelClassName: { classPropertyName: "labelClassName", publicName: "labelClassName", isSignal: true, isRequired: false, transformFunction: null }, inputClassName: { classPropertyName: "inputClassName", publicName: "inputClassName", isSignal: true, isRequired: false, transformFunction: null }, panelClassName: { classPropertyName: "panelClassName", publicName: "panelClassName", isSignal: true, isRequired: false, transformFunction: null }, borderless: { classPropertyName: "borderless", publicName: "borderless", isSignal: true, isRequired: false, transformFunction: null }, filterPlaceholder: { classPropertyName: "filterPlaceholder", publicName: "filterPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, showError: { classPropertyName: "showError", publicName: "showError", isSignal: true, isRequired: false, transformFunction: null }, containerClassName: { classPropertyName: "containerClassName", publicName: "containerClassName", isSignal: true, isRequired: false, transformFunction: null }, resetToken: { classPropertyName: "resetToken", publicName: "resetToken", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, host: { properties: { "class.sce-select-field--default": "variant() === \"default\"", "class.sce-select-field--outlined": "variant() === \"outlined\"", "class.sce-select-field--filled": "variant() === \"filled\"", "class.sce-select-field--underline": "variant() === \"underline\"", "class.sce-select-field--soft": "variant() === \"soft\"", "class.sce-select-field--bordered": "variant() === \"bordered\"", "class.sce-select-field--elevated": "variant() === \"elevated\"", "class.sce-select-field--compact": "variant() === \"compact\"", "class.sce-select-field--minimal": "variant() === \"minimal\"", "class.sce-select-field--pill": "variant() === \"pill\"", "class.sce-select-field--addon": "variant() === \"addon\"", "class.sce-select-field--locked": "disabled()" }, classAttribute: "sce-select-field" }, viewQueries: [{ propertyName: "selectRef", first: true, predicate: Select, descendants: true, isSignal: true }], hostDirectives: [{ directive: i1.SceVariantDirective, inputs: ["sceVariant", "sceVariant", "sceDensity", "sceDensity"] }], ngImport: i0, template: "<div class=\"sce-select-field__wrap\" [class]=\"containerClassName() || null\" [attr.dir]=\"resolvedDir()\">\n <sce-field\n [id]=\"id()\"\n [label]=\"label()\"\n [required]=\"required()\"\n [helpText]=\"helpText()\"\n [error]=\"displayError()\"\n [labelClassName]=\"labelClassName()\"\n [fieldClassName]=\"fieldClassName()\"\n >\n <p-select\n [inputId]=\"id()\"\n [ariaLabel]=\"label() || resolvedPlaceholder()\"\n [options]=\"options()\"\n optionLabel=\"label\"\n optionValue=\"value\"\n optionDisabled=\"disabled\"\n [group]=\"group()\"\n [optionGroupLabel]=\"optionGroupLabel()\"\n [optionGroupChildren]=\"optionGroupChildren()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [disabled]=\"disabled()\"\n [filter]=\"filter()\"\n [filterPlaceholder]=\"filterPlaceholder() ?? ''\"\n [showClear]=\"clearVisible()\"\n [ngModel]=\"boundValue()\"\n (ngModelChange)=\"onValueChange($event)\"\n (onClear)=\"onClear()\"\n (onBlur)=\"onBlur()\"\n (onShow)=\"onOverlayShow()\"\n [class.p-invalid]=\"displayError()\"\n [class.sce-select--borderless]=\"borderless()\"\n [styleClass]=\"'w-full ' + inputClassName()\"\n [panelStyleClass]=\"panelClassName()\"\n dropdownIcon=\"pi pi-chevron-down\"\n [overlayOptions]=\"overlayOptions\"\n appendTo=\"body\"\n >\n <ng-template #dropdownicon>\n <sce-hugeicon [icon]=\"dropdownIcon\" [size]=\"16\" />\n </ng-template>\n </p-select>\n </sce-field>\n</div>\n", styles: ["@charset \"UTF-8\";.sce-select-field .p-select{position:relative;width:100%;height:var(--size-40);min-height:var(--size-40);max-height:var(--size-40);border-radius:var(--radius-lg);border:1px solid var(--color-input-border, var(--color-neutral-200));box-sizing:border-box;transition:border-color .15s ease;display:flex;align-items:center}.sce-select-field .p-select:after{content:\"\";position:absolute;inset-block-end:0;inset-inline:50%;z-index:1;height:2px;background:var(--color-neutral-950);border-end-start-radius:var(--radius-sm);border-end-end-radius:var(--radius-sm);pointer-events:none;transition:inset-inline .15s ease}.sce-select-field .p-select:focus-within:not(:has(:disabled)):not(:has([readonly])):not(.p-disabled):not(.disabled):not(.readonly):after,.sce-select-field .p-select:active:not(:has(:disabled)):not(:has([readonly])):not(.p-disabled):not(.disabled):not(.readonly):after{inset-inline:0}.sce-select-field .p-select.p-focus:not(.p-disabled):after{inset-inline:0}.sce-select-field .p-select:not(.sce-select--borderless){border:1px solid var(--color-input-border, var(--color-neutral-200))}.sce-select-field .p-select:not(.p-disabled):hover{border-color:var(--field-border-hovered)}.sce-select-field .p-select.sce-select--borderless:not(.p-invalid){border-color:transparent;border-radius:var(--radius-sm)}.sce-select-field .p-select.p-select.p-invalid{border-color:var(--color-input-border, var(--color-neutral-200))}.sce-select-field .p-select.p-select.p-invalid:not(.p-disabled).p-focus{border-color:var(--color-input-border, var(--color-neutral-200));box-shadow:none}.sce-select-field .p-select .p-select-dropdown{position:relative;inset:auto;display:flex;align-items:center;justify-content:center;align-self:stretch;flex-shrink:0;width:var(--space-40);margin:0;background:transparent;border:none;color:var(--form-field-icon-color, var(--form-field-text-placeholder))}.sce-select-field .p-select .p-select-dropdown-icon,.sce-select-field .p-select .p-select-dropdown .pi{font-size:var(--font-size-xs);line-height:1;display:inline-flex;align-items:center;justify-content:center}.sce-select-field .p-select .p-select-clear-icon{position:relative;inset:auto;flex-shrink:0;align-self:center;margin-inline-start:0;margin-inline-end:var(--space-4);color:var(--form-field-icon-color, var(--form-field-text-placeholder))}.sce-select-field .p-select .p-select-label{display:flex;align-items:center;flex:1 1 auto;min-width:0;direction:inherit;text-align:start;justify-content:flex-start;width:auto;margin:0;font-family:var(--font-family-arabic-primary);font-size:var(--font-size-md);color:var(--details-value-color);padding-block:0;padding-inline:var(--space-14)}.sce-select-field .sce-select-field__wrap[dir=rtl] .p-select .p-select-label,.sce-select-field .sce-select-field__wrap[dir=rtl] .p-select .p-select-label.p-placeholder{direction:rtl;text-align:start}.sce-select-field .sce-select-field__wrap[dir=ltr] .p-select .p-select-label,.sce-select-field .sce-select-field__wrap[dir=ltr] .p-select .p-select-label.p-placeholder{direction:ltr;text-align:start}.sce-select-field .p-select .p-select-label.p-placeholder{color:var(--form-input-placeholder-color);opacity:1}sce-select-field.sce-select-field--locked .sce-select-field__wrap .p-select.p-disabled{opacity:1;background-color:var(--form-input-disabled-bg, var(--pdf-field-bg-alt))}sce-select-field.sce-select-field--locked .sce-select-field__wrap .p-select.p-disabled .p-select-label:not(.p-placeholder){color:var(--form-input-disabled-text, var(--text-text-disabled))}sce-select-field.sce-select-field--locked .sce-select-field__wrap[dir=rtl] .p-select.p-disabled .p-select-label:not(.p-placeholder){direction:rtl;text-align:start;justify-content:flex-start}sce-select-field.sce-select-field--locked .sce-select-field__wrap[dir=ltr] .p-select.p-disabled .p-select-label:not(.p-placeholder){direction:ltr;text-align:start;justify-content:flex-start}.sce-select-field .p-select-overlay,.p-select-overlay{z-index:var(--z-select-overlay);border-radius:var(--radius-sm);overflow:hidden;background:var(--dropdown-panel-bg);box-shadow:var(--dropdown-panel-shadow);color:var(--details-value-color)}.p-select-overlay .p-select-list .p-select-option{transition:background-color .12s ease;text-align:start}.p-select-overlay .p-select-list .p-select-option-group{font-weight:var(--font-weight-bold);font-size:var(--font-size-xs);color:var(--color-text-secondary);background-color:var(--color-surface-muted, var(--color-neutral-50));padding-block:var(--space-6);padding-inline:var(--space-12);-webkit-user-select:none;user-select:none;cursor:default;border-block-end:1px solid var(--color-border-light, var(--color-neutral-100))}.p-select-overlay .p-select-header .p-select-filter.p-select-filter{margin-inline-end:unset}.sce-select-field--outlined{border:1px solid var(--color-border-light);background:transparent}.sce-select-field--filled{background:var(--color-surface-muted)}.sce-select-field--underline{border:0;border-block-end:1px solid var(--color-border-light);border-radius:0}.sce-select-field--soft{background:var(--color-brand-primary-25)}.sce-select-field--bordered{border-inline-start:var(--space-3) solid var(--color-brand-primary-600)}.sce-select-field--elevated{border-radius:var(--radius-lg);background:var(--color-surface);box-shadow:var(--shadow-card-soft-subtle)}.sce-select-field--compact{padding-block:var(--space-4);font-size:var(--font-size-sm)}.sce-select-field--minimal{background:transparent;border:0;box-shadow:none}.sce-select-field--pill{border-radius:var(--radius-full)}.sce-select-field--addon .p-select{overflow:hidden}.sce-select-field--addon .p-select .p-select-dropdown{width:var(--size-53);isolation:isolate}.sce-select-field--addon .p-select .p-select-dropdown:before{content:\"\";position:absolute;inset:0;z-index:-1;background:var(--color-neutral-100)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: SceFieldComponent, selector: "sce-field", inputs: ["id", "label", "required", "hideLabel", "helpText", "error", "labelClassName", "fieldClassName"] }, { kind: "component", type: SceHugeicon, selector: "sce-hugeicon", inputs: ["icon", "altIcon", "showAlt", "size", "color", "strokeWidth", "absoluteStrokeWidth", "spin", "label", "flipRtl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
916
|
+
}
|
|
917
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceSelectFieldComponent, decorators: [{
|
|
918
|
+
type: Component,
|
|
919
|
+
args: [{ hostDirectives: [{ directive: SceVariantDirective, inputs: ['sceVariant', 'sceDensity'] }], selector: 'sce-select-field', standalone: true, imports: [FormsModule, Select, SceFieldComponent, SceHugeicon], encapsulation: ViewEncapsulation.None, host: {
|
|
920
|
+
class: 'sce-select-field',
|
|
921
|
+
'[class.sce-select-field--default]': 'variant() === "default"',
|
|
922
|
+
'[class.sce-select-field--outlined]': 'variant() === "outlined"',
|
|
923
|
+
'[class.sce-select-field--filled]': 'variant() === "filled"',
|
|
924
|
+
'[class.sce-select-field--underline]': 'variant() === "underline"',
|
|
925
|
+
'[class.sce-select-field--soft]': 'variant() === "soft"',
|
|
926
|
+
'[class.sce-select-field--bordered]': 'variant() === "bordered"',
|
|
927
|
+
'[class.sce-select-field--elevated]': 'variant() === "elevated"',
|
|
928
|
+
'[class.sce-select-field--compact]': 'variant() === "compact"',
|
|
929
|
+
'[class.sce-select-field--minimal]': 'variant() === "minimal"',
|
|
930
|
+
'[class.sce-select-field--pill]': 'variant() === "pill"',
|
|
931
|
+
'[class.sce-select-field--addon]': 'variant() === "addon"',
|
|
932
|
+
'[class.sce-select-field--locked]': 'disabled()',
|
|
933
|
+
}, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"sce-select-field__wrap\" [class]=\"containerClassName() || null\" [attr.dir]=\"resolvedDir()\">\n <sce-field\n [id]=\"id()\"\n [label]=\"label()\"\n [required]=\"required()\"\n [helpText]=\"helpText()\"\n [error]=\"displayError()\"\n [labelClassName]=\"labelClassName()\"\n [fieldClassName]=\"fieldClassName()\"\n >\n <p-select\n [inputId]=\"id()\"\n [ariaLabel]=\"label() || resolvedPlaceholder()\"\n [options]=\"options()\"\n optionLabel=\"label\"\n optionValue=\"value\"\n optionDisabled=\"disabled\"\n [group]=\"group()\"\n [optionGroupLabel]=\"optionGroupLabel()\"\n [optionGroupChildren]=\"optionGroupChildren()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [disabled]=\"disabled()\"\n [filter]=\"filter()\"\n [filterPlaceholder]=\"filterPlaceholder() ?? ''\"\n [showClear]=\"clearVisible()\"\n [ngModel]=\"boundValue()\"\n (ngModelChange)=\"onValueChange($event)\"\n (onClear)=\"onClear()\"\n (onBlur)=\"onBlur()\"\n (onShow)=\"onOverlayShow()\"\n [class.p-invalid]=\"displayError()\"\n [class.sce-select--borderless]=\"borderless()\"\n [styleClass]=\"'w-full ' + inputClassName()\"\n [panelStyleClass]=\"panelClassName()\"\n dropdownIcon=\"pi pi-chevron-down\"\n [overlayOptions]=\"overlayOptions\"\n appendTo=\"body\"\n >\n <ng-template #dropdownicon>\n <sce-hugeicon [icon]=\"dropdownIcon\" [size]=\"16\" />\n </ng-template>\n </p-select>\n </sce-field>\n</div>\n", styles: ["@charset \"UTF-8\";.sce-select-field .p-select{position:relative;width:100%;height:var(--size-40);min-height:var(--size-40);max-height:var(--size-40);border-radius:var(--radius-lg);border:1px solid var(--color-input-border, var(--color-neutral-200));box-sizing:border-box;transition:border-color .15s ease;display:flex;align-items:center}.sce-select-field .p-select:after{content:\"\";position:absolute;inset-block-end:0;inset-inline:50%;z-index:1;height:2px;background:var(--color-neutral-950);border-end-start-radius:var(--radius-sm);border-end-end-radius:var(--radius-sm);pointer-events:none;transition:inset-inline .15s ease}.sce-select-field .p-select:focus-within:not(:has(:disabled)):not(:has([readonly])):not(.p-disabled):not(.disabled):not(.readonly):after,.sce-select-field .p-select:active:not(:has(:disabled)):not(:has([readonly])):not(.p-disabled):not(.disabled):not(.readonly):after{inset-inline:0}.sce-select-field .p-select.p-focus:not(.p-disabled):after{inset-inline:0}.sce-select-field .p-select:not(.sce-select--borderless){border:1px solid var(--color-input-border, var(--color-neutral-200))}.sce-select-field .p-select:not(.p-disabled):hover{border-color:var(--field-border-hovered)}.sce-select-field .p-select.sce-select--borderless:not(.p-invalid){border-color:transparent;border-radius:var(--radius-sm)}.sce-select-field .p-select.p-select.p-invalid{border-color:var(--color-input-border, var(--color-neutral-200))}.sce-select-field .p-select.p-select.p-invalid:not(.p-disabled).p-focus{border-color:var(--color-input-border, var(--color-neutral-200));box-shadow:none}.sce-select-field .p-select .p-select-dropdown{position:relative;inset:auto;display:flex;align-items:center;justify-content:center;align-self:stretch;flex-shrink:0;width:var(--space-40);margin:0;background:transparent;border:none;color:var(--form-field-icon-color, var(--form-field-text-placeholder))}.sce-select-field .p-select .p-select-dropdown-icon,.sce-select-field .p-select .p-select-dropdown .pi{font-size:var(--font-size-xs);line-height:1;display:inline-flex;align-items:center;justify-content:center}.sce-select-field .p-select .p-select-clear-icon{position:relative;inset:auto;flex-shrink:0;align-self:center;margin-inline-start:0;margin-inline-end:var(--space-4);color:var(--form-field-icon-color, var(--form-field-text-placeholder))}.sce-select-field .p-select .p-select-label{display:flex;align-items:center;flex:1 1 auto;min-width:0;direction:inherit;text-align:start;justify-content:flex-start;width:auto;margin:0;font-family:var(--font-family-arabic-primary);font-size:var(--font-size-md);color:var(--details-value-color);padding-block:0;padding-inline:var(--space-14)}.sce-select-field .sce-select-field__wrap[dir=rtl] .p-select .p-select-label,.sce-select-field .sce-select-field__wrap[dir=rtl] .p-select .p-select-label.p-placeholder{direction:rtl;text-align:start}.sce-select-field .sce-select-field__wrap[dir=ltr] .p-select .p-select-label,.sce-select-field .sce-select-field__wrap[dir=ltr] .p-select .p-select-label.p-placeholder{direction:ltr;text-align:start}.sce-select-field .p-select .p-select-label.p-placeholder{color:var(--form-input-placeholder-color);opacity:1}sce-select-field.sce-select-field--locked .sce-select-field__wrap .p-select.p-disabled{opacity:1;background-color:var(--form-input-disabled-bg, var(--pdf-field-bg-alt))}sce-select-field.sce-select-field--locked .sce-select-field__wrap .p-select.p-disabled .p-select-label:not(.p-placeholder){color:var(--form-input-disabled-text, var(--text-text-disabled))}sce-select-field.sce-select-field--locked .sce-select-field__wrap[dir=rtl] .p-select.p-disabled .p-select-label:not(.p-placeholder){direction:rtl;text-align:start;justify-content:flex-start}sce-select-field.sce-select-field--locked .sce-select-field__wrap[dir=ltr] .p-select.p-disabled .p-select-label:not(.p-placeholder){direction:ltr;text-align:start;justify-content:flex-start}.sce-select-field .p-select-overlay,.p-select-overlay{z-index:var(--z-select-overlay);border-radius:var(--radius-sm);overflow:hidden;background:var(--dropdown-panel-bg);box-shadow:var(--dropdown-panel-shadow);color:var(--details-value-color)}.p-select-overlay .p-select-list .p-select-option{transition:background-color .12s ease;text-align:start}.p-select-overlay .p-select-list .p-select-option-group{font-weight:var(--font-weight-bold);font-size:var(--font-size-xs);color:var(--color-text-secondary);background-color:var(--color-surface-muted, var(--color-neutral-50));padding-block:var(--space-6);padding-inline:var(--space-12);-webkit-user-select:none;user-select:none;cursor:default;border-block-end:1px solid var(--color-border-light, var(--color-neutral-100))}.p-select-overlay .p-select-header .p-select-filter.p-select-filter{margin-inline-end:unset}.sce-select-field--outlined{border:1px solid var(--color-border-light);background:transparent}.sce-select-field--filled{background:var(--color-surface-muted)}.sce-select-field--underline{border:0;border-block-end:1px solid var(--color-border-light);border-radius:0}.sce-select-field--soft{background:var(--color-brand-primary-25)}.sce-select-field--bordered{border-inline-start:var(--space-3) solid var(--color-brand-primary-600)}.sce-select-field--elevated{border-radius:var(--radius-lg);background:var(--color-surface);box-shadow:var(--shadow-card-soft-subtle)}.sce-select-field--compact{padding-block:var(--space-4);font-size:var(--font-size-sm)}.sce-select-field--minimal{background:transparent;border:0;box-shadow:none}.sce-select-field--pill{border-radius:var(--radius-full)}.sce-select-field--addon .p-select{overflow:hidden}.sce-select-field--addon .p-select .p-select-dropdown{width:var(--size-53);isolation:isolate}.sce-select-field--addon .p-select .p-select-dropdown:before{content:\"\";position:absolute;inset:0;z-index:-1;background:var(--color-neutral-100)}\n"] }]
|
|
934
|
+
}], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], group: [{ type: i0.Input, args: [{ isSignal: true, alias: "group", required: false }] }], optionGroupLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionGroupLabel", required: false }] }], optionGroupChildren: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionGroupChildren", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], helpText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helpText", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }], filterMaxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterMaxLength", required: false }] }], showClear: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClear", required: false }] }], dir: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], validationMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMode", required: false }] }], validate: [{ type: i0.Input, args: [{ isSignal: true, alias: "validate", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], fieldClassName: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldClassName", required: false }] }], labelClassName: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelClassName", required: false }] }], inputClassName: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputClassName", required: false }] }], panelClassName: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelClassName", required: false }] }], borderless: [{ type: i0.Input, args: [{ isSignal: true, alias: "borderless", required: false }] }], filterPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterPlaceholder", required: false }] }], showError: [{ type: i0.Input, args: [{ isSignal: true, alias: "showError", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], containerClassName: [{ type: i0.Input, args: [{ isSignal: true, alias: "containerClassName", required: false }] }], resetToken: [{ type: i0.Input, args: [{ isSignal: true, alias: "resetToken", required: false }] }], selectRef: [{ type: i0.ViewChild, args: [i0.forwardRef(() => Select), { isSignal: true }] }] } });
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* React source: feedback/inline-api-error.tsx
|
|
938
|
+
*/
|
|
939
|
+
/** Inline error banner for an already-resolved `message`, showing a retry button only when `retryable` is opted into (it defaults to false so a failed save cannot re-trigger a load). */
|
|
940
|
+
class SceInlineApiErrorComponent {
|
|
941
|
+
errorIcon = AlertCircleIcon;
|
|
942
|
+
retryIcon = RefreshIcon;
|
|
943
|
+
message = input.required(...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
|
|
944
|
+
className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
|
|
945
|
+
/**
|
|
946
|
+
* Shows a retry button. Opt in only where the banner reports a *load* failure — on a form
|
|
947
|
+
* page the same banner reports a failed save, where re-running the load would discard input.
|
|
948
|
+
*/
|
|
949
|
+
retryable = input(false, ...(ngDevMode ? [{ debugName: "retryable" }] : /* istanbul ignore next */ []));
|
|
950
|
+
/** The host re-runs the request that failed. */
|
|
951
|
+
retry = output();
|
|
952
|
+
rootClass = computed(() => {
|
|
953
|
+
const cls = this.className();
|
|
954
|
+
return cls ? `inlineApiErrorRoot ${cls}` : 'inlineApiErrorRoot';
|
|
955
|
+
}, ...(ngDevMode ? [{ debugName: "rootClass" }] : /* istanbul ignore next */ []));
|
|
956
|
+
onRetry() {
|
|
957
|
+
this.retry.emit();
|
|
958
|
+
}
|
|
959
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceInlineApiErrorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
960
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: SceInlineApiErrorComponent, isStandalone: true, selector: "sce-inline-api-error", inputs: { message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: true, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, retryable: { classPropertyName: "retryable", publicName: "retryable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { retry: "retry" }, host: { classAttribute: "sce-inline-api-error" }, hostDirectives: [{ directive: i1.SceVariantDirective, inputs: ["sceVariant", "sceVariant", "sceDensity", "sceDensity"] }], ngImport: i0, template: "@if (message()) {\n <!-- role=alert already implies an assertive live region; adding aria-live=polite contradicts it -->\n <div [class]=\"rootClass()\" role=\"alert\">\n <sce-hugeicon class=\"inlineApiErrorIcon\" [icon]=\"errorIcon\" [size]=\"20\" />\n <p class=\"inlineApiErrorMessage\">{{ message() }}</p>\n @if (retryable()) {\n <button type=\"button\" class=\"inlineApiErrorRetry\" (click)=\"onRetry()\">\n <sce-hugeicon [icon]=\"retryIcon\" [size]=\"16\" />\n {{ 'STATUS_PAGES.RETRY' | translate }}\n </button>\n }\n </div>\n}\n", styles: [".inlineApiErrorRoot{display:flex;align-items:center;gap:var(--space-12);margin-block-start:var(--space-12);padding:var(--space-12) var(--space-16);border-radius:var(--radius-sm);border:var(--border-width-1) solid var(--feedback-inline-error-border);border-inline-start-width:var(--border-width-2);border-inline-start-color:var(--feedback-inline-error-text);background:var(--feedback-inline-error-bg);color:var(--feedback-inline-error-text);min-width:0;max-width:100%;box-shadow:var(--shadow-sm)}.inlineApiErrorIcon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--space-32);height:var(--space-32);border-radius:var(--radius-full);background:var(--color-attention-200);font-size:var(--font-size-md);line-height:var(--line-height-none)}.inlineApiErrorMessage{margin:0;font-size:var(--font-size-md);font-weight:var(--font-weight-semibold);line-height:var(--line-height-snug);min-width:0;max-width:100%;overflow-wrap:anywhere;overflow-wrap:break-word;flex:1 1 auto}.inlineApiErrorRetry{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-6);flex-shrink:0;min-height:var(--space-32);padding-block:var(--space-4);padding-inline:var(--space-12);border:var(--border-width-1) solid currentcolor;border-radius:var(--radius-sm);background:var(--app-surface);color:inherit;font-size:var(--font-size-sm);font-weight:var(--font-weight-semibold);cursor:pointer;transition:background-color var(--transition-base),box-shadow var(--transition-base)}.inlineApiErrorRetry:hover{background:var(--feedback-inline-error-border);box-shadow:var(--shadow-sm)}.inlineApiErrorRetry:focus-visible{outline:var(--border-width-2) solid currentcolor;outline-offset:var(--space-2)}@media(max-width:480px){.inlineApiErrorRoot{flex-wrap:wrap}.inlineApiErrorRetry{inline-size:100%}}@media(prefers-reduced-motion:reduce){.inlineApiErrorRetry{transition:none}}\n"], dependencies: [{ kind: "component", type: SceHugeicon, selector: "sce-hugeicon", inputs: ["icon", "altIcon", "showAlt", "size", "color", "strokeWidth", "absoluteStrokeWidth", "spin", "label", "flipRtl"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
961
|
+
}
|
|
962
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceInlineApiErrorComponent, decorators: [{
|
|
963
|
+
type: Component,
|
|
964
|
+
args: [{ hostDirectives: [{ directive: SceVariantDirective, inputs: ['sceVariant', 'sceDensity'] }], host: { class: 'sce-inline-api-error' }, selector: 'sce-inline-api-error', standalone: true, imports: [TranslatePipe, SceHugeicon], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (message()) {\n <!-- role=alert already implies an assertive live region; adding aria-live=polite contradicts it -->\n <div [class]=\"rootClass()\" role=\"alert\">\n <sce-hugeicon class=\"inlineApiErrorIcon\" [icon]=\"errorIcon\" [size]=\"20\" />\n <p class=\"inlineApiErrorMessage\">{{ message() }}</p>\n @if (retryable()) {\n <button type=\"button\" class=\"inlineApiErrorRetry\" (click)=\"onRetry()\">\n <sce-hugeicon [icon]=\"retryIcon\" [size]=\"16\" />\n {{ 'STATUS_PAGES.RETRY' | translate }}\n </button>\n }\n </div>\n}\n", styles: [".inlineApiErrorRoot{display:flex;align-items:center;gap:var(--space-12);margin-block-start:var(--space-12);padding:var(--space-12) var(--space-16);border-radius:var(--radius-sm);border:var(--border-width-1) solid var(--feedback-inline-error-border);border-inline-start-width:var(--border-width-2);border-inline-start-color:var(--feedback-inline-error-text);background:var(--feedback-inline-error-bg);color:var(--feedback-inline-error-text);min-width:0;max-width:100%;box-shadow:var(--shadow-sm)}.inlineApiErrorIcon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--space-32);height:var(--space-32);border-radius:var(--radius-full);background:var(--color-attention-200);font-size:var(--font-size-md);line-height:var(--line-height-none)}.inlineApiErrorMessage{margin:0;font-size:var(--font-size-md);font-weight:var(--font-weight-semibold);line-height:var(--line-height-snug);min-width:0;max-width:100%;overflow-wrap:anywhere;overflow-wrap:break-word;flex:1 1 auto}.inlineApiErrorRetry{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-6);flex-shrink:0;min-height:var(--space-32);padding-block:var(--space-4);padding-inline:var(--space-12);border:var(--border-width-1) solid currentcolor;border-radius:var(--radius-sm);background:var(--app-surface);color:inherit;font-size:var(--font-size-sm);font-weight:var(--font-weight-semibold);cursor:pointer;transition:background-color var(--transition-base),box-shadow var(--transition-base)}.inlineApiErrorRetry:hover{background:var(--feedback-inline-error-border);box-shadow:var(--shadow-sm)}.inlineApiErrorRetry:focus-visible{outline:var(--border-width-2) solid currentcolor;outline-offset:var(--space-2)}@media(max-width:480px){.inlineApiErrorRoot{flex-wrap:wrap}.inlineApiErrorRetry{inline-size:100%}}@media(prefers-reduced-motion:reduce){.inlineApiErrorRetry{transition:none}}\n"] }]
|
|
965
|
+
}], propDecorators: { message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: true }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], retryable: [{ type: i0.Input, args: [{ isSignal: true, alias: "retryable", required: false }] }], retry: [{ type: i0.Output, args: ["retry"] }] } });
|
|
966
|
+
|
|
967
|
+
const PARCEL_NOT_FOUND_MESSAGE = 'لم يتم العثور على أرض محددة في هذه النقطة. يرجى اختيار موقع أرض داخل مخطط.';
|
|
968
|
+
const RATE_LIMITED_MESSAGE = 'تم تجاوز عدد الطلبات المسموح به. يرجى الانتظار قليلًا ثم المحاولة مرة أخرى.';
|
|
969
|
+
/** Hold-off after a 429 that carried no `Retry-After`. */
|
|
970
|
+
const DEFAULT_RATE_LIMIT_BACKOFF_SECONDS = 5;
|
|
971
|
+
/** Fallback center coordinates for Saudi Arabia regions */
|
|
972
|
+
const SAUDI_REGION_CENTERS = {
|
|
973
|
+
'001': { lat: 24.7136, lng: 46.6753, zoom: 8 }, // Riyadh
|
|
974
|
+
'002': { lat: 21.3891, lng: 39.8579, zoom: 8 }, // Makkah
|
|
975
|
+
'003': { lat: 24.5247, lng: 39.5692, zoom: 8 }, // Madinah
|
|
976
|
+
'004': { lat: 26.326, lng: 43.975, zoom: 8 }, // Al Qassim
|
|
977
|
+
'005': { lat: 26.4207, lng: 50.0888, zoom: 7 }, // Eastern
|
|
978
|
+
'006': { lat: 18.2164, lng: 42.5053, zoom: 8 }, // Asir
|
|
979
|
+
'007': { lat: 28.3835, lng: 36.5662, zoom: 8 }, // Tabuk
|
|
980
|
+
'008': { lat: 27.5236, lng: 41.6961, zoom: 8 }, // Hail
|
|
981
|
+
'009': { lat: 30.9753, lng: 41.0381, zoom: 8 }, // Northern Borders
|
|
982
|
+
'010': { lat: 16.8894, lng: 42.5511, zoom: 9 }, // Jazan
|
|
983
|
+
'011': { lat: 17.4924, lng: 44.1277, zoom: 8 }, // Najran
|
|
984
|
+
'012': { lat: 20.0129, lng: 41.4676, zoom: 9 }, // Al Baha
|
|
985
|
+
'013': { lat: 29.9697, lng: 40.1963, zoom: 8 }, // Al Jouf
|
|
986
|
+
};
|
|
987
|
+
function calculateRingsCenter(rings) {
|
|
988
|
+
if (!rings || !rings.length || !rings[0].length) {
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
991
|
+
const points = rings[0];
|
|
992
|
+
let sumLng = 0;
|
|
993
|
+
let sumLat = 0;
|
|
994
|
+
let count = 0;
|
|
995
|
+
for (const pt of points) {
|
|
996
|
+
if (Array.isArray(pt) && pt.length >= 2) {
|
|
997
|
+
sumLng += pt[0];
|
|
998
|
+
sumLat += pt[1];
|
|
999
|
+
count++;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
if (count === 0) {
|
|
1003
|
+
return null;
|
|
1004
|
+
}
|
|
1005
|
+
return { lat: sumLat / count, lng: sumLng / count };
|
|
1006
|
+
}
|
|
1007
|
+
class SceBaladyMapSelectorComponent {
|
|
1008
|
+
locale = input('ar', ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
|
|
1009
|
+
dir = input('rtl', ...(ngDevMode ? [{ debugName: "dir" }] : /* istanbul ignore next */ []));
|
|
1010
|
+
initialPropertyData = input(null, ...(ngDevMode ? [{ debugName: "initialPropertyData" }] : /* istanbul ignore next */ []));
|
|
1011
|
+
propertySelected = output();
|
|
1012
|
+
mapClick = output();
|
|
1013
|
+
gisService = inject(BaladyGisService);
|
|
1014
|
+
toast = inject(ToastService);
|
|
1015
|
+
lastQueriedCoords = null;
|
|
1016
|
+
/** Epoch ms until which point queries are held back after a 429 from the GIS proxy. */
|
|
1017
|
+
pointQueryBlockedUntil = 0;
|
|
1018
|
+
constructor() {
|
|
1019
|
+
effect(() => {
|
|
1020
|
+
const initData = this.initialPropertyData();
|
|
1021
|
+
if (initData && (initData.x != null || initData.y != null || initData.parcelLatitude != null)) {
|
|
1022
|
+
const lat = Number(initData.y ?? initData.parcelLatitude ?? initData.lat);
|
|
1023
|
+
const lng = Number(initData.x ?? initData.parcelLongitude ?? initData.lng);
|
|
1024
|
+
if (!isNaN(lat) && !isNaN(lng) && lat !== 0 && lng !== 0) {
|
|
1025
|
+
this.currentLat.set(lat);
|
|
1026
|
+
this.currentLng.set(lng);
|
|
1027
|
+
this.zoomLevel.set(18);
|
|
1028
|
+
if (!this.selectedPropertyData()) {
|
|
1029
|
+
this.selectedPropertyData.set(initData);
|
|
1030
|
+
}
|
|
1031
|
+
const popupHtml = `
|
|
1032
|
+
<div style="font-family: inherit; font-size: 13px; line-height: 1.5; padding: 4px;">
|
|
1033
|
+
<strong>رقم القطعة:</strong> ${initData.parcelNo ?? '-'}<br/>
|
|
1034
|
+
<strong>رقم المخطط:</strong> ${initData.planNO ?? '-'}<br/>
|
|
1035
|
+
<strong>الاستخدام:</strong> ${initData.landuse ?? '-'}<br/>
|
|
1036
|
+
<strong>المساحة:</strong> ${this.formatArea(initData.area)}
|
|
1037
|
+
</div>
|
|
1038
|
+
`;
|
|
1039
|
+
this.currentMarkers.set([
|
|
1040
|
+
{
|
|
1041
|
+
draggable: false,
|
|
1042
|
+
lat,
|
|
1043
|
+
lng,
|
|
1044
|
+
popupHtml,
|
|
1045
|
+
},
|
|
1046
|
+
]);
|
|
1047
|
+
this.queryPointData(lng, lat, { emit: false });
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
// In-flight API Subscriptions for cancellation
|
|
1053
|
+
regionsSub;
|
|
1054
|
+
citiesSub;
|
|
1055
|
+
districtsSub;
|
|
1056
|
+
pointDataSub;
|
|
1057
|
+
// Filter Signals
|
|
1058
|
+
regions = signal([], ...(ngDevMode ? [{ debugName: "regions" }] : /* istanbul ignore next */ []));
|
|
1059
|
+
cities = signal([], ...(ngDevMode ? [{ debugName: "cities" }] : /* istanbul ignore next */ []));
|
|
1060
|
+
districts = signal([], ...(ngDevMode ? [{ debugName: "districts" }] : /* istanbul ignore next */ []));
|
|
1061
|
+
selectedRegionCode = signal('', ...(ngDevMode ? [{ debugName: "selectedRegionCode" }] : /* istanbul ignore next */ []));
|
|
1062
|
+
selectedCityCode = signal('', ...(ngDevMode ? [{ debugName: "selectedCityCode" }] : /* istanbul ignore next */ []));
|
|
1063
|
+
selectedDistrictCode = signal('', ...(ngDevMode ? [{ debugName: "selectedDistrictCode" }] : /* istanbul ignore next */ []));
|
|
1064
|
+
formatArea(area) {
|
|
1065
|
+
if (!area) {
|
|
1066
|
+
return '-';
|
|
1067
|
+
}
|
|
1068
|
+
const num = parseFloat(area);
|
|
1069
|
+
if (isNaN(num)) {
|
|
1070
|
+
return area;
|
|
1071
|
+
}
|
|
1072
|
+
return `${num.toFixed(2)} م²`;
|
|
1073
|
+
}
|
|
1074
|
+
// Map Signals
|
|
1075
|
+
currentLat = signal(24.7136, ...(ngDevMode ? [{ debugName: "currentLat" }] : /* istanbul ignore next */ []));
|
|
1076
|
+
currentLng = signal(46.6753, ...(ngDevMode ? [{ debugName: "currentLng" }] : /* istanbul ignore next */ []));
|
|
1077
|
+
zoomLevel = signal(6, ...(ngDevMode ? [{ debugName: "zoomLevel" }] : /* istanbul ignore next */ []));
|
|
1078
|
+
currentMarkers = signal([], ...(ngDevMode ? [{ debugName: "currentMarkers" }] : /* istanbul ignore next */ []));
|
|
1079
|
+
// State Signals
|
|
1080
|
+
isLoadingFilterData = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingFilterData" }] : /* istanbul ignore next */ []));
|
|
1081
|
+
isQueryingPointData = signal(false, ...(ngDevMode ? [{ debugName: "isQueryingPointData" }] : /* istanbul ignore next */ []));
|
|
1082
|
+
loadError = signal(false, ...(ngDevMode ? [{ debugName: "loadError" }] : /* istanbul ignore next */ []));
|
|
1083
|
+
selectedPropertyData = signal(null, ...(ngDevMode ? [{ debugName: "selectedPropertyData" }] : /* istanbul ignore next */ []));
|
|
1084
|
+
// Options for Dropdowns
|
|
1085
|
+
regionOptions = computed(() => this.regions().map(r => ({
|
|
1086
|
+
label: r.regionName,
|
|
1087
|
+
value: r.regionCode,
|
|
1088
|
+
})), ...(ngDevMode ? [{ debugName: "regionOptions" }] : /* istanbul ignore next */ []));
|
|
1089
|
+
cityOptions = computed(() => this.cities().map(c => ({
|
|
1090
|
+
label: c.name,
|
|
1091
|
+
value: c.code,
|
|
1092
|
+
})), ...(ngDevMode ? [{ debugName: "cityOptions" }] : /* istanbul ignore next */ []));
|
|
1093
|
+
districtOptions = computed(() => this.districts().map(d => ({
|
|
1094
|
+
label: d.districtName,
|
|
1095
|
+
value: d.districtCode,
|
|
1096
|
+
})), ...(ngDevMode ? [{ debugName: "districtOptions" }] : /* istanbul ignore next */ []));
|
|
1097
|
+
ngOnInit() {
|
|
1098
|
+
this.loadRegions();
|
|
1099
|
+
}
|
|
1100
|
+
ngOnDestroy() {
|
|
1101
|
+
this.regionsSub?.unsubscribe();
|
|
1102
|
+
this.citiesSub?.unsubscribe();
|
|
1103
|
+
this.districtsSub?.unsubscribe();
|
|
1104
|
+
this.pointDataSub?.unsubscribe();
|
|
1105
|
+
}
|
|
1106
|
+
loadRegions() {
|
|
1107
|
+
this.regionsSub?.unsubscribe();
|
|
1108
|
+
this.isLoadingFilterData.set(true);
|
|
1109
|
+
this.loadError.set(false);
|
|
1110
|
+
this.regionsSub = this.gisService.getRegions().subscribe({
|
|
1111
|
+
next: data => {
|
|
1112
|
+
this.regions.set(data);
|
|
1113
|
+
this.isLoadingFilterData.set(false);
|
|
1114
|
+
},
|
|
1115
|
+
error: () => {
|
|
1116
|
+
this.isLoadingFilterData.set(false);
|
|
1117
|
+
this.loadError.set(true);
|
|
1118
|
+
},
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
/** Filter -> Map Sync: User selects Region */
|
|
1122
|
+
onRegionChange(code) {
|
|
1123
|
+
// Cancel in-flight cities and districts requests
|
|
1124
|
+
this.citiesSub?.unsubscribe();
|
|
1125
|
+
this.districtsSub?.unsubscribe();
|
|
1126
|
+
const validCode = code ?? '';
|
|
1127
|
+
this.selectedRegionCode.set(validCode);
|
|
1128
|
+
this.selectedCityCode.set('');
|
|
1129
|
+
this.selectedDistrictCode.set('');
|
|
1130
|
+
this.cities.set([]);
|
|
1131
|
+
this.districts.set([]);
|
|
1132
|
+
if (!validCode) {
|
|
1133
|
+
this.isLoadingFilterData.set(false);
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
// Move map to region center
|
|
1137
|
+
const regionCenter = SAUDI_REGION_CENTERS[validCode];
|
|
1138
|
+
if (regionCenter) {
|
|
1139
|
+
this.currentLat.set(regionCenter.lat);
|
|
1140
|
+
this.currentLng.set(regionCenter.lng);
|
|
1141
|
+
this.zoomLevel.set(regionCenter.zoom);
|
|
1142
|
+
}
|
|
1143
|
+
this.isLoadingFilterData.set(true);
|
|
1144
|
+
this.citiesSub = this.gisService.getCities(validCode).subscribe({
|
|
1145
|
+
next: data => {
|
|
1146
|
+
this.cities.set(data);
|
|
1147
|
+
this.isLoadingFilterData.set(false);
|
|
1148
|
+
},
|
|
1149
|
+
error: () => {
|
|
1150
|
+
this.isLoadingFilterData.set(false);
|
|
1151
|
+
},
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
/** Filter -> Map Sync: User selects City */
|
|
1155
|
+
onCityChange(code) {
|
|
1156
|
+
// Cancel in-flight districts request
|
|
1157
|
+
this.districtsSub?.unsubscribe();
|
|
1158
|
+
const validCode = code ?? '';
|
|
1159
|
+
this.selectedCityCode.set(validCode);
|
|
1160
|
+
this.selectedDistrictCode.set('');
|
|
1161
|
+
this.districts.set([]);
|
|
1162
|
+
if (!validCode) {
|
|
1163
|
+
this.isLoadingFilterData.set(false);
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
// Move map to city center if rings exist
|
|
1167
|
+
const selectedCity = this.cities().find(c => c.code === validCode);
|
|
1168
|
+
if (selectedCity?.rings) {
|
|
1169
|
+
const center = calculateRingsCenter(selectedCity.rings);
|
|
1170
|
+
if (center) {
|
|
1171
|
+
this.currentLat.set(center.lat);
|
|
1172
|
+
this.currentLng.set(center.lng);
|
|
1173
|
+
this.zoomLevel.set(12);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
const regionCode = this.selectedRegionCode();
|
|
1177
|
+
this.isLoadingFilterData.set(true);
|
|
1178
|
+
this.districtsSub = this.gisService.getDistricts(regionCode, validCode).subscribe({
|
|
1179
|
+
next: data => {
|
|
1180
|
+
this.districts.set(data);
|
|
1181
|
+
this.isLoadingFilterData.set(false);
|
|
1182
|
+
},
|
|
1183
|
+
error: () => {
|
|
1184
|
+
this.isLoadingFilterData.set(false);
|
|
1185
|
+
},
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
/** Filter -> Map Sync: User selects District */
|
|
1189
|
+
onDistrictChange(code) {
|
|
1190
|
+
const validCode = code ?? '';
|
|
1191
|
+
this.selectedDistrictCode.set(validCode);
|
|
1192
|
+
if (!validCode) {
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
// Move map to district center if rings exist
|
|
1196
|
+
const selectedDistrict = this.districts().find(d => d.districtCode === validCode);
|
|
1197
|
+
if (selectedDistrict?.rings) {
|
|
1198
|
+
const center = calculateRingsCenter(selectedDistrict.rings);
|
|
1199
|
+
if (center) {
|
|
1200
|
+
this.currentLat.set(center.lat);
|
|
1201
|
+
this.currentLng.set(center.lng);
|
|
1202
|
+
this.zoomLevel.set(14);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
else {
|
|
1206
|
+
this.zoomLevel.set(14);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
/** Query Balady GIS API for point data (x=lng, y=lat) */
|
|
1210
|
+
queryPointData(lng, lat, options = {}) {
|
|
1211
|
+
const emit = options.emit ?? true;
|
|
1212
|
+
if (this.lastQueriedCoords && this.lastQueriedCoords.lat === lat && this.lastQueriedCoords.lng === lng) {
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
if (Date.now() < this.pointQueryBlockedUntil) {
|
|
1216
|
+
if (emit) {
|
|
1217
|
+
this.toast.warning('', RATE_LIMITED_MESSAGE);
|
|
1218
|
+
}
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
this.lastQueriedCoords = { lat, lng };
|
|
1222
|
+
// Cancel any in-flight requests before querying new point
|
|
1223
|
+
this.pointDataSub?.unsubscribe();
|
|
1224
|
+
this.citiesSub?.unsubscribe();
|
|
1225
|
+
this.districtsSub?.unsubscribe();
|
|
1226
|
+
this.currentLat.set(lat);
|
|
1227
|
+
this.currentLng.set(lng);
|
|
1228
|
+
this.zoomLevel.set(18);
|
|
1229
|
+
this.isQueryingPointData.set(true);
|
|
1230
|
+
this.pointDataSub = this.gisService.getPointData(lng, lat).subscribe({
|
|
1231
|
+
next: result => {
|
|
1232
|
+
this.isQueryingPointData.set(false);
|
|
1233
|
+
const hasPointDetails = Boolean(result?.parcelNo || result?.planNO || result?.region || result?.city);
|
|
1234
|
+
if (result && hasPointDetails) {
|
|
1235
|
+
// The saved coordinates are the clicked point — never a vertex of the parcel rings.
|
|
1236
|
+
result.x = lng;
|
|
1237
|
+
result.y = lat;
|
|
1238
|
+
this.zoomLevel.set(18);
|
|
1239
|
+
this.selectedPropertyData.set(result);
|
|
1240
|
+
if (emit) {
|
|
1241
|
+
this.propertySelected.emit(result);
|
|
1242
|
+
}
|
|
1243
|
+
// Update Map Marker
|
|
1244
|
+
const popupHtml = `
|
|
1245
|
+
<div style="font-family: inherit; font-size: 13px; line-height: 1.5; padding: 4px;">
|
|
1246
|
+
<strong>رقم القطعة:</strong> ${result.parcelNo ?? '-'}<br/>
|
|
1247
|
+
<strong>رقم المخطط:</strong> ${result.planNO ?? '-'}<br/>
|
|
1248
|
+
<strong>الاستخدام:</strong> ${result.landuse ?? '-'}<br/>
|
|
1249
|
+
<strong>المساحة:</strong> ${this.formatArea(result.area)}
|
|
1250
|
+
</div>
|
|
1251
|
+
`;
|
|
1252
|
+
this.currentMarkers.set([
|
|
1253
|
+
{
|
|
1254
|
+
draggable: false,
|
|
1255
|
+
lat,
|
|
1256
|
+
lng,
|
|
1257
|
+
popupHtml,
|
|
1258
|
+
},
|
|
1259
|
+
]);
|
|
1260
|
+
}
|
|
1261
|
+
else {
|
|
1262
|
+
if (emit) {
|
|
1263
|
+
this.toast.info('', PARCEL_NOT_FOUND_MESSAGE);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
},
|
|
1267
|
+
error: (err) => {
|
|
1268
|
+
this.isQueryingPointData.set(false);
|
|
1269
|
+
// Let the user retry the same point once the failure is resolved.
|
|
1270
|
+
this.lastQueriedCoords = null;
|
|
1271
|
+
const kind = err instanceof BaladyGisError ? err.kind : 'unknown';
|
|
1272
|
+
if (kind === 'rateLimited') {
|
|
1273
|
+
const retryAfter = err.retryAfterSeconds ?? DEFAULT_RATE_LIMIT_BACKOFF_SECONDS;
|
|
1274
|
+
this.pointQueryBlockedUntil = Date.now() + retryAfter * 1000;
|
|
1275
|
+
}
|
|
1276
|
+
if (emit) {
|
|
1277
|
+
this.showPointQueryError(kind);
|
|
1278
|
+
}
|
|
1279
|
+
},
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
showPointQueryError(kind) {
|
|
1283
|
+
switch (kind) {
|
|
1284
|
+
case 'notFound':
|
|
1285
|
+
case 'invalid':
|
|
1286
|
+
this.toast.info('', PARCEL_NOT_FOUND_MESSAGE);
|
|
1287
|
+
return;
|
|
1288
|
+
case 'rateLimited':
|
|
1289
|
+
this.toast.warning('', RATE_LIMITED_MESSAGE);
|
|
1290
|
+
return;
|
|
1291
|
+
case 'unavailable':
|
|
1292
|
+
this.toast.error('', 'خدمة بلدي غير متاحة حاليًا. يرجى المحاولة لاحقًا.');
|
|
1293
|
+
return;
|
|
1294
|
+
default:
|
|
1295
|
+
this.toast.error('', 'تعذر الاستعلام عن بيانات العقار. يرجى المحاولة مرة أخرى.');
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
/** Map Click -> Query Balady & Sync Filter Dropdowns */
|
|
1299
|
+
onMapClicked(coords) {
|
|
1300
|
+
// Leaflet reports longitudes past ±180 once the map is panned across the antimeridian.
|
|
1301
|
+
const lng = ((((coords.lng + 180) % 360) + 360) % 360) - 180;
|
|
1302
|
+
this.mapClick.emit({ lat: coords.lat, lng });
|
|
1303
|
+
this.queryPointData(lng, coords.lat, { emit: true });
|
|
1304
|
+
}
|
|
1305
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceBaladyMapSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1306
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: SceBaladyMapSelectorComponent, isStandalone: true, selector: "sce-balady-map-selector", inputs: { locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, dir: { classPropertyName: "dir", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, initialPropertyData: { classPropertyName: "initialPropertyData", publicName: "initialPropertyData", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { propertySelected: "propertySelected", mapClick: "mapClick" }, host: { classAttribute: "sce-balady-map-selector" }, hostDirectives: [{ directive: i1.SceVariantDirective, inputs: ["sceVariant", "sceVariant", "sceDensity", "sceDensity"] }], ngImport: i0, template: "<div class=\"baladyMapSelector\">\n <div class=\"baladyMapSelector__filterHeader\">\n <div class=\"baladyMapSelector__filterTitle\">\n <h4>{{ '\u062A\u062D\u062F\u064A\u062F \u0627\u0644\u0639\u0642\u0627\u0631 \u0645\u0646 \u0627\u0644\u062E\u0631\u064A\u0637\u0629 \u0627\u0644\u062A\u0641\u0627\u0639\u0644\u064A\u0629' }}</h4>\n </div>\n <p class=\"baladyMapSelector__filterSubtitle\">\n \u0627\u062E\u062A\u0631 \u0627\u0644\u0645\u0646\u0637\u0642\u0629 \u0648\u0627\u0644\u0645\u062F\u064A\u0646\u0629 \u0648\u0627\u0644\u062D\u064A \u0644\u062A\u062D\u062F\u064A\u062F \u0627\u0644\u0646\u0637\u0627\u0642\u060C \u062B\u0645 \u0627\u0636\u063A\u0637 \u0639\u0644\u0649 \u0645\u0648\u0642\u0639 \u0627\u0644\u0623\u0631\u0636 \u0645\u0628\u0627\u0634\u0631\u0629 \u0641\u064A \u0627\u0644\u062E\u0631\u064A\u0637\u0629 \u0644\u0627\u0633\u062A\u062E\u0631\u0627\u062C \u062A\u0641\u0627\u0635\u064A\u0644 \u0627\u0644\u0645\u062E\u0637\u0637 \u0648\u0627\u0644\u0642\u0637\u0639\u0629 \u0648\u0627\u0644\u0645\u0633\u0627\u062D\u0629 \u0648\u0627\u0644\u062D\u062F\u0648\u062F.\n </p>\n\n @if (loadError()) {\n <sce-inline-api-error [message]=\"'\u062A\u0639\u0630\u0631 \u062A\u062D\u0645\u064A\u0644 \u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0645\u0646\u0627\u0637\u0642'\" [retryable]=\"true\" (retry)=\"loadRegions()\" />\n }\n </div>\n\n <div class=\"baladyMapSelector__mapContainer\">\n <!-- Floating Filters Bar over Map -->\n <div class=\"baladyMapSelector__floatingFilters\">\n @if (isLoadingFilterData()) {\n <div class=\"baladyMapSelector__filterLoader\" role=\"status\" aria-label=\"\u062C\u0627\u0631\u064A \u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A\">\n <div class=\"baladyMapSelector__miniSpinner\"></div>\n <span>\u062C\u0627\u0631\u064A \u062C\u0644\u0628 \u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u062A\u0635\u0641\u064A\u0629...</span>\n </div>\n }\n\n <div class=\"baladyMapSelector__grid\">\n <sce-select-field\n id=\"balady-filter-region\"\n [label]=\"'\u0627\u0644\u0645\u0646\u0637\u0642\u0629'\"\n [options]=\"regionOptions()\"\n [value]=\"selectedRegionCode()\"\n [disabled]=\"isLoadingFilterData()\"\n [dir]=\"dir()\"\n [showClear]=\"true\"\n (valueChange)=\"onRegionChange($event)\"\n />\n <sce-select-field\n id=\"balady-filter-city\"\n [label]=\"'\u0627\u0644\u0645\u062F\u064A\u0646\u0629'\"\n [options]=\"cityOptions()\"\n [value]=\"selectedCityCode()\"\n [disabled]=\"isLoadingFilterData() || !selectedRegionCode()\"\n [dir]=\"dir()\"\n [showClear]=\"true\"\n (valueChange)=\"onCityChange($event)\"\n />\n <sce-select-field\n id=\"balady-filter-district\"\n [label]=\"'\u0627\u0644\u062D\u064A'\"\n [options]=\"districtOptions()\"\n [value]=\"selectedDistrictCode()\"\n [disabled]=\"isLoadingFilterData() || !selectedCityCode()\"\n [dir]=\"dir()\"\n [showClear]=\"true\"\n (valueChange)=\"onDistrictChange($event)\"\n />\n </div>\n </div>\n\n @if (isQueryingPointData()) {\n <div class=\"baladyMapSelector__overlay\" role=\"status\" aria-label=\"\u062C\u0627\u0631\u064A \u0627\u0644\u0627\u0633\u062A\u0639\u0644\u0627\u0645 \u0639\u0646 \u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0623\u0631\u0636\">\n <div class=\"baladyMapSelector__spinner\"></div>\n <span>\u062C\u0627\u0631\u064A \u0627\u0644\u0627\u0633\u062A\u0639\u0644\u0627\u0645 \u0639\u0646 \u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0639\u0642\u0627\u0631 \u0648\u0627\u0644\u0642\u0637\u0639\u0629...</span>\n </div>\n }\n\n <sce-osm-leaflet-map\n [latitude]=\"currentLat()\"\n [longitude]=\"currentLng()\"\n [zoom]=\"zoomLevel()\"\n [markers]=\"currentMarkers()\"\n [interactive]=\"true\"\n (mapClick)=\"onMapClicked($event)\"\n />\n </div>\n\n @if (selectedPropertyData(); as prop) {\n <div class=\"baladyMapSelector__summaryCard\" role=\"region\" aria-label=\"\u062A\u0641\u0627\u0635\u064A\u0644 \u0627\u0644\u0639\u0642\u0627\u0631 \u0627\u0644\u0645Selected\">\n <div class=\"baladyMapSelector__summaryHeader\">\n <span class=\"baladyMapSelector__statusIcon\">\u2713</span>\n <strong>\u062A\u0645 \u062A\u062D\u062F\u064A\u062F \u0627\u0644\u0639\u0642\u0627\u0631 \u0628\u0646\u062C\u0627\u062D</strong>\n </div>\n <div class=\"baladyMapSelector__summaryGrid\">\n <div class=\"baladyMapSelector__infoItem\">\n <span class=\"label\">\u0627\u0644\u0645\u0646\u0637\u0642\u0629 \u0648\u0627\u0644\u0645\u062F\u064A\u0646\u0629:</span>\n <span class=\"val\">{{ prop.region }} - {{ prop.city }} ({{ prop.districtName }})</span>\n </div>\n <div class=\"baladyMapSelector__infoItem\">\n <span class=\"label\">\u0631\u0642\u0645 \u0627\u0644\u0645\u062E\u0637\u0637:</span>\n <span class=\"val\">{{ prop.planNO ?? '-' }}</span>\n </div>\n <div class=\"baladyMapSelector__infoItem\">\n <span class=\"label\">\u0631\u0642\u0645 \u0627\u0644\u0642\u0637\u0639\u0629:</span>\n <span class=\"val\">{{ prop.parcelNo ?? '-' }}</span>\n </div>\n <div class=\"baladyMapSelector__infoItem\">\n <span class=\"label\">\u0627\u0633\u062A\u062E\u062F\u0627\u0645 \u0627\u0644\u0623\u0631\u0636:</span>\n <span class=\"val\">{{ prop.landuse ?? '-' }}</span>\n </div>\n <div class=\"baladyMapSelector__infoItem\">\n <span class=\"label\">\u0627\u0644\u0645\u0633\u0627\u062D\u0629:</span>\n <span class=\"val\">{{ formatArea(prop.area) }}</span>\n </div>\n @if (prop.directionParcel && prop.directionParcel.length) {\n <!-- Track by index: a parcel can face the same direction twice, and duplicate keys throw NG0955. -->\n @for (dir of prop.directionParcel; track $index) {\n <div class=\"baladyMapSelector__infoItem\">\n <span class=\"label\">\u062D\u062F\u0648\u062F \u0627\u0644\u0639\u0642\u0627\u0631 ({{ dir.direction }}):</span>\n <span class=\"val\">{{ dir.length }} \u0645</span>\n </div>\n }\n }\n </div>\n </div>\n }\n</div>\n", styles: [":host.sce-balady-map-selector{display:block;width:100%}.baladyMapSelector{display:flex;flex-direction:column;gap:1rem;background-color:var(--color-neutral-50);border:1px solid var(--color-neutral-200);border-radius:var(--border-radius-lg, 12px);padding:1.25rem}.baladyMapSelector__filterHeader{display:flex;flex-direction:column;gap:.75rem}.baladyMapSelector__filterTitle{display:flex;align-items:center;gap:.5rem}.baladyMapSelector__filterTitle h4{margin:0;font-size:1.05rem;font-weight:700;color:var(--color-neutral-900)}.baladyMapSelector__badge{display:inline-flex;align-items:center;padding:.2rem .6rem;font-size:.75rem;font-weight:700;border-radius:9999px;background-color:#10b9811a;color:#059669}.baladyMapSelector__filterSubtitle{margin:0;font-size:.875rem;color:var(--color-neutral-600);line-height:1.5}.baladyMapSelector__filterLoader{display:flex;align-items:center;gap:.6rem;padding:.5rem .875rem;border-radius:var(--border-radius-md, 8px);background-color:#10b98114;border:1px solid rgba(16,185,129,.2);color:#047857;font-size:.8125rem;font-weight:600;animation:baladyFadeIn .25s ease-in-out}.baladyMapSelector__miniSpinner{width:16px;height:16px;border:2px solid rgba(16,185,129,.25);border-block-start-color:#059669;border-radius:50%;animation:baladySpin .7s linear infinite}.baladyMapSelector__grid{display:grid;grid-template-columns:repeat(1,1fr);gap:.75rem}@media(min-width:768px){.baladyMapSelector__grid{grid-template-columns:repeat(3,1fr)}}.baladyMapSelector__floatingFilters{position:absolute;top:.75rem;inset-inline-start:.75rem;inset-inline-end:.75rem;z-index:500;display:flex;flex-direction:column;gap:.5rem;background-color:#fffffff0;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--color-neutral-200);border-radius:var(--border-radius-md, 8px);padding:.75rem;box-shadow:0 4px 16px #00000014}.dark-theme .baladyMapSelector__floatingFilters{background-color:#0f172af0;border-color:var(--color-neutral-700)}.baladyMapSelector__mapContainer{position:relative;width:100%;height:480px;border-radius:var(--border-radius-md, 8px);overflow:hidden;border:1px solid var(--color-neutral-200)}.baladyMapSelector__mapContainer sce-osm-leaflet-map{width:100%;height:100%;display:block}.baladyMapSelector__overlay{position:absolute;inset:0;z-index:1000;background-color:#ffffffd9;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;color:var(--color-neutral-900);font-size:.9rem;font-weight:600}.dark-theme .baladyMapSelector__overlay{background-color:#0f172ad9;color:var(--color-neutral-100)}.baladyMapSelector__spinner{width:32px;height:32px;border:3px solid rgba(16,185,129,.2);border-block-start-color:#059669;border-radius:50%;animation:baladySpin .8s linear infinite}.baladyMapSelector__summaryCard{background-color:#10b9810d;border:1px solid rgba(16,185,129,.2);border-radius:var(--border-radius-md, 8px);padding:.875rem 1rem;display:flex;flex-direction:column;gap:.5rem}.baladyMapSelector__summaryHeader{display:flex;align-items:center;gap:.5rem;color:#047857;font-size:.95rem}.baladyMapSelector__summaryHeader strong{font-weight:700}.baladyMapSelector__statusIcon{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:50%;background-color:#059669;color:#fff;font-size:.75rem;font-weight:700}.baladyMapSelector__summaryGrid{display:grid;grid-template-columns:repeat(1,1fr);gap:.5rem}@media(min-width:640px){.baladyMapSelector__summaryGrid{grid-template-columns:repeat(2,1fr)}}@media(min-width:1024px){.baladyMapSelector__summaryGrid{grid-template-columns:repeat(3,1fr)}}.baladyMapSelector__infoItem{display:flex;flex-direction:column}.baladyMapSelector__infoItem .label{font-size:.75rem;color:var(--color-neutral-600)}.baladyMapSelector__infoItem .val{font-size:.875rem;font-weight:600;color:var(--color-neutral-900)}@keyframes baladySpin{to{transform:rotate(360deg)}}@keyframes baladyFadeIn{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: SceSelectFieldComponent, selector: "sce-select-field", inputs: ["variant", "id", "label", "options", "group", "optionGroupLabel", "optionGroupChildren", "value", "placeholder", "required", "disabled", "helpText", "error", "filter", "filterMaxLength", "showClear", "dir", "validationMode", "validate", "validationMessages", "fieldClassName", "labelClassName", "inputClassName", "panelClassName", "borderless", "filterPlaceholder", "showError", "containerClassName", "resetToken"], outputs: ["valueChange"] }, { kind: "component", type: SceInlineApiErrorComponent, selector: "sce-inline-api-error", inputs: ["message", "className", "retryable"], outputs: ["retry"] }, { kind: "component", type: SceOsmLeafletMapComponent, selector: "sce-osm-leaflet-map", inputs: ["latitude", "longitude", "zoom", "markers", "interactive"], outputs: ["mapClick", "markerDragEnd"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1307
|
+
}
|
|
1308
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SceBaladyMapSelectorComponent, decorators: [{
|
|
1309
|
+
type: Component,
|
|
1310
|
+
args: [{ hostDirectives: [{ directive: SceVariantDirective, inputs: ['sceVariant', 'sceDensity'] }], host: { class: 'sce-balady-map-selector' }, selector: 'sce-balady-map-selector', standalone: true, imports: [CommonModule, SceSelectFieldComponent, SceInlineApiErrorComponent, SceOsmLeafletMapComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"baladyMapSelector\">\n <div class=\"baladyMapSelector__filterHeader\">\n <div class=\"baladyMapSelector__filterTitle\">\n <h4>{{ '\u062A\u062D\u062F\u064A\u062F \u0627\u0644\u0639\u0642\u0627\u0631 \u0645\u0646 \u0627\u0644\u062E\u0631\u064A\u0637\u0629 \u0627\u0644\u062A\u0641\u0627\u0639\u0644\u064A\u0629' }}</h4>\n </div>\n <p class=\"baladyMapSelector__filterSubtitle\">\n \u0627\u062E\u062A\u0631 \u0627\u0644\u0645\u0646\u0637\u0642\u0629 \u0648\u0627\u0644\u0645\u062F\u064A\u0646\u0629 \u0648\u0627\u0644\u062D\u064A \u0644\u062A\u062D\u062F\u064A\u062F \u0627\u0644\u0646\u0637\u0627\u0642\u060C \u062B\u0645 \u0627\u0636\u063A\u0637 \u0639\u0644\u0649 \u0645\u0648\u0642\u0639 \u0627\u0644\u0623\u0631\u0636 \u0645\u0628\u0627\u0634\u0631\u0629 \u0641\u064A \u0627\u0644\u062E\u0631\u064A\u0637\u0629 \u0644\u0627\u0633\u062A\u062E\u0631\u0627\u062C \u062A\u0641\u0627\u0635\u064A\u0644 \u0627\u0644\u0645\u062E\u0637\u0637 \u0648\u0627\u0644\u0642\u0637\u0639\u0629 \u0648\u0627\u0644\u0645\u0633\u0627\u062D\u0629 \u0648\u0627\u0644\u062D\u062F\u0648\u062F.\n </p>\n\n @if (loadError()) {\n <sce-inline-api-error [message]=\"'\u062A\u0639\u0630\u0631 \u062A\u062D\u0645\u064A\u0644 \u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0645\u0646\u0627\u0637\u0642'\" [retryable]=\"true\" (retry)=\"loadRegions()\" />\n }\n </div>\n\n <div class=\"baladyMapSelector__mapContainer\">\n <!-- Floating Filters Bar over Map -->\n <div class=\"baladyMapSelector__floatingFilters\">\n @if (isLoadingFilterData()) {\n <div class=\"baladyMapSelector__filterLoader\" role=\"status\" aria-label=\"\u062C\u0627\u0631\u064A \u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A\">\n <div class=\"baladyMapSelector__miniSpinner\"></div>\n <span>\u062C\u0627\u0631\u064A \u062C\u0644\u0628 \u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u062A\u0635\u0641\u064A\u0629...</span>\n </div>\n }\n\n <div class=\"baladyMapSelector__grid\">\n <sce-select-field\n id=\"balady-filter-region\"\n [label]=\"'\u0627\u0644\u0645\u0646\u0637\u0642\u0629'\"\n [options]=\"regionOptions()\"\n [value]=\"selectedRegionCode()\"\n [disabled]=\"isLoadingFilterData()\"\n [dir]=\"dir()\"\n [showClear]=\"true\"\n (valueChange)=\"onRegionChange($event)\"\n />\n <sce-select-field\n id=\"balady-filter-city\"\n [label]=\"'\u0627\u0644\u0645\u062F\u064A\u0646\u0629'\"\n [options]=\"cityOptions()\"\n [value]=\"selectedCityCode()\"\n [disabled]=\"isLoadingFilterData() || !selectedRegionCode()\"\n [dir]=\"dir()\"\n [showClear]=\"true\"\n (valueChange)=\"onCityChange($event)\"\n />\n <sce-select-field\n id=\"balady-filter-district\"\n [label]=\"'\u0627\u0644\u062D\u064A'\"\n [options]=\"districtOptions()\"\n [value]=\"selectedDistrictCode()\"\n [disabled]=\"isLoadingFilterData() || !selectedCityCode()\"\n [dir]=\"dir()\"\n [showClear]=\"true\"\n (valueChange)=\"onDistrictChange($event)\"\n />\n </div>\n </div>\n\n @if (isQueryingPointData()) {\n <div class=\"baladyMapSelector__overlay\" role=\"status\" aria-label=\"\u062C\u0627\u0631\u064A \u0627\u0644\u0627\u0633\u062A\u0639\u0644\u0627\u0645 \u0639\u0646 \u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0623\u0631\u0636\">\n <div class=\"baladyMapSelector__spinner\"></div>\n <span>\u062C\u0627\u0631\u064A \u0627\u0644\u0627\u0633\u062A\u0639\u0644\u0627\u0645 \u0639\u0646 \u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0639\u0642\u0627\u0631 \u0648\u0627\u0644\u0642\u0637\u0639\u0629...</span>\n </div>\n }\n\n <sce-osm-leaflet-map\n [latitude]=\"currentLat()\"\n [longitude]=\"currentLng()\"\n [zoom]=\"zoomLevel()\"\n [markers]=\"currentMarkers()\"\n [interactive]=\"true\"\n (mapClick)=\"onMapClicked($event)\"\n />\n </div>\n\n @if (selectedPropertyData(); as prop) {\n <div class=\"baladyMapSelector__summaryCard\" role=\"region\" aria-label=\"\u062A\u0641\u0627\u0635\u064A\u0644 \u0627\u0644\u0639\u0642\u0627\u0631 \u0627\u0644\u0645Selected\">\n <div class=\"baladyMapSelector__summaryHeader\">\n <span class=\"baladyMapSelector__statusIcon\">\u2713</span>\n <strong>\u062A\u0645 \u062A\u062D\u062F\u064A\u062F \u0627\u0644\u0639\u0642\u0627\u0631 \u0628\u0646\u062C\u0627\u062D</strong>\n </div>\n <div class=\"baladyMapSelector__summaryGrid\">\n <div class=\"baladyMapSelector__infoItem\">\n <span class=\"label\">\u0627\u0644\u0645\u0646\u0637\u0642\u0629 \u0648\u0627\u0644\u0645\u062F\u064A\u0646\u0629:</span>\n <span class=\"val\">{{ prop.region }} - {{ prop.city }} ({{ prop.districtName }})</span>\n </div>\n <div class=\"baladyMapSelector__infoItem\">\n <span class=\"label\">\u0631\u0642\u0645 \u0627\u0644\u0645\u062E\u0637\u0637:</span>\n <span class=\"val\">{{ prop.planNO ?? '-' }}</span>\n </div>\n <div class=\"baladyMapSelector__infoItem\">\n <span class=\"label\">\u0631\u0642\u0645 \u0627\u0644\u0642\u0637\u0639\u0629:</span>\n <span class=\"val\">{{ prop.parcelNo ?? '-' }}</span>\n </div>\n <div class=\"baladyMapSelector__infoItem\">\n <span class=\"label\">\u0627\u0633\u062A\u062E\u062F\u0627\u0645 \u0627\u0644\u0623\u0631\u0636:</span>\n <span class=\"val\">{{ prop.landuse ?? '-' }}</span>\n </div>\n <div class=\"baladyMapSelector__infoItem\">\n <span class=\"label\">\u0627\u0644\u0645\u0633\u0627\u062D\u0629:</span>\n <span class=\"val\">{{ formatArea(prop.area) }}</span>\n </div>\n @if (prop.directionParcel && prop.directionParcel.length) {\n <!-- Track by index: a parcel can face the same direction twice, and duplicate keys throw NG0955. -->\n @for (dir of prop.directionParcel; track $index) {\n <div class=\"baladyMapSelector__infoItem\">\n <span class=\"label\">\u062D\u062F\u0648\u062F \u0627\u0644\u0639\u0642\u0627\u0631 ({{ dir.direction }}):</span>\n <span class=\"val\">{{ dir.length }} \u0645</span>\n </div>\n }\n }\n </div>\n </div>\n }\n</div>\n", styles: [":host.sce-balady-map-selector{display:block;width:100%}.baladyMapSelector{display:flex;flex-direction:column;gap:1rem;background-color:var(--color-neutral-50);border:1px solid var(--color-neutral-200);border-radius:var(--border-radius-lg, 12px);padding:1.25rem}.baladyMapSelector__filterHeader{display:flex;flex-direction:column;gap:.75rem}.baladyMapSelector__filterTitle{display:flex;align-items:center;gap:.5rem}.baladyMapSelector__filterTitle h4{margin:0;font-size:1.05rem;font-weight:700;color:var(--color-neutral-900)}.baladyMapSelector__badge{display:inline-flex;align-items:center;padding:.2rem .6rem;font-size:.75rem;font-weight:700;border-radius:9999px;background-color:#10b9811a;color:#059669}.baladyMapSelector__filterSubtitle{margin:0;font-size:.875rem;color:var(--color-neutral-600);line-height:1.5}.baladyMapSelector__filterLoader{display:flex;align-items:center;gap:.6rem;padding:.5rem .875rem;border-radius:var(--border-radius-md, 8px);background-color:#10b98114;border:1px solid rgba(16,185,129,.2);color:#047857;font-size:.8125rem;font-weight:600;animation:baladyFadeIn .25s ease-in-out}.baladyMapSelector__miniSpinner{width:16px;height:16px;border:2px solid rgba(16,185,129,.25);border-block-start-color:#059669;border-radius:50%;animation:baladySpin .7s linear infinite}.baladyMapSelector__grid{display:grid;grid-template-columns:repeat(1,1fr);gap:.75rem}@media(min-width:768px){.baladyMapSelector__grid{grid-template-columns:repeat(3,1fr)}}.baladyMapSelector__floatingFilters{position:absolute;top:.75rem;inset-inline-start:.75rem;inset-inline-end:.75rem;z-index:500;display:flex;flex-direction:column;gap:.5rem;background-color:#fffffff0;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--color-neutral-200);border-radius:var(--border-radius-md, 8px);padding:.75rem;box-shadow:0 4px 16px #00000014}.dark-theme .baladyMapSelector__floatingFilters{background-color:#0f172af0;border-color:var(--color-neutral-700)}.baladyMapSelector__mapContainer{position:relative;width:100%;height:480px;border-radius:var(--border-radius-md, 8px);overflow:hidden;border:1px solid var(--color-neutral-200)}.baladyMapSelector__mapContainer sce-osm-leaflet-map{width:100%;height:100%;display:block}.baladyMapSelector__overlay{position:absolute;inset:0;z-index:1000;background-color:#ffffffd9;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;color:var(--color-neutral-900);font-size:.9rem;font-weight:600}.dark-theme .baladyMapSelector__overlay{background-color:#0f172ad9;color:var(--color-neutral-100)}.baladyMapSelector__spinner{width:32px;height:32px;border:3px solid rgba(16,185,129,.2);border-block-start-color:#059669;border-radius:50%;animation:baladySpin .8s linear infinite}.baladyMapSelector__summaryCard{background-color:#10b9810d;border:1px solid rgba(16,185,129,.2);border-radius:var(--border-radius-md, 8px);padding:.875rem 1rem;display:flex;flex-direction:column;gap:.5rem}.baladyMapSelector__summaryHeader{display:flex;align-items:center;gap:.5rem;color:#047857;font-size:.95rem}.baladyMapSelector__summaryHeader strong{font-weight:700}.baladyMapSelector__statusIcon{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:50%;background-color:#059669;color:#fff;font-size:.75rem;font-weight:700}.baladyMapSelector__summaryGrid{display:grid;grid-template-columns:repeat(1,1fr);gap:.5rem}@media(min-width:640px){.baladyMapSelector__summaryGrid{grid-template-columns:repeat(2,1fr)}}@media(min-width:1024px){.baladyMapSelector__summaryGrid{grid-template-columns:repeat(3,1fr)}}.baladyMapSelector__infoItem{display:flex;flex-direction:column}.baladyMapSelector__infoItem .label{font-size:.75rem;color:var(--color-neutral-600)}.baladyMapSelector__infoItem .val{font-size:.875rem;font-weight:600;color:var(--color-neutral-900)}@keyframes baladySpin{to{transform:rotate(360deg)}}@keyframes baladyFadeIn{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}\n"] }]
|
|
1311
|
+
}], ctorParameters: () => [], propDecorators: { locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], dir: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], initialPropertyData: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialPropertyData", required: false }] }], propertySelected: [{ type: i0.Output, args: ["propertySelected"] }], mapClick: [{ type: i0.Output, args: ["mapClick"] }] } });
|
|
1312
|
+
|
|
1313
|
+
/**
|
|
1314
|
+
* `@moatamed-sce/ui-components/maps` — OSM / Leaflet map organisms.
|
|
1315
|
+
*
|
|
1316
|
+
* Kept off the main ui-components barrel so portal shells that import chrome
|
|
1317
|
+
* (sidebar, layout, notifications) do not pull map/Leaflet code into the
|
|
1318
|
+
* initial shared chunk. Marketplace / contracts / miyar import from here.
|
|
1319
|
+
*/
|
|
1320
|
+
|
|
1321
|
+
/**
|
|
1322
|
+
* Generated bundle index. Do not edit.
|
|
1323
|
+
*/
|
|
1324
|
+
|
|
1325
|
+
export { SceBaladyMapSelectorComponent, SceOfficeLocationMapComponent, SceOsmLeafletMapComponent, SceOsmLocationPickerComponent, SceOsmOfficesMapComponent, SceStoreLocationsComponent };
|
|
1326
|
+
//# sourceMappingURL=moatamed-sce-ui-components-maps.mjs.map
|