@ship-ui/core 0.18.5 → 0.18.10

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.
@@ -1,9 +1,18 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, computed, ElementRef, Renderer2, input, ChangeDetectionStrategy, Component, viewChild, effect, HostListener, NgModule, signal, Injectable, DOCUMENT, model, output, ApplicationRef, OutputEmitterRef, TemplateRef, createComponent, isSignal, DestroyRef, PLATFORM_ID, ViewChild, Directive, contentChild, contentChildren, afterNextRender, Injector, HostBinding, untracked, runInInjectionContext, ChangeDetectorRef, viewChildren, ViewContainerRef, EnvironmentInjector } from '@angular/core';
2
+ import { InjectionToken, inject, computed, ElementRef, Renderer2, input, ChangeDetectionStrategy, Component, viewChild, effect, HostListener, NgModule, signal, Injectable, DOCUMENT, model, output, ApplicationRef, OutputEmitterRef, TemplateRef, createComponent, isSignal, DestroyRef, PLATFORM_ID, ViewChild, Directive, untracked, contentChild, contentChildren, afterNextRender, Injector, HostBinding, runInInjectionContext, ChangeDetectorRef, viewChildren, ViewContainerRef, EnvironmentInjector } from '@angular/core';
3
3
  import { isPlatformBrowser, JsonPipe, DatePipe, isPlatformServer, NgTemplateOutlet } from '@angular/common';
4
+ import { ShipButton as ShipButton$1 } from 'ship-ui';
4
5
  import { NgModel } from '@angular/forms';
5
6
  import { SIGNAL } from '@angular/core/primitives/signals';
6
7
 
8
+ const defaultThemeColors = {
9
+ primary: 'hsl(217, 91%, 60%)',
10
+ accent: 'hsl(258, 90%, 66%)',
11
+ warn: 'hsl(37, 92%, 50%)',
12
+ error: 'hsl(0, 84%, 60%)',
13
+ success: 'hsl(159, 84%, 39%)',
14
+ base: 'hsl(0, 0%, 46%)'
15
+ };
7
16
  const SHIP_CONFIG = new InjectionToken('Ship UI Config');
8
17
 
9
18
  function shipComponentClasses(componentName, inputs) {
@@ -40,6 +49,8 @@ function shipComponentClasses(componentName, inputs) {
40
49
  const dynamic = (inputs.dynamic?.() ?? componentConfig?.dynamic) || false;
41
50
  // Resolve readonly (Input > Component Config > false)
42
51
  const readonly = (inputs.readonly?.() ?? componentConfig?.readonly) || false;
52
+ // Resolve alwaysShow (Input > Component Config > false)
53
+ const alwaysShow = (inputs.alwaysShow?.() ?? componentConfig?.alwaysShow) || false;
43
54
  const classList = [];
44
55
  if (color)
45
56
  classList.push(color);
@@ -60,6 +71,8 @@ function shipComponentClasses(componentName, inputs) {
60
71
  classList.push('dynamic');
61
72
  if (readonly)
62
73
  classList.push('readonly');
74
+ if (alwaysShow)
75
+ classList.push('always-show');
63
76
  return classList.join(' ');
64
77
  });
65
78
  }
@@ -654,6 +667,145 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
654
667
  }]
655
668
  }] });
656
669
 
670
+ function contentProjectionSignal(querySelector, options = { childList: true }) {
671
+ const hostElement = inject((ElementRef)).nativeElement;
672
+ const destroyRef = inject(DestroyRef);
673
+ const projectedElementsSignal = signal([], ...(ngDevMode ? [{ debugName: "projectedElementsSignal" }] : /* istanbul ignore next */ []));
674
+ const updateElements = () => {
675
+ projectedElementsSignal.set(Array.from(hostElement.querySelectorAll(querySelector)));
676
+ };
677
+ updateElements();
678
+ if (typeof MutationObserver === 'undefined')
679
+ return projectedElementsSignal.asReadonly();
680
+ const observer = new MutationObserver((mutations) => {
681
+ const hasChildListChanges = mutations.some((mutation) => mutation.type === 'childList');
682
+ if (hasChildListChanges) {
683
+ updateElements();
684
+ }
685
+ });
686
+ observer.observe(hostElement, options);
687
+ destroyRef.onDestroy(() => observer.disconnect());
688
+ return projectedElementsSignal.asReadonly();
689
+ }
690
+
691
+ class ShipAccordion {
692
+ constructor() {
693
+ this.selfElement = inject((ElementRef)).nativeElement;
694
+ this.name = input(`sh-accordion-${Math.random().toString(36).substring(2, 9)}`, ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
695
+ this.value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
696
+ this.allowMultiple = input(false, ...(ngDevMode ? [{ debugName: "allowMultiple" }] : /* istanbul ignore next */ []));
697
+ this.variant = input(null, ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
698
+ this.size = input(null, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
699
+ this.hostClasses = shipComponentClasses('accordion', {
700
+ variant: this.variant,
701
+ size: this.size,
702
+ });
703
+ this.items = contentProjectionSignal('details', {
704
+ childList: true,
705
+ subtree: true,
706
+ attributes: true,
707
+ attributeFilter: ['open', 'value'],
708
+ });
709
+ this.selfElement.addEventListener('toggle', this.onToggle.bind(this), true);
710
+ effect(() => {
711
+ const isMultiple = this.allowMultiple();
712
+ const groupName = this.name();
713
+ const valStr = this.value();
714
+ const vals = valStr ? valStr.split(',').filter((v) => v !== '') : [];
715
+ this.items().forEach((details) => {
716
+ if (!isMultiple) {
717
+ details.setAttribute('name', groupName);
718
+ }
719
+ else {
720
+ details.removeAttribute('name');
721
+ }
722
+ const summary = details.querySelector('summary');
723
+ if (summary && !summary.querySelector('sh-icon')) {
724
+ const icon = document.createElement('sh-icon');
725
+ icon.textContent = 'caret-down';
726
+ summary.appendChild(icon);
727
+ }
728
+ let contentWrapper = details.querySelector(':scope > .content');
729
+ if (!contentWrapper) {
730
+ const newWrapper = document.createElement('div');
731
+ newWrapper.className = 'content';
732
+ const childrenToMove = Array.from(details.childNodes).filter((node) => {
733
+ if (node.nodeType === Node.ELEMENT_NODE &&
734
+ node.tagName.toLowerCase() === 'summary') {
735
+ return false;
736
+ }
737
+ return true;
738
+ });
739
+ childrenToMove.forEach((child) => newWrapper.appendChild(child));
740
+ details.appendChild(newWrapper);
741
+ contentWrapper = newWrapper;
742
+ }
743
+ const itemVal = details.getAttribute('value');
744
+ if (itemVal && vals.includes(itemVal)) {
745
+ // Avoid triggering endless loops by only setting open if it actually changed
746
+ if (!details.open)
747
+ details.open = true;
748
+ }
749
+ else if (itemVal) {
750
+ if (details.open)
751
+ details.open = false;
752
+ }
753
+ });
754
+ });
755
+ }
756
+ onToggle(event) {
757
+ const target = event.target;
758
+ if (target.tagName !== 'DETAILS')
759
+ return;
760
+ if (!this.selfElement.contains(target))
761
+ return;
762
+ const itemVal = target.getAttribute('value');
763
+ if (!itemVal)
764
+ return; // Uncontrolled details element
765
+ const isOpen = target.open;
766
+ if (this.allowMultiple()) {
767
+ let vals = this.value()
768
+ ? this.value()
769
+ .split(',')
770
+ .filter((v) => v !== '')
771
+ : [];
772
+ if (isOpen && !vals.includes(itemVal))
773
+ vals.push(itemVal);
774
+ if (!isOpen)
775
+ vals = vals.filter((v) => v !== itemVal);
776
+ this.value.set(vals.join(','));
777
+ }
778
+ else {
779
+ if (isOpen) {
780
+ this.value.set(itemVal);
781
+ }
782
+ else {
783
+ if (this.value() === itemVal) {
784
+ this.value.set(null);
785
+ }
786
+ }
787
+ }
788
+ }
789
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipAccordion, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
790
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.5", type: ShipAccordion, isStandalone: true, selector: "sh-accordion", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, allowMultiple: { classPropertyName: "allowMultiple", publicName: "allowMultiple", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { properties: { "class.sh-accordion": "true", "class": "hostClasses()" } }, ngImport: i0, template: `
791
+ <ng-content></ng-content>
792
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
793
+ }
794
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipAccordion, decorators: [{
795
+ type: Component,
796
+ args: [{
797
+ selector: 'sh-accordion',
798
+ template: `
799
+ <ng-content></ng-content>
800
+ `,
801
+ changeDetection: ChangeDetectionStrategy.OnPush,
802
+ host: {
803
+ '[class.sh-accordion]': 'true',
804
+ '[class]': 'hostClasses()',
805
+ },
806
+ }]
807
+ }], ctorParameters: () => [], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], allowMultiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowMultiple", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
808
+
657
809
  function classMutationSignal(_element = null) {
658
810
  const element = _element ?? inject(ElementRef).nativeElement;
659
811
  if (!element)
@@ -1788,27 +1940,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
1788
1940
  args: ['document:touchend', ['$event']]
1789
1941
  }] } });
1790
1942
 
1791
- function contentProjectionSignal(querySelector, options = { childList: true }) {
1792
- const hostElement = inject((ElementRef)).nativeElement;
1793
- const destroyRef = inject(DestroyRef);
1794
- const projectedElementsSignal = signal([], ...(ngDevMode ? [{ debugName: "projectedElementsSignal" }] : /* istanbul ignore next */ []));
1795
- const updateElements = () => {
1796
- projectedElementsSignal.set(Array.from(hostElement.querySelectorAll(querySelector)));
1797
- };
1798
- updateElements();
1799
- if (typeof MutationObserver === 'undefined')
1800
- return projectedElementsSignal.asReadonly();
1801
- const observer = new MutationObserver((mutations) => {
1802
- const hasChildListChanges = mutations.some((mutation) => mutation.type === 'childList');
1803
- if (hasChildListChanges) {
1804
- updateElements();
1805
- }
1806
- });
1807
- observer.observe(hostElement, options);
1808
- destroyRef.onDestroy(() => observer.disconnect());
1809
- return projectedElementsSignal.asReadonly();
1810
- }
1811
-
1812
1943
  class ShipSelectionGroup {
1813
1944
  constructor(itemSelector, activeClass) {
1814
1945
  this.itemSelector = itemSelector;
@@ -2112,69 +2243,313 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
2112
2243
  }]
2113
2244
  }], propDecorators: { color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], sharp: [{ type: i0.Input, args: [{ isSignal: true, alias: "sharp", required: false }] }], dynamic: [{ type: i0.Input, args: [{ isSignal: true, alias: "dynamic", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }] } });
2114
2245
 
2115
- // TODOS
2116
- // - Add a color picker input
2117
- // - Add alpha support
2246
+ function hslToRgbExact(h, s, l) {
2247
+ s /= 100;
2248
+ l /= 100;
2249
+ const k = (n) => (n + h / 30) % 12;
2250
+ const a = s * Math.min(l, 1 - l);
2251
+ const f = (n) => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
2252
+ return [Math.round(255 * f(0)), Math.round(255 * f(8)), Math.round(255 * f(4))];
2253
+ }
2254
+ function hsvToRgbExact(h, s, v) {
2255
+ const sNorm = s / 100;
2256
+ const vNorm = v / 100;
2257
+ const c = vNorm * sNorm;
2258
+ const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
2259
+ const m = vNorm - c;
2260
+ let r = 0, g = 0, b = 0;
2261
+ if (h >= 0 && h < 60) {
2262
+ r = c;
2263
+ g = x;
2264
+ b = 0;
2265
+ }
2266
+ else if (h >= 60 && h < 120) {
2267
+ r = x;
2268
+ g = c;
2269
+ b = 0;
2270
+ }
2271
+ else if (h >= 120 && h < 180) {
2272
+ r = 0;
2273
+ g = c;
2274
+ b = x;
2275
+ }
2276
+ else if (h >= 180 && h < 240) {
2277
+ r = 0;
2278
+ g = x;
2279
+ b = c;
2280
+ }
2281
+ else if (h >= 240 && h < 300) {
2282
+ r = x;
2283
+ g = 0;
2284
+ b = c;
2285
+ }
2286
+ else if (h >= 300 && h < 360) {
2287
+ r = c;
2288
+ g = 0;
2289
+ b = x;
2290
+ }
2291
+ return [Math.round((r + m) * 255), Math.round((g + m) * 255), Math.round((b + m) * 255)];
2292
+ }
2293
+ function rgbToHsv(r, g, b) {
2294
+ r /= 255;
2295
+ g /= 255;
2296
+ b /= 255;
2297
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
2298
+ const d = max - min;
2299
+ const v = max;
2300
+ const s = max === 0 ? 0 : d / max;
2301
+ let h = 0;
2302
+ if (max !== min) {
2303
+ switch (max) {
2304
+ case r:
2305
+ h = (g - b) / d + (g < b ? 6 : 0);
2306
+ break;
2307
+ case g:
2308
+ h = (b - r) / d + 2;
2309
+ break;
2310
+ case b:
2311
+ h = (r - g) / d + 4;
2312
+ break;
2313
+ }
2314
+ h /= 6;
2315
+ }
2316
+ return { h: h * 360, s: s * 100, v: v * 100 };
2317
+ }
2318
+ function rgbToHex(r, g, b) {
2319
+ return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
2320
+ }
2321
+ function rgbaToHex8(r, g, b, a) {
2322
+ const alphaHex = Math.round(a * 255)
2323
+ .toString(16)
2324
+ .padStart(2, '0');
2325
+ return rgbToHex(r, g, b) + alphaHex;
2326
+ }
2327
+ function rgbToHsl(r, g, b) {
2328
+ r /= 255;
2329
+ g /= 255;
2330
+ b /= 255;
2331
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
2332
+ let h = 0, s = 0, l = (max + min) / 2;
2333
+ if (max === min) {
2334
+ h = s = 0;
2335
+ }
2336
+ else {
2337
+ const d = max - min;
2338
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
2339
+ switch (max) {
2340
+ case r:
2341
+ h = (g - b) / d + (g < b ? 6 : 0);
2342
+ break;
2343
+ case g:
2344
+ h = (b - r) / d + 2;
2345
+ break;
2346
+ case b:
2347
+ h = (r - g) / d + 4;
2348
+ break;
2349
+ }
2350
+ h /= 6;
2351
+ }
2352
+ const hDeg = Math.floor(h * 360);
2353
+ const sPct = Math.round(s * 100);
2354
+ const lPct = Math.round(l * 100);
2355
+ return {
2356
+ h: hDeg,
2357
+ s: sPct,
2358
+ l: lPct,
2359
+ string: `hsl(${hDeg}, ${sPct}%, ${lPct}%)`,
2360
+ };
2361
+ }
2362
+ function rgbToOklch(r, g, b) {
2363
+ // Convert sRGB to Linear sRGB
2364
+ const toLinear = (c) => {
2365
+ const v = c / 255;
2366
+ return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
2367
+ };
2368
+ const lr = toLinear(r);
2369
+ const lg = toLinear(g);
2370
+ const lb = toLinear(b);
2371
+ // Convert Linear sRGB to LMS (Oklab intermediate)
2372
+ const l = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb;
2373
+ const m = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb;
2374
+ const s = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb;
2375
+ // Cube root of LMS
2376
+ const l_ = Math.cbrt(l);
2377
+ const m_ = Math.cbrt(m);
2378
+ const s_ = Math.cbrt(s);
2379
+ // Convert LMS to OKLAB
2380
+ const oklabL = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_;
2381
+ const oklabA = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_;
2382
+ const oklabB = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_;
2383
+ // Convert OKLAB to OKLCH
2384
+ const chroma = Math.sqrt(oklabA * oklabA + oklabB * oklabB);
2385
+ let hue = Math.atan2(oklabB, oklabA) * (180 / Math.PI);
2386
+ if (hue < 0)
2387
+ hue += 360;
2388
+ return { l: oklabL, c: chroma, h: hue };
2389
+ }
2390
+ function hslToOklch(h, s, l) {
2391
+ const [r, g, b] = hslToRgbExact(h, s, l);
2392
+ return rgbToOklch(r, g, b);
2393
+ }
2394
+
2118
2395
  class ShipColorPicker {
2119
2396
  constructor() {
2120
2397
  this.#document = inject(DOCUMENT);
2398
+ this.#platformId = inject(PLATFORM_ID);
2121
2399
  this.canvasRef = viewChild.required('colorCanvas');
2122
- this.canvasData = signal(null, ...(ngDevMode ? [{ debugName: "canvasData" }] : /* istanbul ignore next */ []));
2400
+ this.#canvasData = signal(null, ...(ngDevMode ? [{ debugName: "#canvasData" }] : /* istanbul ignore next */ []));
2123
2401
  this.showDarkColors = input(false, ...(ngDevMode ? [{ debugName: "showDarkColors" }] : /* istanbul ignore next */ []));
2124
2402
  this.renderingType = input('hsl', ...(ngDevMode ? [{ debugName: "renderingType" }] : /* istanbul ignore next */ []));
2125
2403
  this.gridSize = input(20, ...(ngDevMode ? [{ debugName: "gridSize" }] : /* istanbul ignore next */ []));
2126
- this.hue = input(0, ...(ngDevMode ? [{ debugName: "hue" }] : /* istanbul ignore next */ []));
2404
+ this.hue = model(0, ...(ngDevMode ? [{ debugName: "hue" }] : /* istanbul ignore next */ []));
2127
2405
  this.direction = input('horizontal', ...(ngDevMode ? [{ debugName: "direction" }] : /* istanbul ignore next */ []));
2128
- this.selectedColor = model([255, 255, 255], ...(ngDevMode ? [{ debugName: "selectedColor" }] : /* istanbul ignore next */ []));
2406
+ this.selectedColor = model([255, 255, 255, 1], ...(ngDevMode ? [{ debugName: "selectedColor" }] : /* istanbul ignore next */ []));
2407
+ this.alpha = model(1, ...(ngDevMode ? [{ debugName: "alpha" }] : /* istanbul ignore next */ []));
2129
2408
  this.currentColor = output();
2130
2409
  this.centerLightness = computed(() => (this.showDarkColors() ? 200 : 100), ...(ngDevMode ? [{ debugName: "centerLightness" }] : /* istanbul ignore next */ []));
2131
2410
  this.isDragging = signal(false, ...(ngDevMode ? [{ debugName: "isDragging" }] : /* istanbul ignore next */ []));
2132
2411
  this.markerPosition = signal({ x: '50%', y: '50%' }, ...(ngDevMode ? [{ debugName: "markerPosition" }] : /* istanbul ignore next */ []));
2133
2412
  this._pos = { x: '0', y: '0' };
2134
2413
  this._markerPosition = effect(() => (this._pos = this.markerPosition()), ...(ngDevMode ? [{ debugName: "_markerPosition" }] : /* istanbul ignore next */ []));
2135
- this.selectedColorRgb = computed(() => `rgb(${this.selectedColor().join(',')})`, ...(ngDevMode ? [{ debugName: "selectedColorRgb" }] : /* istanbul ignore next */ []));
2136
- this.selectedColorHex = computed(() => this.rgbToHex(...this.selectedColor()), ...(ngDevMode ? [{ debugName: "selectedColorHex" }] : /* istanbul ignore next */ []));
2137
- this.selectedColorHsl = computed(() => this.rgbToHsl(...this.selectedColor()), ...(ngDevMode ? [{ debugName: "selectedColorHsl" }] : /* istanbul ignore next */ []));
2414
+ this.#skipMarkerUpdate = false;
2415
+ this.selectedColorRgb = computed(() => {
2416
+ const c = this.selectedColor();
2417
+ return c[3] !== undefined ? `rgba(${c[0]},${c[1]},${c[2]},${c[3]})` : `rgb(${c[0]},${c[1]},${c[2]})`;
2418
+ }, ...(ngDevMode ? [{ debugName: "selectedColorRgb" }] : /* istanbul ignore next */ []));
2419
+ this.selectedColorHex = computed(() => rgbToHex(...this.selectedColor()), ...(ngDevMode ? [{ debugName: "selectedColorHex" }] : /* istanbul ignore next */ []));
2420
+ this.selectedColorHsl = computed(() => rgbToHsl(...this.selectedColor()).string, ...(ngDevMode ? [{ debugName: "selectedColorHsl" }] : /* istanbul ignore next */ []));
2421
+ this.alphaEffect = effect(() => {
2422
+ const a = this.alpha();
2423
+ const current = untracked(() => this.selectedColor());
2424
+ if (current[3] !== a) {
2425
+ this.#skipMarkerUpdate = false;
2426
+ this.selectedColor.set([current[0], current[1], current[2], a]);
2427
+ }
2428
+ }, ...(ngDevMode ? [{ debugName: "alphaEffect" }] : /* istanbul ignore next */ []));
2429
+ this._prevColorStr = '';
2138
2430
  this.selectedColorEffect = effect(() => {
2139
2431
  const selectedColor = this.selectedColor();
2140
- const hsl = this.rgbToHsl(...selectedColor);
2141
- const hex = this.rgbToHex(...selectedColor);
2142
- this.updateMarkerFromColor(selectedColor);
2432
+ const r = selectedColor[0];
2433
+ const g = selectedColor[1];
2434
+ const b = selectedColor[2];
2435
+ const a = selectedColor[3] ?? 1;
2436
+ untracked(() => {
2437
+ if (this.alpha() !== a) {
2438
+ this.alpha.set(a);
2439
+ }
2440
+ });
2441
+ const str = `${r},${g},${b},${a}`;
2442
+ if (this._prevColorStr === str && !this.#skipMarkerUpdate) {
2443
+ // We still want to clear skipMarkerUpdate if it was set
2444
+ // wait, actually if skipMarkerUpdate is true, it means we JUST dragged.
2445
+ // In that case we do want to emit currentColor.
2446
+ }
2447
+ const hsl = rgbToHsl(r, g, b);
2448
+ const hex = rgbToHex(r, g, b);
2449
+ if (this.#skipMarkerUpdate) {
2450
+ this.#skipMarkerUpdate = false;
2451
+ this._prevColorStr = str;
2452
+ }
2453
+ else {
2454
+ if (this._prevColorStr !== str) {
2455
+ this.updateMarkerFromColor(selectedColor);
2456
+ this._prevColorStr = str;
2457
+ }
2458
+ else {
2459
+ // Color is structurally identical and not from a drag event, skip marker jump
2460
+ }
2461
+ }
2143
2462
  this.currentColor.emit({
2144
- rgb: `rgb(${selectedColor.join(',')})`,
2463
+ rgb: `rgb(${r}, ${g}, ${b})`,
2464
+ rgba: `rgba(${r}, ${g}, ${b}, ${a})`,
2145
2465
  hex: hex,
2146
- hsl: hsl,
2147
- hue: hsl.match(/\d+/g).map(Number)[0],
2148
- saturation: hsl.match(/\d+/g).map(Number)[1],
2466
+ hex8: rgbaToHex8(r, g, b, a),
2467
+ hsl: hsl.string,
2468
+ hsla: `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${a})`,
2469
+ hue: hsl.h,
2470
+ saturation: hsl.s,
2471
+ alpha: a,
2149
2472
  });
2150
2473
  }, ...(ngDevMode ? [{ debugName: "selectedColorEffect" }] : /* istanbul ignore next */ []));
2474
+ this.alphaColorRedrawEffect = effect(() => {
2475
+ const color = this.selectedColor();
2476
+ if (this.renderingType() === 'alpha' && this.#canvasData()) {
2477
+ untracked(() => this.drawAlpha());
2478
+ }
2479
+ }, ...(ngDevMode ? [{ debugName: "alphaColorRedrawEffect" }] : /* istanbul ignore next */ []));
2480
+ this.previousLayoutHash = '';
2481
+ this.previousHue = null;
2151
2482
  this.renderingTypeEffect = effect(() => {
2152
- const currentRenderingType = this.renderingType();
2153
- if (this.canvasData()) {
2154
- this.drawColorPicker();
2155
- if (currentRenderingType === 'hsl') {
2156
- this.adjustMarkerPosition();
2157
- queueMicrotask(() => this.updateMarkerFromColor(this.selectedColor()));
2483
+ const currentHue = this.hue();
2484
+ const layoutHash = this.getLayoutHash();
2485
+ const canvasData = untracked(() => this.#canvasData());
2486
+ if (canvasData) {
2487
+ untracked(() => this.drawColorPicker());
2488
+ if (this.previousLayoutHash !== layoutHash) {
2489
+ if (this.renderingType() === 'hsl') {
2490
+ this.adjustMarkerPosition();
2491
+ queueMicrotask(() => this.updateMarkerFromColor(untracked(() => this.selectedColor())));
2492
+ }
2493
+ else {
2494
+ this.updateMarkerFromColor(untracked(() => this.selectedColor()));
2495
+ }
2496
+ this.previousLayoutHash = layoutHash;
2497
+ this.previousHue = currentHue;
2158
2498
  }
2159
- else {
2160
- this.updateMarkerFromColor(this.selectedColor());
2499
+ else if (this.previousHue !== currentHue) {
2500
+ const pos = untracked(() => this.markerPosition());
2501
+ const { canvas, ctx } = canvasData;
2502
+ let x = (parseFloat(pos.x.replace('%', '')) / 100) * Math.max(1, canvas.width - 1);
2503
+ let y = (parseFloat(pos.y.replace('%', '')) / 100) * Math.max(1, canvas.height - 1);
2504
+ x = Math.max(0, Math.min(canvas.width - 1, Math.round(x)));
2505
+ y = Math.max(0, Math.min(canvas.height - 1, Math.round(y)));
2506
+ const color = untracked(() => this.getColorAtPosition(x, y));
2507
+ if (this.renderingType() !== 'alpha') {
2508
+ color[3] = untracked(() => this.selectedColor()[3] ?? 1);
2509
+ }
2510
+ this.#skipMarkerUpdate = true;
2511
+ this.selectedColor.set(color);
2512
+ this.previousHue = currentHue;
2161
2513
  }
2162
2514
  }
2163
2515
  }, ...(ngDevMode ? [{ debugName: "renderingTypeEffect" }] : /* istanbul ignore next */ []));
2516
+ this.#resizeObserver = typeof ResizeObserver !== 'undefined'
2517
+ ? new ResizeObserver((entries) => {
2518
+ for (const entry of entries) {
2519
+ if (entry.contentRect.width > 0 && entry.contentRect.height > 0) {
2520
+ this.setCanvasSize();
2521
+ }
2522
+ }
2523
+ })
2524
+ : null;
2164
2525
  this.initColor = null;
2165
2526
  }
2166
2527
  #document;
2528
+ #platformId;
2529
+ #canvasData;
2530
+ #skipMarkerUpdate;
2167
2531
  onResize() {
2168
2532
  this.setCanvasSize();
2169
2533
  }
2534
+ getLayoutHash() {
2535
+ return `${this.renderingType()}-${this.direction()}-${this.gridSize()}-${this.showDarkColors()}`;
2536
+ }
2537
+ #resizeObserver;
2170
2538
  ngAfterViewInit() {
2171
2539
  this.initColor = this.selectedColor();
2540
+ const canvas = this.canvasRef()?.nativeElement;
2541
+ if (canvas?.parentElement) {
2542
+ this.#resizeObserver?.observe(canvas.parentElement);
2543
+ }
2172
2544
  this.setCanvasSize();
2173
2545
  this.initCanvasEvents();
2174
2546
  }
2175
- updateMarkerFromColor(rgb) {
2176
- const [r, g, b] = rgb;
2177
- const coords = this.findPositionByColor(r, g, b);
2547
+ ngOnDestroy() {
2548
+ this.#resizeObserver?.disconnect();
2549
+ }
2550
+ updateMarkerFromColor(rgba) {
2551
+ const [r, g, b, a] = rgba;
2552
+ const coords = this.findPositionByColor(r, g, b, a);
2178
2553
  if (coords === null)
2179
2554
  return;
2180
2555
  const { x, y } = coords;
@@ -2187,11 +2562,63 @@ class ShipColorPicker {
2187
2562
  };
2188
2563
  this.updateColorAndMarker(mockEvent, false, true);
2189
2564
  }
2190
- findPositionByColor(r, g, b) {
2191
- const canvasData = this.canvasData();
2565
+ findPositionByColor(r, g, b, a) {
2566
+ const canvasData = this.#canvasData();
2192
2567
  if (!canvasData || !canvasData.canvas)
2193
2568
  return null;
2194
2569
  const { canvas, ctx } = canvasData;
2570
+ if (canvas.width === 0 || canvas.height === 0)
2571
+ return null;
2572
+ if (this.renderingType() === 'alpha') {
2573
+ const aVal = a ?? 1;
2574
+ if (this.direction() === 'horizontal') {
2575
+ return { x: Math.round(aVal * (canvas.width - 1)), y: Math.round((canvas.height - 1) / 2) };
2576
+ }
2577
+ else {
2578
+ return { x: Math.round((canvas.width - 1) / 2), y: Math.round(aVal * (canvas.height - 1)) };
2579
+ }
2580
+ }
2581
+ if (this.renderingType() === 'hue') {
2582
+ const hsl = rgbToHsl(r, g, b);
2583
+ const ratio = hsl.h / 360;
2584
+ if (this.direction() === 'horizontal') {
2585
+ return { x: Math.round(ratio * (canvas.width - 1)), y: Math.round((canvas.height - 1) / 2) };
2586
+ }
2587
+ else {
2588
+ return { x: Math.round((canvas.width - 1) / 2), y: Math.round(ratio * (canvas.height - 1)) };
2589
+ }
2590
+ }
2591
+ if (this.renderingType() === 'rgb') {
2592
+ const hsv = rgbToHsv(r, g, b);
2593
+ return {
2594
+ x: Math.round((hsv.s / 100) * (canvas.width - 1)),
2595
+ y: Math.round((1 - hsv.v / 100) * (canvas.height - 1)),
2596
+ };
2597
+ }
2598
+ if (this.renderingType() === 'saturation') {
2599
+ const hsl = rgbToHsl(r, g, b);
2600
+ const ratio = hsl.s / 100;
2601
+ if (this.direction() === 'horizontal') {
2602
+ return { x: Math.round(ratio * (canvas.width - 1)), y: Math.round((canvas.height - 1) / 2) };
2603
+ }
2604
+ else {
2605
+ return { x: Math.round((canvas.width - 1) / 2), y: Math.round(ratio * (canvas.height - 1)) };
2606
+ }
2607
+ }
2608
+ if (this.renderingType() === 'hsl') {
2609
+ const { centerX, centerY, radius } = canvasData;
2610
+ const hsl = rgbToHsl(r, g, b);
2611
+ const centerL = this.centerLightness();
2612
+ let distance = 0;
2613
+ if (centerL > 0) {
2614
+ distance = ((100 - hsl.l) * radius) / ((50 * centerL) / 100);
2615
+ }
2616
+ const angle = (hsl.h / 360) * 2 * Math.PI - Math.PI;
2617
+ return {
2618
+ x: Math.round(centerX + distance * Math.cos(angle)),
2619
+ y: Math.round(centerY + distance * Math.sin(angle)),
2620
+ };
2621
+ }
2195
2622
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
2196
2623
  let bestMatch = { x: 0, y: 0, distance: Infinity };
2197
2624
  for (let y = 0; y < canvas.height; y++) {
@@ -2209,7 +2636,7 @@ class ShipColorPicker {
2209
2636
  return { x: bestMatch.x, y: bestMatch.y };
2210
2637
  }
2211
2638
  adjustMarkerPosition() {
2212
- const { canvas, centerX, centerY, radius } = this.canvasData();
2639
+ const { canvas, centerX, centerY, radius } = this.#canvasData();
2213
2640
  let { x, y } = this._pos;
2214
2641
  let markerX = (parseFloat(x.replace('%', '')) / 100) * canvas.width;
2215
2642
  let markerY = (parseFloat(y.replace('%', '')) / 100) * canvas.height;
@@ -2224,7 +2651,10 @@ class ShipColorPicker {
2224
2651
  }
2225
2652
  }
2226
2653
  initCanvasEvents() {
2227
- const { canvas } = this.canvasData();
2654
+ const data = this.#canvasData();
2655
+ if (!data)
2656
+ return;
2657
+ const { canvas } = data;
2228
2658
  canvas.addEventListener('mousedown', (event) => {
2229
2659
  this.isDragging.set(true);
2230
2660
  this.updateColorAndMarker(event);
@@ -2247,6 +2677,8 @@ class ShipColorPicker {
2247
2677
  this.#document.addEventListener('touchcancel', () => this.isDragging.set(false));
2248
2678
  }
2249
2679
  setCanvasSize() {
2680
+ if (!isPlatformBrowser(this.#platformId))
2681
+ return;
2250
2682
  const canvas = this.canvasRef()?.nativeElement;
2251
2683
  if (canvas) {
2252
2684
  const ctx = canvas.getContext('2d', {
@@ -2257,7 +2689,7 @@ class ShipColorPicker {
2257
2689
  const parentWidth = canvas.parentElement?.offsetWidth || canvas.offsetWidth;
2258
2690
  canvas.width = parentWidth;
2259
2691
  canvas.height = parentWidth;
2260
- this.canvasData.set({
2692
+ this.#canvasData.set({
2261
2693
  canvas,
2262
2694
  ctx,
2263
2695
  centerX: canvas.width / 2,
@@ -2265,10 +2697,42 @@ class ShipColorPicker {
2265
2697
  radius: Math.min(canvas.width, canvas.height) / 2,
2266
2698
  });
2267
2699
  this.drawColorPicker();
2700
+ setTimeout(() => {
2701
+ if (!this.isDragging()) {
2702
+ this.updateMarkerFromColor(untracked(() => this.selectedColor()));
2703
+ }
2704
+ });
2705
+ }
2706
+ }
2707
+ getColorAtPosition(mouseX, mouseY) {
2708
+ const { canvas, ctx } = this.#canvasData();
2709
+ const w = Math.max(1, canvas.width - 1);
2710
+ const h = Math.max(1, canvas.height - 1);
2711
+ const xRatio = mouseX / w;
2712
+ const yRatio = mouseY / h;
2713
+ if (this.renderingType() === 'rgb') {
2714
+ return hsvToRgbExact(this.hue(), xRatio * 100, (1 - yRatio) * 100);
2715
+ }
2716
+ else if (this.renderingType() === 'saturation') {
2717
+ const ratio = this.direction() === 'horizontal' ? xRatio : yRatio;
2718
+ return hslToRgbExact(this.hue(), ratio * 100, 50);
2719
+ }
2720
+ else if (this.renderingType() === 'hue') {
2721
+ const ratio = this.direction() === 'horizontal' ? xRatio : yRatio;
2722
+ return hslToRgbExact(ratio * 360, 100, 50);
2723
+ }
2724
+ else if (this.renderingType() === 'alpha') {
2725
+ const ratio = this.direction() === 'horizontal' ? xRatio : yRatio;
2726
+ const current = this.selectedColor();
2727
+ return [current[0], current[1], current[2], parseFloat(ratio.toFixed(2))];
2728
+ }
2729
+ else {
2730
+ const pixelData = ctx.getImageData(mouseX, mouseY, 1, 1).data;
2731
+ return [pixelData[0], pixelData[1], pixelData[2]];
2268
2732
  }
2269
2733
  }
2270
2734
  updateColorAndMarker(event, outsideCanvas = false, onlyMarker = false) {
2271
- const { canvas, ctx } = this.canvasData();
2735
+ const { canvas, ctx } = this.#canvasData();
2272
2736
  let mouseX = event instanceof MouseEvent ? event.offsetX : event.clientX;
2273
2737
  let mouseY = event instanceof MouseEvent ? event.offsetY : event.clientY;
2274
2738
  if (outsideCanvas) {
@@ -2289,13 +2753,16 @@ class ShipColorPicker {
2289
2753
  }
2290
2754
  mouseX = Math.max(0, Math.min(canvas.width - 1, Math.round(mouseX)));
2291
2755
  mouseY = Math.max(0, Math.min(canvas.height - 1, Math.round(mouseY)));
2292
- const pixelData = ctx.getImageData(mouseX, mouseY, 1, 1).data;
2293
- const [r, g, b] = pixelData;
2294
2756
  if (!onlyMarker) {
2295
- this.selectedColor.set([r, g, b]);
2757
+ const newColor = this.getColorAtPosition(mouseX, mouseY);
2758
+ if (this.renderingType() !== 'alpha') {
2759
+ newColor[3] = this.selectedColor()[3] ?? 1;
2760
+ }
2761
+ this.#skipMarkerUpdate = true;
2762
+ this.selectedColor.set(newColor);
2296
2763
  }
2297
- const xPercent = ((mouseX / canvas.width) * 100).toFixed(2) + '%';
2298
- const yPercent = ((mouseY / canvas.height) * 100).toFixed(2) + '%';
2764
+ const xPercent = ((mouseX / Math.max(1, canvas.width - 1)) * 100).toFixed(2) + '%';
2765
+ const yPercent = ((mouseY / Math.max(1, canvas.height - 1)) * 100).toFixed(2) + '%';
2299
2766
  this.markerPosition.set({ x: xPercent, y: yPercent });
2300
2767
  }
2301
2768
  drawColorPicker() {
@@ -2309,6 +2776,9 @@ class ShipColorPicker {
2309
2776
  case 'hue':
2310
2777
  this.drawHue();
2311
2778
  break;
2779
+ case 'alpha':
2780
+ this.drawAlpha();
2781
+ break;
2312
2782
  case 'rgb':
2313
2783
  this.drawRgb();
2314
2784
  break;
@@ -2317,32 +2787,48 @@ class ShipColorPicker {
2317
2787
  break;
2318
2788
  }
2319
2789
  }
2790
+ drawAlpha() {
2791
+ const { canvas, ctx } = this.#canvasData();
2792
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
2793
+ const w = Math.max(1, canvas.width - 1);
2794
+ const h = Math.max(1, canvas.height - 1);
2795
+ const gradient = ctx.createLinearGradient(0, 0, this.direction() === 'horizontal' ? w : 0, this.direction() === 'horizontal' ? 0 : h);
2796
+ const [r, g, b] = this.selectedColor();
2797
+ gradient.addColorStop(0, `rgba(${r}, ${g}, ${b}, 0)`);
2798
+ gradient.addColorStop(1, `rgba(${r}, ${g}, ${b}, 1)`);
2799
+ ctx.fillStyle = gradient;
2800
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
2801
+ }
2320
2802
  drawRgb() {
2321
- const { canvas, ctx } = this.canvasData();
2803
+ const { canvas, ctx } = this.#canvasData();
2322
2804
  ctx.clearRect(0, 0, canvas.width, canvas.height);
2323
- const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
2324
- gradient.addColorStop(0, 'white');
2805
+ const w = Math.max(1, canvas.width - 1);
2806
+ const h = Math.max(1, canvas.height - 1);
2807
+ const gradient = ctx.createLinearGradient(0, 0, w, 0);
2808
+ gradient.addColorStop(0, '#ffffff');
2325
2809
  gradient.addColorStop(1, `hsl(${this.hue()}, 100%, 50%)`);
2326
2810
  ctx.fillStyle = gradient;
2327
2811
  ctx.fillRect(0, 0, canvas.width, canvas.height);
2328
- const gradient2 = ctx.createLinearGradient(0, 0, 0, canvas.height);
2812
+ const gradient2 = ctx.createLinearGradient(0, 0, 0, h);
2329
2813
  gradient2.addColorStop(0, 'rgba(0, 0, 0, 0)');
2330
- gradient2.addColorStop(1, 'black');
2814
+ gradient2.addColorStop(1, '#000000');
2331
2815
  ctx.fillStyle = gradient2;
2332
2816
  ctx.fillRect(0, 0, canvas.width, canvas.height);
2333
2817
  }
2334
2818
  drawSaturation() {
2335
- const { canvas, ctx } = this.canvasData();
2819
+ const { canvas, ctx } = this.#canvasData();
2336
2820
  ctx.clearRect(0, 0, canvas.width, canvas.height);
2821
+ const w = Math.max(1, canvas.width - 1);
2822
+ const h = Math.max(1, canvas.height - 1);
2337
2823
  if (this.direction() === 'horizontal') {
2338
- const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
2824
+ const gradient = ctx.createLinearGradient(0, 0, w, 0);
2339
2825
  gradient.addColorStop(0, `hsl(${this.hue()}, 0%, 50%)`);
2340
2826
  gradient.addColorStop(1, `hsl(${this.hue()}, 100%, 50%)`);
2341
2827
  ctx.fillStyle = gradient;
2342
2828
  ctx.fillRect(0, 0, canvas.width, canvas.height);
2343
2829
  }
2344
2830
  else {
2345
- const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
2831
+ const gradient = ctx.createLinearGradient(0, 0, 0, h);
2346
2832
  gradient.addColorStop(0, `hsl(${this.hue()}, 0%, 50%)`);
2347
2833
  gradient.addColorStop(1, `hsl(${this.hue()}, 100%, 50%)`);
2348
2834
  ctx.fillStyle = gradient;
@@ -2350,9 +2836,11 @@ class ShipColorPicker {
2350
2836
  }
2351
2837
  }
2352
2838
  drawHue() {
2353
- const { canvas, ctx } = this.canvasData();
2839
+ const { canvas, ctx } = this.#canvasData();
2354
2840
  ctx.clearRect(0, 0, canvas.width, canvas.height);
2355
- const gradient = ctx.createLinearGradient(0, 0, this.direction() === 'horizontal' ? canvas.width : 0, this.direction() === 'horizontal' ? 0 : canvas.height);
2841
+ const w = Math.max(1, canvas.width - 1);
2842
+ const h = Math.max(1, canvas.height - 1);
2843
+ const gradient = ctx.createLinearGradient(0, 0, this.direction() === 'horizontal' ? w : 0, this.direction() === 'horizontal' ? 0 : h);
2356
2844
  for (let i = 0; i <= 360; i += 10) {
2357
2845
  gradient.addColorStop(i / 360, `hsl(${i}, 100%, 50%)`);
2358
2846
  }
@@ -2360,7 +2848,7 @@ class ShipColorPicker {
2360
2848
  ctx.fillRect(0, 0, canvas.width, canvas.height);
2361
2849
  }
2362
2850
  drawColorWheel() {
2363
- const { canvas, ctx, centerX, centerY, radius } = this.canvasData();
2851
+ const { canvas, ctx, centerX, centerY, radius } = this.#canvasData();
2364
2852
  ctx.clearRect(0, 0, canvas.width, canvas.height);
2365
2853
  for (let y = 0; y < canvas.height; y++) {
2366
2854
  for (let x = 0; x < canvas.width; x++) {
@@ -2375,7 +2863,7 @@ class ShipColorPicker {
2375
2863
  }
2376
2864
  }
2377
2865
  drawGrid() {
2378
- const { canvas, ctx } = this.canvasData();
2866
+ const { canvas, ctx } = this.#canvasData();
2379
2867
  ctx.clearRect(0, 0, canvas.width, canvas.height);
2380
2868
  const gridSize = this.gridSize();
2381
2869
  const cellSize = canvas.width / gridSize;
@@ -2396,38 +2884,8 @@ class ShipColorPicker {
2396
2884
  }
2397
2885
  }
2398
2886
  }
2399
- rgbToHex(r, g, b) {
2400
- return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
2401
- }
2402
- rgbToHsl(r, g, b) {
2403
- r /= 255;
2404
- g /= 255;
2405
- b /= 255;
2406
- const max = Math.max(r, g, b), min = Math.min(r, g, b);
2407
- let h = 0, s = 0, l = (max + min) / 2;
2408
- if (max === min) {
2409
- h = s = 0;
2410
- }
2411
- else {
2412
- const d = max - min;
2413
- s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
2414
- switch (max) {
2415
- case r:
2416
- h = (g - b) / d + (g < b ? 6 : 0);
2417
- break;
2418
- case g:
2419
- h = (b - r) / d + 2;
2420
- break;
2421
- case b:
2422
- h = (r - g) / d + 4;
2423
- break;
2424
- }
2425
- h /= 6;
2426
- }
2427
- return `hsl(${Math.floor(h * 360)}, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%)`;
2428
- }
2429
2887
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipColorPicker, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2430
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.5", type: ShipColorPicker, isStandalone: true, selector: "sh-color-picker", inputs: { showDarkColors: { classPropertyName: "showDarkColors", publicName: "showDarkColors", isSignal: true, isRequired: false, transformFunction: null }, renderingType: { classPropertyName: "renderingType", publicName: "renderingType", isSignal: true, isRequired: false, transformFunction: null }, gridSize: { classPropertyName: "gridSize", publicName: "gridSize", isSignal: true, isRequired: false, transformFunction: null }, hue: { classPropertyName: "hue", publicName: "hue", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, selectedColor: { classPropertyName: "selectedColor", publicName: "selectedColor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedColor: "selectedColorChange", currentColor: "currentColor" }, host: { listeners: { "window:resize": "onResize()" }, properties: { "class": "renderingType()", "class.vertical": "(renderingType() === \"hue\" || renderingType() === \"saturation\") && direction() === \"vertical\"" } }, viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["colorCanvas"], descendants: true, isSignal: true }], ngImport: i0, template: `
2888
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.5", type: ShipColorPicker, isStandalone: true, selector: "sh-color-picker", inputs: { showDarkColors: { classPropertyName: "showDarkColors", publicName: "showDarkColors", isSignal: true, isRequired: false, transformFunction: null }, renderingType: { classPropertyName: "renderingType", publicName: "renderingType", isSignal: true, isRequired: false, transformFunction: null }, gridSize: { classPropertyName: "gridSize", publicName: "gridSize", isSignal: true, isRequired: false, transformFunction: null }, hue: { classPropertyName: "hue", publicName: "hue", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, selectedColor: { classPropertyName: "selectedColor", publicName: "selectedColor", isSignal: true, isRequired: false, transformFunction: null }, alpha: { classPropertyName: "alpha", publicName: "alpha", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { hue: "hueChange", selectedColor: "selectedColorChange", alpha: "alphaChange", currentColor: "currentColor" }, host: { listeners: { "window:resize": "onResize()" }, properties: { "class": "renderingType()", "class.vertical": "(renderingType() === \"hue\" || renderingType() === \"saturation\") && direction() === \"vertical\"" } }, viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["colorCanvas"], descendants: true, isSignal: true }], ngImport: i0, template: `
2431
2889
  <canvas #colorCanvas></canvas>
2432
2890
  <div
2433
2891
  class="marker"
@@ -2455,636 +2913,368 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
2455
2913
  '[class.vertical]': '(renderingType() === "hue" || renderingType() === "saturation") && direction() === "vertical"',
2456
2914
  },
2457
2915
  }]
2458
- }], propDecorators: { canvasRef: [{ type: i0.ViewChild, args: ['colorCanvas', { isSignal: true }] }], showDarkColors: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDarkColors", required: false }] }], renderingType: [{ type: i0.Input, args: [{ isSignal: true, alias: "renderingType", required: false }] }], gridSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridSize", required: false }] }], hue: [{ type: i0.Input, args: [{ isSignal: true, alias: "hue", required: false }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], selectedColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedColor", required: false }] }, { type: i0.Output, args: ["selectedColorChange"] }], currentColor: [{ type: i0.Output, args: ["currentColor"] }], onResize: [{
2916
+ }], propDecorators: { canvasRef: [{ type: i0.ViewChild, args: ['colorCanvas', { isSignal: true }] }], showDarkColors: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDarkColors", required: false }] }], renderingType: [{ type: i0.Input, args: [{ isSignal: true, alias: "renderingType", required: false }] }], gridSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridSize", required: false }] }], hue: [{ type: i0.Input, args: [{ isSignal: true, alias: "hue", required: false }] }, { type: i0.Output, args: ["hueChange"] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], selectedColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedColor", required: false }] }, { type: i0.Output, args: ["selectedColorChange"] }], alpha: [{ type: i0.Input, args: [{ isSignal: true, alias: "alpha", required: false }] }, { type: i0.Output, args: ["alphaChange"] }], currentColor: [{ type: i0.Output, args: ["currentColor"] }], onResize: [{
2459
2917
  type: HostListener,
2460
2918
  args: ['window:resize', []]
2461
2919
  }] } });
2462
2920
 
2463
- class ShipDatepicker {
2921
+ const BASE_SPACE = 4;
2922
+ const SCROLLABLE_STYLES = ['scroll', 'auto'];
2923
+ const DEFAULT_OPTIONS = {
2924
+ width: undefined,
2925
+ height: undefined,
2926
+ closeOnButton: true,
2927
+ closeOnEsc: true,
2928
+ };
2929
+ class ShipPopover {
2464
2930
  constructor() {
2465
- this.#INIT_DATE = new Date(new Date().setHours(0, 0, 0, 0));
2466
- this.date = model(null, ...(ngDevMode ? [{ debugName: "date" }] : /* istanbul ignore next */ []));
2467
- this.endDate = model(null, ...(ngDevMode ? [{ debugName: "endDate" }] : /* istanbul ignore next */ []));
2468
- this.asRange = input(false, ...(ngDevMode ? [{ debugName: "asRange" }] : /* istanbul ignore next */ []));
2469
- this.monthsToShow = input(1, ...(ngDevMode ? [{ debugName: "monthsToShow" }] : /* istanbul ignore next */ []));
2470
- this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
2471
- this.startOfWeek = input(1, ...(ngDevMode ? [{ debugName: "startOfWeek" }] : /* istanbul ignore next */ []));
2472
- this.weekdayLabels = input(['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], ...(ngDevMode ? [{ debugName: "weekdayLabels" }] : /* istanbul ignore next */ []));
2473
- this.daysRef = viewChild('daysRef', ...(ngDevMode ? [{ debugName: "daysRef" }] : /* istanbul ignore next */ []));
2474
- this.currentDate = signal(this.date() ?? this.#INIT_DATE, ...(ngDevMode ? [{ debugName: "currentDate" }] : /* istanbul ignore next */ []));
2475
- this.monthOffsets = computed(() => {
2476
- return Array.from({ length: this.monthsToShow() }, (_, i) => i);
2477
- }, ...(ngDevMode ? [{ debugName: "monthOffsets" }] : /* istanbul ignore next */ []));
2478
- this.selectedDateStylePosition = signal(null, ...(ngDevMode ? [{ debugName: "selectedDateStylePosition" }] : /* istanbul ignore next */ []));
2479
- this.weekdays = computed(() => {
2480
- const startOfWeek = this.startOfWeek();
2481
- const weekdayLabels = this.weekdayLabels();
2482
- return weekdayLabels.slice(startOfWeek).concat(weekdayLabels.slice(0, startOfWeek));
2483
- }, ...(ngDevMode ? [{ debugName: "weekdays" }] : /* istanbul ignore next */ []));
2484
- this.currentClasses = classMutationSignal();
2485
- this.someEffect = effect(() => {
2486
- const _ = this.currentClasses();
2487
- this.#findSelectedAndCalc();
2488
- }, ...(ngDevMode ? [{ debugName: "someEffect" }] : /* istanbul ignore next */ []));
2489
- this.#newDateEffect = effect(() => {
2490
- if (this.monthsToShow() > 1)
2491
- return;
2492
- this.#setDateAsCurrent();
2493
- }, ...(ngDevMode ? [{ debugName: "#newDateEffect" }] : /* istanbul ignore next */ []));
2494
- }
2495
- #INIT_DATE;
2496
- getLastVisibleMonth() {
2497
- const lastMonthOffset = this.monthsToShow() - 1;
2498
- return this.getOffsetDate(lastMonthOffset);
2499
- }
2500
- getOffsetDate(monthOffset) {
2501
- const date = new Date(this.currentDate());
2502
- date.setMonth(date.getMonth() + monthOffset);
2503
- return date;
2931
+ this.#document = inject(DOCUMENT);
2932
+ this.SUPPORTS_ANCHOR = typeof CSS !== 'undefined' && CSS.supports('position-anchor', '--abc') && CSS.supports('anchor-name', '--abc');
2933
+ this.asMultiLayer = input(false, ...(ngDevMode ? [{ debugName: "asMultiLayer" }] : /* istanbul ignore next */ []));
2934
+ this.disableOpenByClick = input(false, ...(ngDevMode ? [{ debugName: "disableOpenByClick" }] : /* istanbul ignore next */ []));
2935
+ this.isOpen = model(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
2936
+ this.options = input(...(ngDevMode ? [undefined, { debugName: "options" }] : /* istanbul ignore next */ []));
2937
+ this.closed = output();
2938
+ this.defaultOptionMerge = computed(() => ({
2939
+ ...DEFAULT_OPTIONS,
2940
+ ...this.options(),
2941
+ }), ...(ngDevMode ? [{ debugName: "defaultOptionMerge" }] : /* istanbul ignore next */ []));
2942
+ this.triggerRef = viewChild.required('triggerRef');
2943
+ this.popoverRef = viewChild('popoverRef', ...(ngDevMode ? [{ debugName: "popoverRef" }] : /* istanbul ignore next */ []));
2944
+ this.popoverContentRef = viewChild('popoverContentRef', ...(ngDevMode ? [{ debugName: "popoverContentRef" }] : /* istanbul ignore next */ []));
2945
+ this.id = signal('--' + generateUniqueId(), ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
2946
+ this.menuStyle = signal(null, ...(ngDevMode ? [{ debugName: "menuStyle" }] : /* istanbul ignore next */ []));
2947
+ this.openAbort = null;
2948
+ this.openEffect = effect(() => {
2949
+ const open = this.isOpen();
2950
+ queueMicrotask(() => {
2951
+ const popoverEl = this.popoverRef()?.nativeElement;
2952
+ if (!popoverEl) {
2953
+ this.openAbort?.abort();
2954
+ this.openAbort = null;
2955
+ return;
2956
+ }
2957
+ if (open) {
2958
+ if (this.openAbort) {
2959
+ this.openAbort.abort();
2960
+ }
2961
+ this.openAbort = new AbortController();
2962
+ this.#document.addEventListener('keydown', (e) => {
2963
+ if (e.key === 'Escape' && !this.defaultOptionMerge().closeOnEsc) {
2964
+ e.preventDefault();
2965
+ }
2966
+ if (e.key === 'Escape' && this.defaultOptionMerge().closeOnEsc) {
2967
+ this.isOpen.set(false);
2968
+ }
2969
+ }, {
2970
+ signal: this.openAbort?.signal,
2971
+ });
2972
+ if (!popoverEl.isConnected)
2973
+ return;
2974
+ popoverEl.showPopover();
2975
+ if (!this.SUPPORTS_ANCHOR) {
2976
+ setTimeout(() => {
2977
+ const scrollableParent = this.#findScrollableParent(popoverEl);
2978
+ scrollableParent.addEventListener('scroll', () => this.#calculateMenuPosition(), {
2979
+ signal: this.openAbort?.signal,
2980
+ });
2981
+ window?.addEventListener('resize', () => this.#calculateMenuPosition(), {
2982
+ signal: this.openAbort?.signal,
2983
+ });
2984
+ this.#calculateMenuPosition();
2985
+ });
2986
+ }
2987
+ }
2988
+ else {
2989
+ this.closed.emit();
2990
+ popoverEl.hidePopover();
2991
+ this.openAbort?.abort();
2992
+ this.openAbort = null;
2993
+ }
2994
+ });
2995
+ }, ...(ngDevMode ? [{ debugName: "openEffect" }] : /* istanbul ignore next */ []));
2504
2996
  }
2505
- getMonthDates(monthOffset) {
2506
- const offsetDate = this.getOffsetDate(monthOffset);
2507
- return this.#generateMonthDates(offsetDate, this.startOfWeek());
2997
+ #document;
2998
+ toggleIsOpen(event) {
2999
+ if (!this.disableOpenByClick()) {
3000
+ event.preventDefault();
3001
+ event.stopPropagation();
3002
+ this.isOpen.set(!this.isOpen());
3003
+ }
2508
3004
  }
2509
- #newDateEffect;
2510
- ngOnInit() {
2511
- if (this.monthsToShow() === 1)
3005
+ eventClose($event) {
3006
+ if (!this.isOpen())
2512
3007
  return;
2513
- this.#setDateAsCurrent();
2514
- }
2515
- #setDateAsCurrent() {
2516
- const newDate = this.date();
2517
- if (newDate)
2518
- this.currentDate.set(newDate);
2519
- this.#findSelectedAndCalc();
3008
+ this.isOpen.set(false);
2520
3009
  }
2521
- #findSelectedAndCalc() {
2522
- setTimeout(() => {
2523
- const selectedElement = this.daysRef()?.nativeElement.querySelector('.sel');
2524
- if (!selectedElement) {
2525
- return this.selectedDateStylePosition.update((x) => (x ? { ...x, opacity: '0' } : null));
3010
+ #findScrollableParent(element) {
3011
+ let parent = element.parentElement;
3012
+ while (parent) {
3013
+ if (SCROLLABLE_STYLES.indexOf(window?.getComputedStyle(parent).overflowY) > -1 &&
3014
+ parent.scrollHeight > parent.clientHeight) {
3015
+ return parent;
2526
3016
  }
2527
- this.setSelectedDateStylePosition(selectedElement);
2528
- });
2529
- }
2530
- #generateMonthDates(date, startOfWeek) {
2531
- const year = date.getFullYear();
2532
- const month = date.getMonth();
2533
- const firstDay = new Date(year, month, 1).getDay();
2534
- const daysInMonth = new Date(year, month + 1, 0).getDate();
2535
- const dates = [];
2536
- let offset = firstDay - startOfWeek;
2537
- if (offset < 0) {
2538
- offset += 7;
2539
- }
2540
- const lastDayOfPrevMonth = new Date(year, month, 0).getDate();
2541
- for (let i = offset - 1; i >= 0; i--) {
2542
- dates.push(new Date(year, month - 1, lastDayOfPrevMonth - i, 0, 0, 0, 0));
2543
- }
2544
- for (let i = 1; i <= daysInMonth; i++) {
2545
- dates.push(new Date(year, month, i, 0, 0, 0, 0));
2546
- }
2547
- let nextMonthDay = 1;
2548
- while (dates.length % 7 !== 0) {
2549
- dates.push(new Date(year, month + 1, nextMonthDay++, 0, 0, 0, 0));
3017
+ parent = parent.parentElement;
2550
3018
  }
2551
- return dates;
3019
+ return this.#document.documentElement;
2552
3020
  }
2553
- nextMonth() {
2554
- this.currentDate.update((currentDate) => {
2555
- const newDate = new Date(currentDate);
2556
- newDate.setMonth(currentDate.getMonth() + 1);
2557
- return newDate;
2558
- });
2559
- this.#findSelectedAndCalc();
3021
+ /**
3022
+ * Position generators that mirror the CSS position-try-fallbacks.
3023
+ * Each returns { left, top } for the popover-content in fixed coordinates.
3024
+ */
3025
+ // bottom span-right: below trigger, left edge aligned with trigger left
3026
+ #bottomSpanRight(t, _m) {
3027
+ return { left: t.left, top: t.bottom + BASE_SPACE };
2560
3028
  }
2561
- previousMonth() {
2562
- this.currentDate.update((currentDate) => {
2563
- const newDate = new Date(currentDate);
2564
- newDate.setMonth(currentDate.getMonth() - 1);
2565
- return newDate;
2566
- });
2567
- this.#findSelectedAndCalc();
3029
+ // top span-right: above trigger, left edge aligned with trigger left
3030
+ #topSpanRight(t, m) {
3031
+ return { left: t.left, top: t.top - m.height - BASE_SPACE };
2568
3032
  }
2569
- setDate(newDate, selectedElement) {
2570
- const createDateWithExistingTime = (newDate, existingDate) => {
2571
- const hours = existingDate?.getHours() ?? 0;
2572
- const minutes = existingDate?.getMinutes() ?? 0;
2573
- const seconds = existingDate?.getSeconds() ?? 0;
2574
- const milliseconds = existingDate?.getMilliseconds() ?? 0;
2575
- return new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate(), hours, minutes, seconds, milliseconds);
2576
- };
2577
- if (!this.asRange()) {
2578
- this.date.set(createDateWithExistingTime(newDate, this.date()));
2579
- this.endDate.set(null);
2580
- }
2581
- else {
2582
- const startDate = this.date();
2583
- const endDate = this.endDate();
2584
- const utcDate = createDateWithExistingTime(newDate, null);
2585
- if (!startDate) {
2586
- this.date.set(utcDate);
2587
- }
2588
- else if (!endDate) {
2589
- if (utcDate < startDate) {
2590
- this.date.set(utcDate);
2591
- this.endDate.set(null);
2592
- }
2593
- else {
2594
- this.endDate.set(utcDate);
2595
- }
2596
- }
2597
- else {
2598
- this.date.set(utcDate);
2599
- this.endDate.set(null);
2600
- }
2601
- }
2602
- if (this.asRange())
2603
- return;
2604
- this.setSelectedDateStylePosition(selectedElement);
3033
+ // bottom span-left: below trigger, right edge aligned with trigger right
3034
+ #bottomSpanLeft(t, m) {
3035
+ return { left: t.right - m.width, top: t.bottom + BASE_SPACE };
2605
3036
  }
2606
- isDateSelected(date) {
2607
- const startDate = this.date();
2608
- const endDate = this.endDate();
2609
- if (startDate === null)
2610
- return null;
2611
- const startOfDay = (date) => {
2612
- return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
2613
- };
2614
- const endOfDay = (date) => {
2615
- return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999);
2616
- };
2617
- const currentDate = startOfDay(date);
2618
- const rangeStart = startOfDay(startDate);
2619
- const rangeEnd = endDate ? endOfDay(endDate) : null;
2620
- let classes = [];
2621
- if (this.asRange()) {
2622
- if (rangeEnd === null) {
2623
- if (currentDate.getTime() === rangeStart.getTime()) {
2624
- classes.push('sel first last');
2625
- }
2626
- }
2627
- else {
2628
- if (currentDate.getTime() === rangeStart.getTime()) {
2629
- classes.push('first');
2630
- }
2631
- if (currentDate.getTime() === startOfDay(rangeEnd).getTime()) {
2632
- classes.push('last');
2633
- }
2634
- if (currentDate >= rangeStart && currentDate <= rangeEnd) {
2635
- classes.push('sel');
2636
- const dayOfWeek = currentDate.getDay();
2637
- const startOfWeek = this.startOfWeek();
2638
- if (dayOfWeek === startOfWeek) {
2639
- classes.push('week-start');
2640
- }
2641
- const endOfWeek = (startOfWeek + 6) % 7;
2642
- if (dayOfWeek === endOfWeek) {
2643
- classes.push('week-end');
2644
- }
2645
- }
2646
- const nextDate = new Date(currentDate);
2647
- nextDate.setDate(currentDate.getDate() + 1);
2648
- const prevDate = new Date(currentDate);
2649
- prevDate.setDate(currentDate.getDate() - 1);
2650
- const isFirstOfMonth = currentDate.getDate() === 1;
2651
- const isLastOfMonth = nextDate.getMonth() !== currentDate.getMonth();
2652
- if (isFirstOfMonth) {
2653
- classes.push('month-start');
2654
- }
2655
- if (isLastOfMonth) {
2656
- classes.push('month-end');
2657
- }
2658
- }
2659
- }
2660
- else {
2661
- if (currentDate.getTime() === rangeStart.getTime()) {
2662
- classes.push('sel');
2663
- }
2664
- }
2665
- return classes.join(' ') || null;
3037
+ // top span-left: above trigger, right edge aligned with trigger right
3038
+ #topSpanLeft(t, m) {
3039
+ return { left: t.right - m.width, top: t.top - m.height - BASE_SPACE };
2666
3040
  }
2667
- setSelectedDateStylePosition(selectedElement) {
2668
- this.selectedDateStylePosition.set({
2669
- transform: `translate(${selectedElement.offsetLeft}px, ${selectedElement.offsetTop}px)`,
2670
- opacity: '1',
2671
- });
3041
+ // right span-bottom: to the right of trigger, top edge aligned with trigger top
3042
+ #rightSpanBottom(t, _m) {
3043
+ return { left: t.right + BASE_SPACE, top: t.top };
2672
3044
  }
2673
- getMonthName(date) {
2674
- return date.toLocaleString('default', { month: 'long' });
3045
+ // left span-bottom: to the left of trigger, top edge aligned with trigger top
3046
+ #leftSpanBottom(t, m) {
3047
+ return { left: t.left - m.width - BASE_SPACE, top: t.top };
2675
3048
  }
2676
- getFullYear(date) {
2677
- return date.getFullYear();
3049
+ // right center: to the right of trigger, vertically centered
3050
+ #rightCenter(t, m) {
3051
+ return { left: t.right + BASE_SPACE, top: t.top + t.height / 2 - m.height / 2 };
2678
3052
  }
2679
- // Rest of the component methods remain the same, but update isCurrentMonth:
2680
- isCurrentMonth(date, monthOffset) {
2681
- const offsetDate = this.getOffsetDate(monthOffset);
2682
- return date.getMonth() === offsetDate.getMonth();
3053
+ // left center: to the left of trigger, vertically centered
3054
+ #leftCenter(t, m) {
3055
+ return { left: t.left - m.width - BASE_SPACE, top: t.top + t.height / 2 - m.height / 2 };
2683
3056
  }
2684
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipDatepicker, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2685
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ShipDatepicker, isStandalone: true, selector: "sh-datepicker", inputs: { date: { classPropertyName: "date", publicName: "date", isSignal: true, isRequired: false, transformFunction: null }, endDate: { classPropertyName: "endDate", publicName: "endDate", isSignal: true, isRequired: false, transformFunction: null }, asRange: { classPropertyName: "asRange", publicName: "asRange", isSignal: true, isRequired: false, transformFunction: null }, monthsToShow: { classPropertyName: "monthsToShow", publicName: "monthsToShow", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, startOfWeek: { classPropertyName: "startOfWeek", publicName: "startOfWeek", isSignal: true, isRequired: false, transformFunction: null }, weekdayLabels: { classPropertyName: "weekdayLabels", publicName: "weekdayLabels", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { date: "dateChange", endDate: "endDateChange" }, host: { properties: { "class.as-range": "asRange()", "class": "\"columns-\" + monthsToShow()", "class.disabled": "disabled()" } }, viewQueries: [{ propertyName: "daysRef", first: true, predicate: ["daysRef"], descendants: true, isSignal: true }], ngImport: i0, template: `
2686
- <header>
2687
- <button (click)="previousMonth()"><sh-icon>caret-left</sh-icon></button>
2688
- <div class="title">
2689
- {{ getMonthName(currentDate()!) }}
2690
- @if (monthsToShow() > 1) {
2691
- - {{ getMonthName(getLastVisibleMonth()) }}
3057
+ // right span-top: to the right of trigger, bottom edge aligned with trigger bottom
3058
+ #rightSpanTop(t, m) {
3059
+ return { left: t.right + BASE_SPACE, top: t.bottom - m.height };
3060
+ }
3061
+ // left span-top: to the left of trigger, bottom edge aligned with trigger bottom
3062
+ #leftSpanTop(t, m) {
3063
+ return { left: t.left - m.width - BASE_SPACE, top: t.bottom - m.height };
3064
+ }
3065
+ /** Check if a position fits entirely within the viewport */
3066
+ #fitsInViewport(pos, m) {
3067
+ return (pos.left >= 0 &&
3068
+ pos.top >= 0 &&
3069
+ pos.left + m.width <= window.innerWidth &&
3070
+ pos.top + m.height <= window.innerHeight);
3071
+ }
3072
+ /** Clamp a position so the popover stays within the viewport */
3073
+ #clampToViewport(pos, m) {
3074
+ return {
3075
+ left: Math.max(0, Math.min(pos.left, window.innerWidth - m.width)),
3076
+ top: Math.max(0, Math.min(pos.top, window.innerHeight - m.height)),
3077
+ };
3078
+ }
3079
+ #calculateMenuPosition() {
3080
+ const triggerRect = this.triggerRef()?.nativeElement.getBoundingClientRect();
3081
+ const menuRect = this.popoverContentRef()?.nativeElement.getBoundingClientRect();
3082
+ if (!triggerRect || !menuRect)
3083
+ return;
3084
+ // Mirror the CSS position-try-fallbacks order
3085
+ const tryOrderDefault = [
3086
+ this.#bottomSpanRight,
3087
+ this.#topSpanRight,
3088
+ this.#bottomSpanLeft,
3089
+ this.#topSpanLeft,
3090
+ this.#rightSpanBottom,
3091
+ this.#leftSpanBottom,
3092
+ this.#rightCenter,
3093
+ this.#leftCenter,
3094
+ this.#rightSpanTop,
3095
+ this.#leftSpanTop,
3096
+ ];
3097
+ const tryOrderMultiLayer = [
3098
+ this.#rightSpanBottom,
3099
+ this.#rightSpanTop,
3100
+ this.#leftSpanBottom,
3101
+ this.#leftSpanTop,
3102
+ this.#rightCenter,
3103
+ this.#leftCenter,
3104
+ this.#bottomSpanRight,
3105
+ this.#topSpanRight,
3106
+ this.#bottomSpanLeft,
3107
+ this.#topSpanLeft,
3108
+ ];
3109
+ const tryOrder = this.asMultiLayer() ? tryOrderMultiLayer : tryOrderDefault;
3110
+ // Try each position, use the first one that fits
3111
+ for (const positionFn of tryOrder) {
3112
+ const pos = positionFn.call(this, triggerRect, menuRect);
3113
+ if (this.#fitsInViewport(pos, menuRect)) {
3114
+ this.menuStyle.set({ left: pos.left + 'px', top: pos.top + 'px' });
3115
+ return;
3116
+ }
2692
3117
  }
2693
- {{ getFullYear(currentDate()!) }}
3118
+ // If nothing fits perfectly, use the first position clamped to viewport
3119
+ const fallback = this.#clampToViewport(tryOrder[0].call(this, triggerRect, menuRect), menuRect);
3120
+ this.menuStyle.set({ left: fallback.left + 'px', top: fallback.top + 'px' });
3121
+ }
3122
+ ngOnDestroy() {
3123
+ this.openAbort?.abort();
3124
+ this.openAbort = null;
3125
+ }
3126
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipPopover, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3127
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ShipPopover, isStandalone: true, selector: "sh-popover", inputs: { asMultiLayer: { classPropertyName: "asMultiLayer", publicName: "asMultiLayer", isSignal: true, isRequired: false, transformFunction: null }, disableOpenByClick: { classPropertyName: "disableOpenByClick", publicName: "disableOpenByClick", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isOpen: "isOpenChange", closed: "closed" }, host: { properties: { "class.multi-layer": "asMultiLayer()" } }, viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["triggerRef"], descendants: true, isSignal: true }, { propertyName: "popoverRef", first: true, predicate: ["popoverRef"], descendants: true, isSignal: true }, { propertyName: "popoverContentRef", first: true, predicate: ["popoverContentRef"], descendants: true, isSignal: true }], ngImport: i0, template: `
3128
+ <div class="trigger" #triggerRef [attr.popovertarget]="id() + 'hello'" (click)="toggleIsOpen($event)">
3129
+ <div class="trigger-wrapper">
3130
+ <ng-content select="[trigger]" />
3131
+ <ng-content select="button" />
3132
+ <ng-content select="[shButton]" />
2694
3133
  </div>
2695
- <button (click)="nextMonth()"><sh-icon>caret-right</sh-icon></button>
2696
- </header>
2697
-
2698
- <section class="months-container">
2699
- @for (monthOffset of monthOffsets(); track monthOffset) {
2700
- <div class="month">
2701
- <nav class="weekdays">
2702
- @for (day of weekdays(); track $index) {
2703
- <div>{{ day }}</div>
2704
- }
2705
- </nav>
2706
-
2707
- <div class="days" #daysRef>
2708
- @for (calDate of getMonthDates(monthOffset); track $index) {
2709
- <div
2710
- #elementRef
2711
- [class.out-of-scope]="!isCurrentMonth(calDate, monthOffset)"
2712
- [class]="isDateSelected(calDate)"
2713
- (click)="setDate(calDate, elementRef)">
2714
- {{ calDate.getDate() }}
2715
- </div>
2716
- }
3134
+ <div class="trigger-anchor" [style.anchor-name]="id()"></div>
3135
+ </div>
2717
3136
 
2718
- @if (!asRange()) {
2719
- <article class="days">
2720
- <div class="sel-el" [style]="selectedDateStylePosition()"></div>
2721
- </article>
2722
- }
2723
- </div>
3137
+ @if (isOpen()) {
3138
+ <div [attr.id]="id() + 'hello'" popover="manual" #popoverRef class="popover">
3139
+ <div class="overlay" (click)="eventClose($event)"></div>
3140
+ <div class="popover-content" #popoverContentRef [style.position-anchor]="id()" [style]="menuStyle()">
3141
+ <ng-content />
2724
3142
  </div>
2725
- }
2726
- </section>
2727
- `, isInline: true, dependencies: [{ kind: "component", type: ShipIcon, selector: "sh-icon", inputs: ["color", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3143
+ </div>
3144
+ }
3145
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2728
3146
  }
2729
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipDatepicker, decorators: [{
3147
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipPopover, decorators: [{
2730
3148
  type: Component,
2731
3149
  args: [{
2732
- selector: 'sh-datepicker',
2733
- imports: [ShipIcon],
3150
+ selector: 'sh-popover',
3151
+ imports: [],
2734
3152
  template: `
2735
- <header>
2736
- <button (click)="previousMonth()"><sh-icon>caret-left</sh-icon></button>
2737
- <div class="title">
2738
- {{ getMonthName(currentDate()!) }}
2739
- @if (monthsToShow() > 1) {
2740
- - {{ getMonthName(getLastVisibleMonth()) }}
2741
- }
2742
- {{ getFullYear(currentDate()!) }}
3153
+ <div class="trigger" #triggerRef [attr.popovertarget]="id() + 'hello'" (click)="toggleIsOpen($event)">
3154
+ <div class="trigger-wrapper">
3155
+ <ng-content select="[trigger]" />
3156
+ <ng-content select="button" />
3157
+ <ng-content select="[shButton]" />
2743
3158
  </div>
2744
- <button (click)="nextMonth()"><sh-icon>caret-right</sh-icon></button>
2745
- </header>
2746
-
2747
- <section class="months-container">
2748
- @for (monthOffset of monthOffsets(); track monthOffset) {
2749
- <div class="month">
2750
- <nav class="weekdays">
2751
- @for (day of weekdays(); track $index) {
2752
- <div>{{ day }}</div>
2753
- }
2754
- </nav>
2755
-
2756
- <div class="days" #daysRef>
2757
- @for (calDate of getMonthDates(monthOffset); track $index) {
2758
- <div
2759
- #elementRef
2760
- [class.out-of-scope]="!isCurrentMonth(calDate, monthOffset)"
2761
- [class]="isDateSelected(calDate)"
2762
- (click)="setDate(calDate, elementRef)">
2763
- {{ calDate.getDate() }}
2764
- </div>
2765
- }
3159
+ <div class="trigger-anchor" [style.anchor-name]="id()"></div>
3160
+ </div>
2766
3161
 
2767
- @if (!asRange()) {
2768
- <article class="days">
2769
- <div class="sel-el" [style]="selectedDateStylePosition()"></div>
2770
- </article>
2771
- }
2772
- </div>
3162
+ @if (isOpen()) {
3163
+ <div [attr.id]="id() + 'hello'" popover="manual" #popoverRef class="popover">
3164
+ <div class="overlay" (click)="eventClose($event)"></div>
3165
+ <div class="popover-content" #popoverContentRef [style.position-anchor]="id()" [style]="menuStyle()">
3166
+ <ng-content />
2773
3167
  </div>
2774
- }
2775
- </section>
3168
+ </div>
3169
+ }
2776
3170
  `,
2777
3171
  changeDetection: ChangeDetectionStrategy.OnPush,
2778
3172
  host: {
2779
- '[class.as-range]': 'asRange()',
2780
- '[class]': '"columns-" + monthsToShow()',
2781
- '[class.disabled]': 'disabled()',
3173
+ '[class.multi-layer]': 'asMultiLayer()',
2782
3174
  },
2783
3175
  }]
2784
- }], propDecorators: { date: [{ type: i0.Input, args: [{ isSignal: true, alias: "date", required: false }] }, { type: i0.Output, args: ["dateChange"] }], endDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "endDate", required: false }] }, { type: i0.Output, args: ["endDateChange"] }], asRange: [{ type: i0.Input, args: [{ isSignal: true, alias: "asRange", required: false }] }], monthsToShow: [{ type: i0.Input, args: [{ isSignal: true, alias: "monthsToShow", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], startOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "startOfWeek", required: false }] }], weekdayLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekdayLabels", required: false }] }], daysRef: [{ type: i0.ViewChild, args: ['daysRef', { isSignal: true }] }] } });
3176
+ }], propDecorators: { asMultiLayer: [{ type: i0.Input, args: [{ isSignal: true, alias: "asMultiLayer", required: false }] }], disableOpenByClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableOpenByClick", required: false }] }], isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }, { type: i0.Output, args: ["isOpenChange"] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], triggerRef: [{ type: i0.ViewChild, args: ['triggerRef', { isSignal: true }] }], popoverRef: [{ type: i0.ViewChild, args: ['popoverRef', { isSignal: true }] }], popoverContentRef: [{ type: i0.ViewChild, args: ['popoverContentRef', { isSignal: true }] }] } });
2785
3177
 
2786
- const BASE_SPACE = 4;
2787
- const SCROLLABLE_STYLES = ['scroll', 'auto'];
2788
- const DEFAULT_OPTIONS = {
2789
- width: undefined,
2790
- height: undefined,
2791
- closeOnButton: true,
2792
- closeOnEsc: true,
2793
- };
2794
- class ShipPopover {
3178
+ class ShipFormFieldPopover {
2795
3179
  constructor() {
2796
- this.#document = inject(DOCUMENT);
2797
- this.SUPPORTS_ANCHOR = typeof CSS !== 'undefined' && CSS.supports('position-anchor', '--abc') && CSS.supports('anchor-name', '--abc');
2798
- this.asMultiLayer = input(false, ...(ngDevMode ? [{ debugName: "asMultiLayer" }] : /* istanbul ignore next */ []));
2799
- this.disableOpenByClick = input(false, ...(ngDevMode ? [{ debugName: "disableOpenByClick" }] : /* istanbul ignore next */ []));
3180
+ this.#selfRef = inject(ElementRef);
2800
3181
  this.isOpen = model(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
2801
- this.options = input(...(ngDevMode ? [undefined, { debugName: "options" }] : /* istanbul ignore next */ []));
2802
3182
  this.closed = output();
2803
- this.defaultOptionMerge = computed(() => ({
2804
- ...DEFAULT_OPTIONS,
2805
- ...this.options(),
2806
- }), ...(ngDevMode ? [{ debugName: "defaultOptionMerge" }] : /* istanbul ignore next */ []));
2807
- this.triggerRef = viewChild.required('triggerRef');
2808
- this.popoverRef = viewChild('popoverRef', ...(ngDevMode ? [{ debugName: "popoverRef" }] : /* istanbul ignore next */ []));
2809
- this.popoverContentRef = viewChild('popoverContentRef', ...(ngDevMode ? [{ debugName: "popoverContentRef" }] : /* istanbul ignore next */ []));
2810
- this.id = signal('--' + generateUniqueId(), ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
2811
- this.menuStyle = signal(null, ...(ngDevMode ? [{ debugName: "menuStyle" }] : /* istanbul ignore next */ []));
2812
- this.openAbort = null;
2813
- this.openEffect = effect(() => {
2814
- const open = this.isOpen();
2815
- queueMicrotask(() => {
2816
- const popoverEl = this.popoverRef()?.nativeElement;
2817
- if (!popoverEl) {
2818
- this.openAbort?.abort();
2819
- this.openAbort = null;
2820
- return;
2821
- }
2822
- if (open) {
2823
- if (this.openAbort) {
2824
- this.openAbort.abort();
2825
- }
2826
- this.openAbort = new AbortController();
2827
- this.#document.addEventListener('keydown', (e) => {
2828
- if (e.key === 'Escape' && !this.defaultOptionMerge().closeOnEsc) {
2829
- e.preventDefault();
2830
- }
2831
- if (e.key === 'Escape' && this.defaultOptionMerge().closeOnEsc) {
2832
- this.isOpen.set(false);
2833
- }
2834
- }, {
2835
- signal: this.openAbort?.signal,
2836
- });
2837
- if (!popoverEl.isConnected)
2838
- return;
2839
- popoverEl.showPopover();
2840
- if (!this.SUPPORTS_ANCHOR) {
2841
- setTimeout(() => {
2842
- const scrollableParent = this.#findScrollableParent(popoverEl);
2843
- scrollableParent.addEventListener('scroll', () => this.#calculateMenuPosition(), {
2844
- signal: this.openAbort?.signal,
2845
- });
2846
- window?.addEventListener('resize', () => this.#calculateMenuPosition(), {
2847
- signal: this.openAbort?.signal,
2848
- });
2849
- this.#calculateMenuPosition();
2850
- });
2851
- }
2852
- }
2853
- else {
2854
- this.closed.emit();
2855
- popoverEl.hidePopover();
2856
- this.openAbort?.abort();
2857
- this.openAbort = null;
2858
- }
2859
- });
2860
- }, ...(ngDevMode ? [{ debugName: "openEffect" }] : /* istanbul ignore next */ []));
3183
+ this.color = input(null, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
3184
+ this.variant = input(null, ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
3185
+ this.size = input(null, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
3186
+ this.readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
3187
+ this.hostClasses = shipComponentClasses('formField', {
3188
+ color: this.color,
3189
+ variant: this.variant,
3190
+ size: this.size,
3191
+ readonly: this.readonly,
3192
+ });
2861
3193
  }
2862
- #document;
2863
- toggleIsOpen(event) {
2864
- if (!this.disableOpenByClick()) {
2865
- event.preventDefault();
2866
- event.stopPropagation();
2867
- this.isOpen.set(!this.isOpen());
3194
+ #selfRef;
3195
+ onClick() {
3196
+ if (this.#selfRef.nativeElement.querySelector('input')) {
3197
+ this.#selfRef.nativeElement.querySelector('input').focus();
2868
3198
  }
2869
- }
2870
- eventClose($event) {
2871
- if (!this.isOpen())
2872
- return;
2873
- this.isOpen.set(false);
2874
- }
2875
- #findScrollableParent(element) {
2876
- let parent = element.parentElement;
2877
- while (parent) {
2878
- if (SCROLLABLE_STYLES.indexOf(window?.getComputedStyle(parent).overflowY) > -1 &&
2879
- parent.scrollHeight > parent.clientHeight) {
2880
- return parent;
2881
- }
2882
- parent = parent.parentElement;
3199
+ if (this.#selfRef.nativeElement.querySelector('textarea')) {
3200
+ this.#selfRef.nativeElement.querySelector('textarea').focus();
2883
3201
  }
2884
- return this.#document.documentElement;
2885
- }
2886
- /**
2887
- * Position generators that mirror the CSS position-try-fallbacks.
2888
- * Each returns { left, top } for the popover-content in fixed coordinates.
2889
- */
2890
- // bottom span-right: below trigger, left edge aligned with trigger left
2891
- #bottomSpanRight(t, _m) {
2892
- return { left: t.left, top: t.bottom + BASE_SPACE };
2893
- }
2894
- // top span-right: above trigger, left edge aligned with trigger left
2895
- #topSpanRight(t, m) {
2896
- return { left: t.left, top: t.top - m.height - BASE_SPACE };
2897
- }
2898
- // bottom span-left: below trigger, right edge aligned with trigger right
2899
- #bottomSpanLeft(t, m) {
2900
- return { left: t.right - m.width, top: t.bottom + BASE_SPACE };
2901
- }
2902
- // top span-left: above trigger, right edge aligned with trigger right
2903
- #topSpanLeft(t, m) {
2904
- return { left: t.right - m.width, top: t.top - m.height - BASE_SPACE };
2905
- }
2906
- // right span-bottom: to the right of trigger, top edge aligned with trigger top
2907
- #rightSpanBottom(t, _m) {
2908
- return { left: t.right + BASE_SPACE, top: t.top };
2909
- }
2910
- // left span-bottom: to the left of trigger, top edge aligned with trigger top
2911
- #leftSpanBottom(t, m) {
2912
- return { left: t.left - m.width - BASE_SPACE, top: t.top };
2913
- }
2914
- // right center: to the right of trigger, vertically centered
2915
- #rightCenter(t, m) {
2916
- return { left: t.right + BASE_SPACE, top: t.top + t.height / 2 - m.height / 2 };
2917
- }
2918
- // left center: to the left of trigger, vertically centered
2919
- #leftCenter(t, m) {
2920
- return { left: t.left - m.width - BASE_SPACE, top: t.top + t.height / 2 - m.height / 2 };
2921
- }
2922
- // right span-top: to the right of trigger, bottom edge aligned with trigger bottom
2923
- #rightSpanTop(t, m) {
2924
- return { left: t.right + BASE_SPACE, top: t.bottom - m.height };
2925
- }
2926
- // left span-top: to the left of trigger, bottom edge aligned with trigger bottom
2927
- #leftSpanTop(t, m) {
2928
- return { left: t.left - m.width - BASE_SPACE, top: t.bottom - m.height };
2929
- }
2930
- /** Check if a position fits entirely within the viewport */
2931
- #fitsInViewport(pos, m) {
2932
- return (pos.left >= 0 &&
2933
- pos.top >= 0 &&
2934
- pos.left + m.width <= window.innerWidth &&
2935
- pos.top + m.height <= window.innerHeight);
2936
3202
  }
2937
- /** Clamp a position so the popover stays within the viewport */
2938
- #clampToViewport(pos, m) {
2939
- return {
2940
- left: Math.max(0, Math.min(pos.left, window.innerWidth - m.width)),
2941
- top: Math.max(0, Math.min(pos.top, window.innerHeight - m.height)),
2942
- };
3203
+ close() {
3204
+ this.closed.emit();
2943
3205
  }
2944
- #calculateMenuPosition() {
2945
- const triggerRect = this.triggerRef()?.nativeElement.getBoundingClientRect();
2946
- const menuRect = this.popoverContentRef()?.nativeElement.getBoundingClientRect();
2947
- if (!triggerRect || !menuRect)
2948
- return;
2949
- // Mirror the CSS position-try-fallbacks order
2950
- const tryOrderDefault = [
2951
- this.#bottomSpanRight,
2952
- this.#topSpanRight,
2953
- this.#bottomSpanLeft,
2954
- this.#topSpanLeft,
2955
- this.#rightSpanBottom,
2956
- this.#leftSpanBottom,
2957
- this.#rightCenter,
2958
- this.#leftCenter,
2959
- this.#rightSpanTop,
2960
- this.#leftSpanTop,
2961
- ];
2962
- const tryOrderMultiLayer = [
2963
- this.#rightSpanBottom,
2964
- this.#rightSpanTop,
2965
- this.#leftSpanBottom,
2966
- this.#leftSpanTop,
2967
- this.#rightCenter,
2968
- this.#leftCenter,
2969
- this.#bottomSpanRight,
2970
- this.#topSpanRight,
2971
- this.#bottomSpanLeft,
2972
- this.#topSpanLeft,
2973
- ];
2974
- const tryOrder = this.asMultiLayer() ? tryOrderMultiLayer : tryOrderDefault;
2975
- // Try each position, use the first one that fits
2976
- for (const positionFn of tryOrder) {
2977
- const pos = positionFn.call(this, triggerRect, menuRect);
2978
- if (this.#fitsInViewport(pos, menuRect)) {
2979
- this.menuStyle.set({ left: pos.left + 'px', top: pos.top + 'px' });
2980
- return;
3206
+ ngOnInit() {
3207
+ const supportFieldSizing = typeof CSS !== 'undefined' && CSS.supports('field-sizing', 'content');
3208
+ const text = this.#selfRef.nativeElement.querySelector('textarea');
3209
+ if (!supportFieldSizing && text !== null) {
3210
+ const text = this.#selfRef.nativeElement.querySelector('textarea');
3211
+ function resize() {
3212
+ text.style.height = 'auto';
3213
+ text.style.height = text.scrollHeight + 'px';
3214
+ }
3215
+ /* 0-timeout to get the already changed text */
3216
+ function delayedResize() {
3217
+ setTimeout(resize, 0);
3218
+ }
3219
+ if (text) {
3220
+ text.addEventListener('change', resize);
3221
+ text.addEventListener('cut', delayedResize);
3222
+ text.addEventListener('paste', delayedResize);
3223
+ text.addEventListener('drop', delayedResize);
3224
+ text.addEventListener('keydown', delayedResize);
3225
+ text.focus();
3226
+ text.select();
3227
+ resize();
2981
3228
  }
2982
3229
  }
2983
- // If nothing fits perfectly, use the first position clamped to viewport
2984
- const fallback = this.#clampToViewport(tryOrder[0].call(this, triggerRect, menuRect), menuRect);
2985
- this.menuStyle.set({ left: fallback.left + 'px', top: fallback.top + 'px' });
2986
- }
2987
- ngOnDestroy() {
2988
- this.openAbort?.abort();
2989
- this.openAbort = null;
2990
3230
  }
2991
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipPopover, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2992
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ShipPopover, isStandalone: true, selector: "sh-popover", inputs: { asMultiLayer: { classPropertyName: "asMultiLayer", publicName: "asMultiLayer", isSignal: true, isRequired: false, transformFunction: null }, disableOpenByClick: { classPropertyName: "disableOpenByClick", publicName: "disableOpenByClick", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isOpen: "isOpenChange", closed: "closed" }, host: { properties: { "class.multi-layer": "asMultiLayer()" } }, viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["triggerRef"], descendants: true, isSignal: true }, { propertyName: "popoverRef", first: true, predicate: ["popoverRef"], descendants: true, isSignal: true }, { propertyName: "popoverContentRef", first: true, predicate: ["popoverContentRef"], descendants: true, isSignal: true }], ngImport: i0, template: `
2993
- <div class="trigger" #triggerRef [attr.popovertarget]="id() + 'hello'" (click)="toggleIsOpen($event)">
2994
- <div class="trigger-wrapper">
2995
- <ng-content select="[trigger]" />
2996
- <ng-content select="button" />
2997
- <ng-content select="[shButton]" />
2998
- </div>
2999
- <div class="trigger-anchor" [style.anchor-name]="id()"></div>
3000
- </div>
3231
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipFormFieldPopover, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3232
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.5", type: ShipFormFieldPopover, isStandalone: true, selector: "sh-form-field-popover", inputs: { isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isOpen: "isOpenChange", closed: "closed" }, host: { listeners: { "click": "onClick()" }, properties: { "class": "hostClasses()" } }, ngImport: i0, template: `
3233
+ <ng-content select="label"></ng-content>
3001
3234
 
3002
- @if (isOpen()) {
3003
- <div [attr.id]="id() + 'hello'" popover="manual" #popoverRef class="popover">
3004
- <div class="overlay" (click)="eventClose($event)"></div>
3005
- <div class="popover-content" #popoverContentRef [style.position-anchor]="id()" [style]="menuStyle()">
3006
- <ng-content />
3235
+ <sh-popover
3236
+ [(isOpen)]="isOpen"
3237
+ (closed)="close()"
3238
+ [options]="{
3239
+ closeOnButton: false,
3240
+ closeOnEsc: true,
3241
+ }">
3242
+ <div trigger class="input-wrap">
3243
+ <div class="prefix">
3244
+ <ng-content select="[prefix]"></ng-content>
3245
+ <ng-content select="[textPrefix]"></ng-content>
3007
3246
  </div>
3247
+
3248
+ <div class="prefix-space"></div>
3249
+
3250
+ <ng-content select="input"></ng-content>
3251
+
3252
+ <ng-content select="textarea"></ng-content>
3253
+
3254
+ <ng-content select="[textSuffix]"></ng-content>
3255
+ <div class="suffix-space"></div>
3256
+ <ng-content select="[suffix]"></ng-content>
3008
3257
  </div>
3009
- }
3010
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3258
+ <ng-content select="[popoverContent]"></ng-content>
3259
+ </sh-popover>
3260
+
3261
+ <div class="helpers">
3262
+ <div class="error-wrap">
3263
+ <ng-content select="[error]"></ng-content>
3264
+ </div>
3265
+
3266
+ <div class="hint">
3267
+ <ng-content select="[hint]"></ng-content>
3268
+ </div>
3269
+ </div>
3270
+ `, isInline: true, dependencies: [{ kind: "component", type: ShipPopover, selector: "sh-popover", inputs: ["asMultiLayer", "disableOpenByClick", "isOpen", "options"], outputs: ["isOpenChange", "closed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3011
3271
  }
3012
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipPopover, decorators: [{
3272
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipFormFieldPopover, decorators: [{
3013
3273
  type: Component,
3014
3274
  args: [{
3015
- selector: 'sh-popover',
3016
- imports: [],
3275
+ selector: 'sh-form-field-popover',
3276
+ imports: [ShipPopover],
3017
3277
  template: `
3018
- <div class="trigger" #triggerRef [attr.popovertarget]="id() + 'hello'" (click)="toggleIsOpen($event)">
3019
- <div class="trigger-wrapper">
3020
- <ng-content select="[trigger]" />
3021
- <ng-content select="button" />
3022
- <ng-content select="[shButton]" />
3023
- </div>
3024
- <div class="trigger-anchor" [style.anchor-name]="id()"></div>
3025
- </div>
3026
-
3027
- @if (isOpen()) {
3028
- <div [attr.id]="id() + 'hello'" popover="manual" #popoverRef class="popover">
3029
- <div class="overlay" (click)="eventClose($event)"></div>
3030
- <div class="popover-content" #popoverContentRef [style.position-anchor]="id()" [style]="menuStyle()">
3031
- <ng-content />
3032
- </div>
3033
- </div>
3034
- }
3035
- `,
3036
- changeDetection: ChangeDetectionStrategy.OnPush,
3037
- host: {
3038
- '[class.multi-layer]': 'asMultiLayer()',
3039
- },
3040
- }]
3041
- }], propDecorators: { asMultiLayer: [{ type: i0.Input, args: [{ isSignal: true, alias: "asMultiLayer", required: false }] }], disableOpenByClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableOpenByClick", required: false }] }], isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }, { type: i0.Output, args: ["isOpenChange"] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], triggerRef: [{ type: i0.ViewChild, args: ['triggerRef', { isSignal: true }] }], popoverRef: [{ type: i0.ViewChild, args: ['popoverRef', { isSignal: true }] }], popoverContentRef: [{ type: i0.ViewChild, args: ['popoverContentRef', { isSignal: true }] }] } });
3042
-
3043
- class ShipFormFieldPopover {
3044
- constructor() {
3045
- this.#selfRef = inject(ElementRef);
3046
- this.isOpen = model(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
3047
- this.closed = output();
3048
- }
3049
- #selfRef;
3050
- onClick() {
3051
- if (this.#selfRef.nativeElement.querySelector('input')) {
3052
- this.#selfRef.nativeElement.querySelector('input').focus();
3053
- }
3054
- if (this.#selfRef.nativeElement.querySelector('textarea')) {
3055
- this.#selfRef.nativeElement.querySelector('textarea').focus();
3056
- }
3057
- }
3058
- close() {
3059
- this.closed.emit();
3060
- }
3061
- ngOnInit() {
3062
- const supportFieldSizing = typeof CSS !== 'undefined' && CSS.supports('field-sizing', 'content');
3063
- const text = this.#selfRef.nativeElement.querySelector('textarea');
3064
- if (!supportFieldSizing && text !== null) {
3065
- const text = this.#selfRef.nativeElement.querySelector('textarea');
3066
- function resize() {
3067
- text.style.height = 'auto';
3068
- text.style.height = text.scrollHeight + 'px';
3069
- }
3070
- /* 0-timeout to get the already changed text */
3071
- function delayedResize() {
3072
- setTimeout(resize, 0);
3073
- }
3074
- if (text) {
3075
- text.addEventListener('change', resize);
3076
- text.addEventListener('cut', delayedResize);
3077
- text.addEventListener('paste', delayedResize);
3078
- text.addEventListener('drop', delayedResize);
3079
- text.addEventListener('keydown', delayedResize);
3080
- text.focus();
3081
- text.select();
3082
- resize();
3083
- }
3084
- }
3085
- }
3086
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipFormFieldPopover, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3087
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.5", type: ShipFormFieldPopover, isStandalone: true, selector: "sh-form-field-popover", inputs: { isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isOpen: "isOpenChange", closed: "closed" }, host: { listeners: { "click": "onClick()" } }, ngImport: i0, template: `
3088
3278
  <ng-content select="label"></ng-content>
3089
3279
 
3090
3280
  <sh-popover
@@ -3118,62 +3308,727 @@ class ShipFormFieldPopover {
3118
3308
  <ng-content select="[error]"></ng-content>
3119
3309
  </div>
3120
3310
 
3121
- <div class="hint">
3122
- <ng-content select="[hint]"></ng-content>
3123
- </div>
3124
- </div>
3125
- `, isInline: true, dependencies: [{ kind: "component", type: ShipPopover, selector: "sh-popover", inputs: ["asMultiLayer", "disableOpenByClick", "isOpen", "options"], outputs: ["isOpenChange", "closed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3311
+ <div class="hint">
3312
+ <ng-content select="[hint]"></ng-content>
3313
+ </div>
3314
+ </div>
3315
+ `,
3316
+ changeDetection: ChangeDetectionStrategy.OnPush,
3317
+ host: {
3318
+ '[class]': 'hostClasses()',
3319
+ },
3320
+ }]
3321
+ }], propDecorators: { isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }, { type: i0.Output, args: ["isOpenChange"] }], closed: [{ type: i0.Output, args: ["closed"] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], onClick: [{
3322
+ type: HostListener,
3323
+ args: ['click']
3324
+ }] } });
3325
+
3326
+ class ShipColorPickerInput {
3327
+ constructor() {
3328
+ this.#document = inject(DOCUMENT);
3329
+ this.renderingType = input('hsl', ...(ngDevMode ? [{ debugName: "renderingType" }] : /* istanbul ignore next */ []));
3330
+ this.format = input('rgb', ...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
3331
+ this.color = input(null, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
3332
+ this.variant = input(null, ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
3333
+ this.size = input(null, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
3334
+ this.readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
3335
+ this.closed = output();
3336
+ this.isOpen = model(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
3337
+ this.currentClass = classMutationSignal();
3338
+ this.isEyeDropperSupported = typeof window !== 'undefined' && 'EyeDropper' in window;
3339
+ this.showEyeDropper = input(true, ...(ngDevMode ? [{ debugName: "showEyeDropper" }] : /* istanbul ignore next */ []));
3340
+ this.internalHue = signal(0, ...(ngDevMode ? [{ debugName: "internalHue" }] : /* istanbul ignore next */ []));
3341
+ this.internalAlpha = signal(1, ...(ngDevMode ? [{ debugName: "internalAlpha" }] : /* istanbul ignore next */ []));
3342
+ this.internalColorTuple = signal([255, 255, 255, 1], ...(ngDevMode ? [{ debugName: "internalColorTuple" }] : /* istanbul ignore next */ []));
3343
+ this.hasAlpha = computed(() => ['rgba', 'hex8', 'hsla'].includes(this.format()), ...(ngDevMode ? [{ debugName: "hasAlpha" }] : /* istanbul ignore next */ []));
3344
+ this.formattedColorString = computed(() => {
3345
+ const format = this.format();
3346
+ const tuple = this.internalColorTuple();
3347
+ const [r, g, b, aRaw] = tuple;
3348
+ const a = aRaw ?? 1;
3349
+ switch (format) {
3350
+ case 'rgb':
3351
+ return `rgb(${r}, ${g}, ${b})`;
3352
+ case 'rgba':
3353
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
3354
+ case 'hex':
3355
+ return rgbToHex(r, g, b);
3356
+ case 'hex8':
3357
+ return rgbaToHex8(r, g, b, a);
3358
+ case 'hsl':
3359
+ return rgbToHsl(r, g, b).string;
3360
+ case 'hsla': {
3361
+ const hsl = rgbToHsl(r, g, b);
3362
+ return `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${a})`;
3363
+ }
3364
+ default:
3365
+ return `rgb(${r}, ${g}, ${b})`;
3366
+ }
3367
+ }, ...(ngDevMode ? [{ debugName: "formattedColorString" }] : /* istanbul ignore next */ []));
3368
+ this.#formatSyncEffect = effect(() => {
3369
+ const str = this.formattedColorString();
3370
+ const input = untracked(() => this.#inputRef());
3371
+ if (input && input.value !== str) {
3372
+ if (this.#document.activeElement !== input) {
3373
+ input.value = str;
3374
+ input.dispatchEvent(new Event('input'));
3375
+ }
3376
+ }
3377
+ }, ...(ngDevMode ? [{ debugName: "#formatSyncEffect" }] : /* istanbul ignore next */ []));
3378
+ this.pureHueColor = computed(() => {
3379
+ return hslToRgbExact(this.internalHue(), 100, 50);
3380
+ }, ...(ngDevMode ? [{ debugName: "pureHueColor" }] : /* istanbul ignore next */ []));
3381
+ this.#inputRef = signal(null, ...(ngDevMode ? [{ debugName: "#inputRef" }] : /* istanbul ignore next */ []));
3382
+ this.#inputObserver = contentProjectionSignal('#input-wrap input');
3383
+ this.#inputRefEffect = effect(() => {
3384
+ const inputs = this.#inputObserver();
3385
+ if (!inputs.length)
3386
+ return;
3387
+ const input = inputs[0];
3388
+ if (!input)
3389
+ return;
3390
+ this.#createCustomInputEventListener(input);
3391
+ input.addEventListener('inputValueChanged', (event) => {
3392
+ this.parseAndSetColor(event.detail.value);
3393
+ });
3394
+ input.addEventListener('input', (event) => {
3395
+ const target = event.target;
3396
+ this.parseAndSetColor(target.value);
3397
+ });
3398
+ input.addEventListener('blur', () => {
3399
+ const str = untracked(() => this.formattedColorString());
3400
+ if (input.value !== str) {
3401
+ input.value = str;
3402
+ input.dispatchEvent(new Event('input'));
3403
+ }
3404
+ });
3405
+ input.addEventListener('focus', () => {
3406
+ this.isOpen.set(true);
3407
+ });
3408
+ this.#inputRef.set(input);
3409
+ input.autocomplete = 'off';
3410
+ if (typeof input.value === 'string' && input.value) {
3411
+ this.parseAndSetColor(input.value);
3412
+ }
3413
+ }, ...(ngDevMode ? [{ debugName: "#inputRefEffect" }] : /* istanbul ignore next */ []));
3414
+ }
3415
+ #document;
3416
+ #formatSyncEffect;
3417
+ #inputRef;
3418
+ #inputObserver;
3419
+ onMainColorChange(colorObj) {
3420
+ if (colorObj.hue !== undefined) {
3421
+ if (this.renderingType() === 'hsl' && colorObj.saturation > 0) {
3422
+ this.internalHue.set(colorObj.hue);
3423
+ }
3424
+ }
3425
+ }
3426
+ onHuePickerChange(colorObj) {
3427
+ if (colorObj.hue !== undefined && colorObj.hue !== this.internalHue()) {
3428
+ this.internalHue.set(colorObj.hue);
3429
+ }
3430
+ }
3431
+ async openEyeDropper(event) {
3432
+ event.preventDefault();
3433
+ event.stopPropagation();
3434
+ try {
3435
+ // @ts-ignore
3436
+ const eyeDropper = new window.EyeDropper();
3437
+ const result = await eyeDropper.open();
3438
+ if (result && result.sRGBHex) {
3439
+ this.parseAndSetColor(result.sRGBHex);
3440
+ // Force the text field to immediately display the new output string according to the active format
3441
+ const input = untracked(() => this.#inputRef());
3442
+ if (input) {
3443
+ const str = this.formattedColorString();
3444
+ if (input.value !== str) {
3445
+ input.value = str;
3446
+ input.dispatchEvent(new Event('input'));
3447
+ }
3448
+ }
3449
+ }
3450
+ }
3451
+ catch (e) {
3452
+ // User cancelled
3453
+ }
3454
+ }
3455
+ close() {
3456
+ this.closed.emit(this.formattedColorString());
3457
+ }
3458
+ #inputRefEffect;
3459
+ parseAndSetColor(colorStr) {
3460
+ if (!colorStr)
3461
+ return;
3462
+ if (colorStr === untracked(() => this.formattedColorString()))
3463
+ return;
3464
+ const div = this.#document.createElement('div');
3465
+ div.style.color = colorStr;
3466
+ if (div.style.color === '')
3467
+ return; // Not a valid color format
3468
+ this.#document.body.appendChild(div);
3469
+ const computedColor = getComputedStyle(div).color;
3470
+ this.#document.body.removeChild(div);
3471
+ const rgbaMatch = computedColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
3472
+ if (rgbaMatch) {
3473
+ const r = parseInt(rgbaMatch[1], 10);
3474
+ const g = parseInt(rgbaMatch[2], 10);
3475
+ const b = parseInt(rgbaMatch[3], 10);
3476
+ const a = rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1;
3477
+ const current = untracked(() => this.internalColorTuple());
3478
+ if (current[0] !== r || current[1] !== g || current[2] !== b || (current[3] ?? 1) !== a) {
3479
+ this.internalColorTuple.set([r, g, b, a]);
3480
+ this.internalAlpha.set(a);
3481
+ // Also update hue so the hue slider matches the typed color!
3482
+ const max = Math.max(r, g, b) / 255;
3483
+ const min = Math.min(r, g, b) / 255;
3484
+ if (max !== min) {
3485
+ const d = max - min;
3486
+ let h = 0;
3487
+ switch (max) {
3488
+ case r / 255:
3489
+ h = (g / 255 - b / 255) / d + (g / 255 < b / 255 ? 6 : 0);
3490
+ break;
3491
+ case g / 255:
3492
+ h = (b / 255 - r / 255) / d + 2;
3493
+ break;
3494
+ case b / 255:
3495
+ h = (r / 255 - g / 255) / d + 4;
3496
+ break;
3497
+ }
3498
+ h /= 6;
3499
+ this.internalHue.set(Math.floor(h * 360));
3500
+ }
3501
+ }
3502
+ }
3503
+ }
3504
+ hslToRgbExact(h, s, l) {
3505
+ s /= 100;
3506
+ l /= 100;
3507
+ const k = (n) => (n + h / 30) % 12;
3508
+ const a = s * Math.min(l, 1 - l);
3509
+ const f = (n) => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
3510
+ return [Math.round(255 * f(0)), Math.round(255 * f(8)), Math.round(255 * f(4))];
3511
+ }
3512
+ rgbToHex(r, g, b) {
3513
+ return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
3514
+ }
3515
+ rgbaToHex8(r, g, b, a) {
3516
+ const alphaHex = Math.round(a * 255)
3517
+ .toString(16)
3518
+ .padStart(2, '0');
3519
+ return this.rgbToHex(r, g, b) + alphaHex;
3520
+ }
3521
+ rgbToHsl(r, g, b) {
3522
+ r /= 255;
3523
+ g /= 255;
3524
+ b /= 255;
3525
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
3526
+ let h = 0, s = 0, l = (max + min) / 2;
3527
+ if (max === min) {
3528
+ h = s = 0;
3529
+ }
3530
+ else {
3531
+ const d = max - min;
3532
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
3533
+ switch (max) {
3534
+ case r:
3535
+ h = (g - b) / d + (g < b ? 6 : 0);
3536
+ break;
3537
+ case g:
3538
+ h = (b - r) / d + 2;
3539
+ break;
3540
+ case b:
3541
+ h = (r - g) / d + 4;
3542
+ break;
3543
+ }
3544
+ h /= 6;
3545
+ }
3546
+ const hDeg = Math.floor(h * 360);
3547
+ const sPct = Math.round(s * 100);
3548
+ const lPct = Math.round(l * 100);
3549
+ return {
3550
+ h: hDeg,
3551
+ s: sPct,
3552
+ l: lPct,
3553
+ string: `hsl(${hDeg}, ${sPct}%, ${lPct}%)`,
3554
+ };
3555
+ }
3556
+ #createCustomInputEventListener(input) {
3557
+ Object.defineProperty(input, 'value', {
3558
+ configurable: true,
3559
+ get() {
3560
+ const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
3561
+ return descriptor.get.call(this);
3562
+ },
3563
+ set(newVal) {
3564
+ const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
3565
+ descriptor.set.call(this, newVal);
3566
+ const inputEvent = new CustomEvent('inputValueChanged', {
3567
+ bubbles: true,
3568
+ cancelable: true,
3569
+ detail: {
3570
+ value: newVal,
3571
+ },
3572
+ });
3573
+ this.dispatchEvent(inputEvent);
3574
+ return newVal;
3575
+ },
3576
+ });
3577
+ return input;
3578
+ }
3579
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipColorPickerInput, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3580
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ShipColorPickerInput, isStandalone: true, selector: "sh-color-picker-input", inputs: { renderingType: { classPropertyName: "renderingType", publicName: "renderingType", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, showEyeDropper: { classPropertyName: "showEyeDropper", publicName: "showEyeDropper", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", isOpen: "isOpenChange" }, ngImport: i0, template: `
3581
+ <sh-form-field-popover
3582
+ (closed)="close()"
3583
+ [(isOpen)]="isOpen"
3584
+ [class]="currentClass()"
3585
+ [variant]="variant()"
3586
+ [size]="size()"
3587
+ [color]="color()"
3588
+ [readonly]="readonly()"
3589
+ >
3590
+ <ng-content select="label" ngProjectAs="label" />
3591
+
3592
+ <ng-content select="[prefix]" ngProjectAs="[prefix]" />
3593
+ <ng-content select="[textPrefix]" ngProjectAs="[textPrefix]" />
3594
+
3595
+ <div id="input-wrap" class="input-container" ngProjectAs="input">
3596
+ <ng-content select="input" />
3597
+
3598
+ <div class="color-indicator" [style.--indicator-color]="formattedColorString()"></div>
3599
+
3600
+ @if (isEyeDropperSupported && showEyeDropper()) {
3601
+ <button size="xsmall" tabindex="-1" variant="outlined" shButton (click)="openEyeDropper($event)">
3602
+ <sh-icon>eyedropper</sh-icon>
3603
+ </button>
3604
+ }
3605
+ </div>
3606
+
3607
+ <ng-content select="[textSuffix]" ngProjectAs="[textSuffix]" />
3608
+ <ng-content select="[suffix]" ngProjectAs="[suffix]" />
3609
+
3610
+ <div popoverContent>
3611
+ @if (this.isOpen()) {
3612
+ <sh-color-picker
3613
+ [renderingType]="renderingType()"
3614
+ [direction]="'horizontal'"
3615
+ [(selectedColor)]="internalColorTuple"
3616
+ [hue]="internalHue()"
3617
+ [(alpha)]="internalAlpha"
3618
+ (currentColor)="onMainColorChange($event)" />
3619
+
3620
+ @if (renderingType() !== 'hsl') {
3621
+ <sh-color-picker
3622
+ renderingType="hue"
3623
+ [direction]="'horizontal'"
3624
+ [hue]="internalHue()"
3625
+ [selectedColor]="pureHueColor()"
3626
+ (currentColor)="onHuePickerChange($event)" />
3627
+ }
3628
+
3629
+ @if (hasAlpha()) {
3630
+ <sh-color-picker
3631
+ renderingType="alpha"
3632
+ [direction]="'horizontal'"
3633
+ [(alpha)]="internalAlpha"
3634
+ [(selectedColor)]="internalColorTuple" />
3635
+ }
3636
+ }
3637
+ </div>
3638
+ </sh-form-field-popover>
3639
+ `, isInline: true, dependencies: [{ kind: "component", type: ShipFormFieldPopover, selector: "sh-form-field-popover", inputs: ["isOpen", "color", "variant", "size", "readonly"], outputs: ["isOpenChange", "closed"] }, { kind: "component", type: ShipColorPicker, selector: "sh-color-picker", inputs: ["showDarkColors", "renderingType", "gridSize", "hue", "direction", "selectedColor", "alpha"], outputs: ["hueChange", "selectedColorChange", "alphaChange", "currentColor"] }, { kind: "component", type: ShipIcon, selector: "sh-icon", inputs: ["color", "size"] }, { kind: "component", type: ShipButton$1, selector: "[shButton]", inputs: ["color", "variant", "size", "readonly"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3640
+ }
3641
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipColorPickerInput, decorators: [{
3642
+ type: Component,
3643
+ args: [{
3644
+ selector: 'sh-color-picker-input',
3645
+ imports: [ShipFormFieldPopover, ShipColorPicker, ShipIcon, ShipButton$1],
3646
+ template: `
3647
+ <sh-form-field-popover
3648
+ (closed)="close()"
3649
+ [(isOpen)]="isOpen"
3650
+ [class]="currentClass()"
3651
+ [variant]="variant()"
3652
+ [size]="size()"
3653
+ [color]="color()"
3654
+ [readonly]="readonly()"
3655
+ >
3656
+ <ng-content select="label" ngProjectAs="label" />
3657
+
3658
+ <ng-content select="[prefix]" ngProjectAs="[prefix]" />
3659
+ <ng-content select="[textPrefix]" ngProjectAs="[textPrefix]" />
3660
+
3661
+ <div id="input-wrap" class="input-container" ngProjectAs="input">
3662
+ <ng-content select="input" />
3663
+
3664
+ <div class="color-indicator" [style.--indicator-color]="formattedColorString()"></div>
3665
+
3666
+ @if (isEyeDropperSupported && showEyeDropper()) {
3667
+ <button size="xsmall" tabindex="-1" variant="outlined" shButton (click)="openEyeDropper($event)">
3668
+ <sh-icon>eyedropper</sh-icon>
3669
+ </button>
3670
+ }
3671
+ </div>
3672
+
3673
+ <ng-content select="[textSuffix]" ngProjectAs="[textSuffix]" />
3674
+ <ng-content select="[suffix]" ngProjectAs="[suffix]" />
3675
+
3676
+ <div popoverContent>
3677
+ @if (this.isOpen()) {
3678
+ <sh-color-picker
3679
+ [renderingType]="renderingType()"
3680
+ [direction]="'horizontal'"
3681
+ [(selectedColor)]="internalColorTuple"
3682
+ [hue]="internalHue()"
3683
+ [(alpha)]="internalAlpha"
3684
+ (currentColor)="onMainColorChange($event)" />
3685
+
3686
+ @if (renderingType() !== 'hsl') {
3687
+ <sh-color-picker
3688
+ renderingType="hue"
3689
+ [direction]="'horizontal'"
3690
+ [hue]="internalHue()"
3691
+ [selectedColor]="pureHueColor()"
3692
+ (currentColor)="onHuePickerChange($event)" />
3693
+ }
3694
+
3695
+ @if (hasAlpha()) {
3696
+ <sh-color-picker
3697
+ renderingType="alpha"
3698
+ [direction]="'horizontal'"
3699
+ [(alpha)]="internalAlpha"
3700
+ [(selectedColor)]="internalColorTuple" />
3701
+ }
3702
+ }
3703
+ </div>
3704
+ </sh-form-field-popover>
3705
+ `,
3706
+ changeDetection: ChangeDetectionStrategy.OnPush,
3707
+ }]
3708
+ }], propDecorators: { renderingType: [{ type: i0.Input, args: [{ isSignal: true, alias: "renderingType", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }, { type: i0.Output, args: ["isOpenChange"] }], showEyeDropper: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEyeDropper", required: false }] }] } });
3709
+
3710
+ class ShipDatepicker {
3711
+ constructor() {
3712
+ this.#INIT_DATE = new Date(new Date().setHours(0, 0, 0, 0));
3713
+ this.date = model(null, ...(ngDevMode ? [{ debugName: "date" }] : /* istanbul ignore next */ []));
3714
+ this.endDate = model(null, ...(ngDevMode ? [{ debugName: "endDate" }] : /* istanbul ignore next */ []));
3715
+ this.asRange = input(false, ...(ngDevMode ? [{ debugName: "asRange" }] : /* istanbul ignore next */ []));
3716
+ this.monthsToShow = input(1, ...(ngDevMode ? [{ debugName: "monthsToShow" }] : /* istanbul ignore next */ []));
3717
+ this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
3718
+ this.startOfWeek = input(1, ...(ngDevMode ? [{ debugName: "startOfWeek" }] : /* istanbul ignore next */ []));
3719
+ this.weekdayLabels = input(['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], ...(ngDevMode ? [{ debugName: "weekdayLabels" }] : /* istanbul ignore next */ []));
3720
+ this.daysRef = viewChild('daysRef', ...(ngDevMode ? [{ debugName: "daysRef" }] : /* istanbul ignore next */ []));
3721
+ this.currentDate = signal(this.date() ?? this.#INIT_DATE, ...(ngDevMode ? [{ debugName: "currentDate" }] : /* istanbul ignore next */ []));
3722
+ this.monthOffsets = computed(() => {
3723
+ return Array.from({ length: this.monthsToShow() }, (_, i) => i);
3724
+ }, ...(ngDevMode ? [{ debugName: "monthOffsets" }] : /* istanbul ignore next */ []));
3725
+ this.selectedDateStylePosition = signal(null, ...(ngDevMode ? [{ debugName: "selectedDateStylePosition" }] : /* istanbul ignore next */ []));
3726
+ this.weekdays = computed(() => {
3727
+ const startOfWeek = this.startOfWeek();
3728
+ const weekdayLabels = this.weekdayLabels();
3729
+ return weekdayLabels.slice(startOfWeek).concat(weekdayLabels.slice(0, startOfWeek));
3730
+ }, ...(ngDevMode ? [{ debugName: "weekdays" }] : /* istanbul ignore next */ []));
3731
+ this.currentClasses = classMutationSignal();
3732
+ this.someEffect = effect(() => {
3733
+ const _ = this.currentClasses();
3734
+ this.#findSelectedAndCalc();
3735
+ }, ...(ngDevMode ? [{ debugName: "someEffect" }] : /* istanbul ignore next */ []));
3736
+ this.#newDateEffect = effect(() => {
3737
+ if (this.monthsToShow() > 1)
3738
+ return;
3739
+ this.#setDateAsCurrent();
3740
+ }, ...(ngDevMode ? [{ debugName: "#newDateEffect" }] : /* istanbul ignore next */ []));
3741
+ }
3742
+ #INIT_DATE;
3743
+ getLastVisibleMonth() {
3744
+ const lastMonthOffset = this.monthsToShow() - 1;
3745
+ return this.getOffsetDate(lastMonthOffset);
3746
+ }
3747
+ getOffsetDate(monthOffset) {
3748
+ const date = new Date(this.currentDate());
3749
+ date.setMonth(date.getMonth() + monthOffset);
3750
+ return date;
3751
+ }
3752
+ getMonthDates(monthOffset) {
3753
+ const offsetDate = this.getOffsetDate(monthOffset);
3754
+ return this.#generateMonthDates(offsetDate, this.startOfWeek());
3755
+ }
3756
+ #newDateEffect;
3757
+ ngOnInit() {
3758
+ if (this.monthsToShow() === 1)
3759
+ return;
3760
+ this.#setDateAsCurrent();
3761
+ }
3762
+ #setDateAsCurrent() {
3763
+ const newDate = this.date();
3764
+ if (newDate)
3765
+ this.currentDate.set(newDate);
3766
+ this.#findSelectedAndCalc();
3767
+ }
3768
+ #findSelectedAndCalc() {
3769
+ setTimeout(() => {
3770
+ const selectedElement = this.daysRef()?.nativeElement.querySelector('.sel');
3771
+ if (!selectedElement) {
3772
+ return this.selectedDateStylePosition.update((x) => (x ? { ...x, opacity: '0' } : null));
3773
+ }
3774
+ this.setSelectedDateStylePosition(selectedElement);
3775
+ });
3776
+ }
3777
+ #generateMonthDates(date, startOfWeek) {
3778
+ const year = date.getFullYear();
3779
+ const month = date.getMonth();
3780
+ const firstDay = new Date(year, month, 1).getDay();
3781
+ const daysInMonth = new Date(year, month + 1, 0).getDate();
3782
+ const dates = [];
3783
+ let offset = firstDay - startOfWeek;
3784
+ if (offset < 0) {
3785
+ offset += 7;
3786
+ }
3787
+ const lastDayOfPrevMonth = new Date(year, month, 0).getDate();
3788
+ for (let i = offset - 1; i >= 0; i--) {
3789
+ dates.push(new Date(year, month - 1, lastDayOfPrevMonth - i, 0, 0, 0, 0));
3790
+ }
3791
+ for (let i = 1; i <= daysInMonth; i++) {
3792
+ dates.push(new Date(year, month, i, 0, 0, 0, 0));
3793
+ }
3794
+ let nextMonthDay = 1;
3795
+ while (dates.length % 7 !== 0) {
3796
+ dates.push(new Date(year, month + 1, nextMonthDay++, 0, 0, 0, 0));
3797
+ }
3798
+ return dates;
3799
+ }
3800
+ nextMonth() {
3801
+ this.currentDate.update((currentDate) => {
3802
+ const newDate = new Date(currentDate);
3803
+ newDate.setMonth(currentDate.getMonth() + 1);
3804
+ return newDate;
3805
+ });
3806
+ this.#findSelectedAndCalc();
3807
+ }
3808
+ previousMonth() {
3809
+ this.currentDate.update((currentDate) => {
3810
+ const newDate = new Date(currentDate);
3811
+ newDate.setMonth(currentDate.getMonth() - 1);
3812
+ return newDate;
3813
+ });
3814
+ this.#findSelectedAndCalc();
3815
+ }
3816
+ setDate(newDate, selectedElement) {
3817
+ const createDateWithExistingTime = (newDate, existingDate) => {
3818
+ const hours = existingDate?.getHours() ?? 0;
3819
+ const minutes = existingDate?.getMinutes() ?? 0;
3820
+ const seconds = existingDate?.getSeconds() ?? 0;
3821
+ const milliseconds = existingDate?.getMilliseconds() ?? 0;
3822
+ return new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate(), hours, minutes, seconds, milliseconds);
3823
+ };
3824
+ if (!this.asRange()) {
3825
+ this.date.set(createDateWithExistingTime(newDate, this.date()));
3826
+ this.endDate.set(null);
3827
+ }
3828
+ else {
3829
+ const startDate = this.date();
3830
+ const endDate = this.endDate();
3831
+ const utcDate = createDateWithExistingTime(newDate, null);
3832
+ if (!startDate) {
3833
+ this.date.set(utcDate);
3834
+ }
3835
+ else if (!endDate) {
3836
+ if (utcDate < startDate) {
3837
+ this.date.set(utcDate);
3838
+ this.endDate.set(null);
3839
+ }
3840
+ else {
3841
+ this.endDate.set(utcDate);
3842
+ }
3843
+ }
3844
+ else {
3845
+ this.date.set(utcDate);
3846
+ this.endDate.set(null);
3847
+ }
3848
+ }
3849
+ if (this.asRange())
3850
+ return;
3851
+ this.setSelectedDateStylePosition(selectedElement);
3852
+ }
3853
+ isDateSelected(date) {
3854
+ const startDate = this.date();
3855
+ const endDate = this.endDate();
3856
+ if (startDate === null)
3857
+ return null;
3858
+ const startOfDay = (date) => {
3859
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
3860
+ };
3861
+ const endOfDay = (date) => {
3862
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999);
3863
+ };
3864
+ const currentDate = startOfDay(date);
3865
+ const rangeStart = startOfDay(startDate);
3866
+ const rangeEnd = endDate ? endOfDay(endDate) : null;
3867
+ let classes = [];
3868
+ if (this.asRange()) {
3869
+ if (rangeEnd === null) {
3870
+ if (currentDate.getTime() === rangeStart.getTime()) {
3871
+ classes.push('sel first last');
3872
+ }
3873
+ }
3874
+ else {
3875
+ if (currentDate.getTime() === rangeStart.getTime()) {
3876
+ classes.push('first');
3877
+ }
3878
+ if (currentDate.getTime() === startOfDay(rangeEnd).getTime()) {
3879
+ classes.push('last');
3880
+ }
3881
+ if (currentDate >= rangeStart && currentDate <= rangeEnd) {
3882
+ classes.push('sel');
3883
+ const dayOfWeek = currentDate.getDay();
3884
+ const startOfWeek = this.startOfWeek();
3885
+ if (dayOfWeek === startOfWeek) {
3886
+ classes.push('week-start');
3887
+ }
3888
+ const endOfWeek = (startOfWeek + 6) % 7;
3889
+ if (dayOfWeek === endOfWeek) {
3890
+ classes.push('week-end');
3891
+ }
3892
+ }
3893
+ const nextDate = new Date(currentDate);
3894
+ nextDate.setDate(currentDate.getDate() + 1);
3895
+ const prevDate = new Date(currentDate);
3896
+ prevDate.setDate(currentDate.getDate() - 1);
3897
+ const isFirstOfMonth = currentDate.getDate() === 1;
3898
+ const isLastOfMonth = nextDate.getMonth() !== currentDate.getMonth();
3899
+ if (isFirstOfMonth) {
3900
+ classes.push('month-start');
3901
+ }
3902
+ if (isLastOfMonth) {
3903
+ classes.push('month-end');
3904
+ }
3905
+ }
3906
+ }
3907
+ else {
3908
+ if (currentDate.getTime() === rangeStart.getTime()) {
3909
+ classes.push('sel');
3910
+ }
3911
+ }
3912
+ return classes.join(' ') || null;
3913
+ }
3914
+ setSelectedDateStylePosition(selectedElement) {
3915
+ this.selectedDateStylePosition.set({
3916
+ transform: `translate(${selectedElement.offsetLeft}px, ${selectedElement.offsetTop}px)`,
3917
+ opacity: '1',
3918
+ });
3919
+ }
3920
+ getMonthName(date) {
3921
+ return date.toLocaleString('default', { month: 'long' });
3922
+ }
3923
+ getFullYear(date) {
3924
+ return date.getFullYear();
3925
+ }
3926
+ // Rest of the component methods remain the same, but update isCurrentMonth:
3927
+ isCurrentMonth(date, monthOffset) {
3928
+ const offsetDate = this.getOffsetDate(monthOffset);
3929
+ return date.getMonth() === offsetDate.getMonth();
3930
+ }
3931
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipDatepicker, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3932
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ShipDatepicker, isStandalone: true, selector: "sh-datepicker", inputs: { date: { classPropertyName: "date", publicName: "date", isSignal: true, isRequired: false, transformFunction: null }, endDate: { classPropertyName: "endDate", publicName: "endDate", isSignal: true, isRequired: false, transformFunction: null }, asRange: { classPropertyName: "asRange", publicName: "asRange", isSignal: true, isRequired: false, transformFunction: null }, monthsToShow: { classPropertyName: "monthsToShow", publicName: "monthsToShow", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, startOfWeek: { classPropertyName: "startOfWeek", publicName: "startOfWeek", isSignal: true, isRequired: false, transformFunction: null }, weekdayLabels: { classPropertyName: "weekdayLabels", publicName: "weekdayLabels", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { date: "dateChange", endDate: "endDateChange" }, host: { properties: { "class.as-range": "asRange()", "class": "\"columns-\" + monthsToShow()", "class.disabled": "disabled()" } }, viewQueries: [{ propertyName: "daysRef", first: true, predicate: ["daysRef"], descendants: true, isSignal: true }], ngImport: i0, template: `
3933
+ <header>
3934
+ <button (click)="previousMonth()"><sh-icon>caret-left</sh-icon></button>
3935
+ <div class="title">
3936
+ {{ getMonthName(currentDate()!) }}
3937
+ @if (monthsToShow() > 1) {
3938
+ - {{ getMonthName(getLastVisibleMonth()) }}
3939
+ }
3940
+ {{ getFullYear(currentDate()!) }}
3941
+ </div>
3942
+ <button (click)="nextMonth()"><sh-icon>caret-right</sh-icon></button>
3943
+ </header>
3944
+
3945
+ <section class="months-container">
3946
+ @for (monthOffset of monthOffsets(); track monthOffset) {
3947
+ <div class="month">
3948
+ <nav class="weekdays">
3949
+ @for (day of weekdays(); track $index) {
3950
+ <div>{{ day }}</div>
3951
+ }
3952
+ </nav>
3953
+
3954
+ <div class="days" #daysRef>
3955
+ @for (calDate of getMonthDates(monthOffset); track $index) {
3956
+ <div
3957
+ #elementRef
3958
+ [class.out-of-scope]="!isCurrentMonth(calDate, monthOffset)"
3959
+ [class]="isDateSelected(calDate)"
3960
+ (click)="setDate(calDate, elementRef)">
3961
+ {{ calDate.getDate() }}
3962
+ </div>
3963
+ }
3964
+
3965
+ @if (!asRange()) {
3966
+ <article class="days">
3967
+ <div class="sel-el" [style]="selectedDateStylePosition()"></div>
3968
+ </article>
3969
+ }
3970
+ </div>
3971
+ </div>
3972
+ }
3973
+ </section>
3974
+ `, isInline: true, dependencies: [{ kind: "component", type: ShipIcon, selector: "sh-icon", inputs: ["color", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3126
3975
  }
3127
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipFormFieldPopover, decorators: [{
3976
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipDatepicker, decorators: [{
3128
3977
  type: Component,
3129
3978
  args: [{
3130
- selector: 'sh-form-field-popover',
3131
- imports: [ShipPopover],
3979
+ selector: 'sh-datepicker',
3980
+ imports: [ShipIcon],
3132
3981
  template: `
3133
- <ng-content select="label"></ng-content>
3134
-
3135
- <sh-popover
3136
- [(isOpen)]="isOpen"
3137
- (closed)="close()"
3138
- [options]="{
3139
- closeOnButton: false,
3140
- closeOnEsc: true,
3141
- }">
3142
- <div trigger class="input-wrap">
3143
- <div class="prefix">
3144
- <ng-content select="[prefix]"></ng-content>
3145
- <ng-content select="[textPrefix]"></ng-content>
3146
- </div>
3147
-
3148
- <div class="prefix-space"></div>
3149
-
3150
- <ng-content select="input"></ng-content>
3151
-
3152
- <ng-content select="textarea"></ng-content>
3153
-
3154
- <ng-content select="[textSuffix]"></ng-content>
3155
- <div class="suffix-space"></div>
3156
- <ng-content select="[suffix]"></ng-content>
3982
+ <header>
3983
+ <button (click)="previousMonth()"><sh-icon>caret-left</sh-icon></button>
3984
+ <div class="title">
3985
+ {{ getMonthName(currentDate()!) }}
3986
+ @if (monthsToShow() > 1) {
3987
+ - {{ getMonthName(getLastVisibleMonth()) }}
3988
+ }
3989
+ {{ getFullYear(currentDate()!) }}
3157
3990
  </div>
3158
- <ng-content select="[popoverContent]"></ng-content>
3159
- </sh-popover>
3991
+ <button (click)="nextMonth()"><sh-icon>caret-right</sh-icon></button>
3992
+ </header>
3160
3993
 
3161
- <div class="helpers">
3162
- <div class="error-wrap">
3163
- <ng-content select="[error]"></ng-content>
3164
- </div>
3994
+ <section class="months-container">
3995
+ @for (monthOffset of monthOffsets(); track monthOffset) {
3996
+ <div class="month">
3997
+ <nav class="weekdays">
3998
+ @for (day of weekdays(); track $index) {
3999
+ <div>{{ day }}</div>
4000
+ }
4001
+ </nav>
3165
4002
 
3166
- <div class="hint">
3167
- <ng-content select="[hint]"></ng-content>
3168
- </div>
3169
- </div>
4003
+ <div class="days" #daysRef>
4004
+ @for (calDate of getMonthDates(monthOffset); track $index) {
4005
+ <div
4006
+ #elementRef
4007
+ [class.out-of-scope]="!isCurrentMonth(calDate, monthOffset)"
4008
+ [class]="isDateSelected(calDate)"
4009
+ (click)="setDate(calDate, elementRef)">
4010
+ {{ calDate.getDate() }}
4011
+ </div>
4012
+ }
4013
+
4014
+ @if (!asRange()) {
4015
+ <article class="days">
4016
+ <div class="sel-el" [style]="selectedDateStylePosition()"></div>
4017
+ </article>
4018
+ }
4019
+ </div>
4020
+ </div>
4021
+ }
4022
+ </section>
3170
4023
  `,
3171
4024
  changeDetection: ChangeDetectionStrategy.OnPush,
4025
+ host: {
4026
+ '[class.as-range]': 'asRange()',
4027
+ '[class]': '"columns-" + monthsToShow()',
4028
+ '[class.disabled]': 'disabled()',
4029
+ },
3172
4030
  }]
3173
- }], propDecorators: { isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }, { type: i0.Output, args: ["isOpenChange"] }], closed: [{ type: i0.Output, args: ["closed"] }], onClick: [{
3174
- type: HostListener,
3175
- args: ['click']
3176
- }] } });
4031
+ }], propDecorators: { date: [{ type: i0.Input, args: [{ isSignal: true, alias: "date", required: false }] }, { type: i0.Output, args: ["dateChange"] }], endDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "endDate", required: false }] }, { type: i0.Output, args: ["endDateChange"] }], asRange: [{ type: i0.Input, args: [{ isSignal: true, alias: "asRange", required: false }] }], monthsToShow: [{ type: i0.Input, args: [{ isSignal: true, alias: "monthsToShow", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], startOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "startOfWeek", required: false }] }], weekdayLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekdayLabels", required: false }] }], daysRef: [{ type: i0.ViewChild, args: ['daysRef', { isSignal: true }] }] } });
3177
4032
 
3178
4033
  class ShipDatepickerInput {
3179
4034
  constructor() {
@@ -3285,7 +4140,7 @@ class ShipDatepickerInput {
3285
4140
  </sh-form-field-popover>
3286
4141
 
3287
4142
  <ng-template #defaultIndicator></ng-template>
3288
- `, isInline: true, dependencies: [{ kind: "component", type: ShipDatepicker, selector: "sh-datepicker", inputs: ["date", "endDate", "asRange", "monthsToShow", "disabled", "startOfWeek", "weekdayLabels"], outputs: ["dateChange", "endDateChange"] }, { kind: "component", type: ShipFormFieldPopover, selector: "sh-form-field-popover", inputs: ["isOpen"], outputs: ["isOpenChange", "closed"] }, { kind: "component", type: ShipIcon, selector: "sh-icon", inputs: ["color", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4143
+ `, isInline: true, dependencies: [{ kind: "component", type: ShipDatepicker, selector: "sh-datepicker", inputs: ["date", "endDate", "asRange", "monthsToShow", "disabled", "startOfWeek", "weekdayLabels"], outputs: ["dateChange", "endDateChange"] }, { kind: "component", type: ShipFormFieldPopover, selector: "sh-form-field-popover", inputs: ["isOpen", "color", "variant", "size", "readonly"], outputs: ["isOpenChange", "closed"] }, { kind: "component", type: ShipIcon, selector: "sh-icon", inputs: ["color", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3289
4144
  }
3290
4145
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipDatepickerInput, decorators: [{
3291
4146
  type: Component,
@@ -3457,7 +4312,7 @@ class ShipDaterangeInput {
3457
4312
  }
3458
4313
  </div>
3459
4314
  </sh-form-field-popover>
3460
- `, isInline: true, dependencies: [{ kind: "component", type: ShipDatepicker, selector: "sh-datepicker", inputs: ["date", "endDate", "asRange", "monthsToShow", "disabled", "startOfWeek", "weekdayLabels"], outputs: ["dateChange", "endDateChange"] }, { kind: "component", type: ShipFormFieldPopover, selector: "sh-form-field-popover", inputs: ["isOpen"], outputs: ["isOpenChange", "closed"] }, { kind: "component", type: ShipIcon, selector: "sh-icon", inputs: ["color", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4315
+ `, isInline: true, dependencies: [{ kind: "component", type: ShipDatepicker, selector: "sh-datepicker", inputs: ["date", "endDate", "asRange", "monthsToShow", "disabled", "startOfWeek", "weekdayLabels"], outputs: ["dateChange", "endDateChange"] }, { kind: "component", type: ShipFormFieldPopover, selector: "sh-form-field-popover", inputs: ["isOpen", "color", "variant", "size", "readonly"], outputs: ["isOpenChange", "closed"] }, { kind: "component", type: ShipIcon, selector: "sh-icon", inputs: ["color", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3461
4316
  }
3462
4317
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipDaterangeInput, decorators: [{
3463
4318
  type: Component,
@@ -3783,124 +4638,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
3783
4638
  }]
3784
4639
  }], propDecorators: { inputRef: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }, { type: i0.Output, args: ["placeholderChange"] }], overlayText: [{ type: i0.Input, args: [{ isSignal: true, alias: "overlayText", required: false }] }], files: [{ type: i0.Input, args: [{ isSignal: true, alias: "files", required: false }] }, { type: i0.Output, args: ["filesChange"] }] } });
3785
4640
 
3786
- class ShipAccordion {
3787
- constructor() {
3788
- this.selfElement = inject((ElementRef)).nativeElement;
3789
- this.name = input(`sh-accordion-${Math.random().toString(36).substring(2, 9)}`, ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
3790
- this.value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
3791
- this.allowMultiple = input(false, ...(ngDevMode ? [{ debugName: "allowMultiple" }] : /* istanbul ignore next */ []));
3792
- this.variant = input(null, ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
3793
- this.size = input(null, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
3794
- this.hostClasses = shipComponentClasses('accordion', {
3795
- variant: this.variant,
3796
- size: this.size,
3797
- });
3798
- this.items = contentProjectionSignal('details', {
3799
- childList: true,
3800
- subtree: true,
3801
- attributes: true,
3802
- attributeFilter: ['open', 'value'],
3803
- });
3804
- this.selfElement.addEventListener('toggle', this.onToggle.bind(this), true);
3805
- effect(() => {
3806
- const isMultiple = this.allowMultiple();
3807
- const groupName = this.name();
3808
- const valStr = this.value();
3809
- const vals = valStr ? valStr.split(',').filter((v) => v !== '') : [];
3810
- this.items().forEach((details) => {
3811
- if (!isMultiple) {
3812
- details.setAttribute('name', groupName);
3813
- }
3814
- else {
3815
- details.removeAttribute('name');
3816
- }
3817
- const summary = details.querySelector('summary');
3818
- if (summary && !summary.querySelector('sh-icon')) {
3819
- const icon = document.createElement('sh-icon');
3820
- icon.textContent = 'caret-down';
3821
- summary.appendChild(icon);
3822
- }
3823
- let contentWrapper = details.querySelector(':scope > .content');
3824
- if (!contentWrapper) {
3825
- const newWrapper = document.createElement('div');
3826
- newWrapper.className = 'content';
3827
- const childrenToMove = Array.from(details.childNodes).filter((node) => {
3828
- if (node.nodeType === Node.ELEMENT_NODE &&
3829
- node.tagName.toLowerCase() === 'summary') {
3830
- return false;
3831
- }
3832
- return true;
3833
- });
3834
- childrenToMove.forEach((child) => newWrapper.appendChild(child));
3835
- details.appendChild(newWrapper);
3836
- contentWrapper = newWrapper;
3837
- }
3838
- const itemVal = details.getAttribute('value');
3839
- if (itemVal && vals.includes(itemVal)) {
3840
- // Avoid triggering endless loops by only setting open if it actually changed
3841
- if (!details.open)
3842
- details.open = true;
3843
- }
3844
- else if (itemVal) {
3845
- if (details.open)
3846
- details.open = false;
3847
- }
3848
- });
3849
- });
3850
- }
3851
- onToggle(event) {
3852
- const target = event.target;
3853
- if (target.tagName !== 'DETAILS')
3854
- return;
3855
- if (!this.selfElement.contains(target))
3856
- return;
3857
- const itemVal = target.getAttribute('value');
3858
- if (!itemVal)
3859
- return; // Uncontrolled details element
3860
- const isOpen = target.open;
3861
- if (this.allowMultiple()) {
3862
- let vals = this.value()
3863
- ? this.value()
3864
- .split(',')
3865
- .filter((v) => v !== '')
3866
- : [];
3867
- if (isOpen && !vals.includes(itemVal))
3868
- vals.push(itemVal);
3869
- if (!isOpen)
3870
- vals = vals.filter((v) => v !== itemVal);
3871
- this.value.set(vals.join(','));
3872
- }
3873
- else {
3874
- if (isOpen) {
3875
- this.value.set(itemVal);
3876
- }
3877
- else {
3878
- if (this.value() === itemVal) {
3879
- this.value.set(null);
3880
- }
3881
- }
3882
- }
3883
- }
3884
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipAccordion, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3885
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.5", type: ShipAccordion, isStandalone: true, selector: "sh-accordion", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, allowMultiple: { classPropertyName: "allowMultiple", publicName: "allowMultiple", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { properties: { "class.sh-accordion": "true", "class": "hostClasses()" } }, ngImport: i0, template: `
3886
- <ng-content></ng-content>
3887
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3888
- }
3889
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipAccordion, decorators: [{
3890
- type: Component,
3891
- args: [{
3892
- selector: 'sh-accordion',
3893
- template: `
3894
- <ng-content></ng-content>
3895
- `,
3896
- changeDetection: ChangeDetectionStrategy.OnPush,
3897
- host: {
3898
- '[class.sh-accordion]': 'true',
3899
- '[class]': 'hostClasses()',
3900
- },
3901
- }]
3902
- }], ctorParameters: () => [], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], allowMultiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowMultiple", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
3903
-
3904
4641
  class ShipList {
3905
4642
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipList, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3906
4643
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.5", type: ShipList, isStandalone: true, selector: "sh-list", ngImport: i0, template: `
@@ -4674,6 +5411,18 @@ class ShipRangeSlider {
4674
5411
  max: 100,
4675
5412
  step: 1,
4676
5413
  }, ...(ngDevMode ? [{ debugName: "inputState" }] : /* istanbul ignore next */ []));
5414
+ this.color = input(null, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
5415
+ this.variant = input(null, ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
5416
+ this.size = input(null, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
5417
+ this.sharp = input(undefined, ...(ngDevMode ? [{ debugName: "sharp" }] : /* istanbul ignore next */ []));
5418
+ this.alwaysShow = input(undefined, ...(ngDevMode ? [{ debugName: "alwaysShow" }] : /* istanbul ignore next */ []));
5419
+ this.hostClasses = shipComponentClasses('rangeSlider', {
5420
+ color: this.color,
5421
+ variant: this.variant,
5422
+ size: this.size,
5423
+ sharp: this.sharp,
5424
+ alwaysShow: this.alwaysShow,
5425
+ });
4677
5426
  this.valuePercentage = computed(() => {
4678
5427
  const { min, max } = this.inputState();
4679
5428
  const currentValue = this.value() ?? min;
@@ -4843,7 +5592,7 @@ class ShipRangeSlider {
4843
5592
  }
4844
5593
  }
4845
5594
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ShipRangeSlider, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4846
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.5", type: ShipRangeSlider, isStandalone: true, selector: "sh-range-slider", inputs: { unit: { classPropertyName: "unit", publicName: "unit", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { properties: { "class.has-input": "this.hasInputElement" } }, ngImport: i0, template: `
5595
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.5", type: ShipRangeSlider, isStandalone: true, selector: "sh-range-slider", inputs: { unit: { classPropertyName: "unit", publicName: "unit", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, sharp: { classPropertyName: "sharp", publicName: "sharp", isSignal: true, isRequired: false, transformFunction: null }, alwaysShow: { classPropertyName: "alwaysShow", publicName: "alwaysShow", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { properties: { "class.sh-range-slider": "true", "class": "hostClasses()", "class.has-input": "this.hasInputElement" } }, ngImport: i0, template: `
4847
5596
  <div class="label">
4848
5597
  <ng-content select="label"></ng-content>
4849
5598
  </div>
@@ -4900,8 +5649,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
4900
5649
  </div>
4901
5650
  `,
4902
5651
  changeDetection: ChangeDetectionStrategy.OnPush,
5652
+ host: {
5653
+ '[class.sh-range-slider]': 'true',
5654
+ '[class]': 'hostClasses()',
5655
+ },
4903
5656
  }]
4904
- }], propDecorators: { unit: [{ type: i0.Input, args: [{ isSignal: true, alias: "unit", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], hasInputElement: [{
5657
+ }], propDecorators: { unit: [{ type: i0.Input, args: [{ isSignal: true, alias: "unit", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], sharp: [{ type: i0.Input, args: [{ isSignal: true, alias: "sharp", required: false }] }], alwaysShow: [{ type: i0.Input, args: [{ isSignal: true, alias: "alwaysShow", required: false }] }], hasInputElement: [{
4905
5658
  type: HostBinding,
4906
5659
  args: ['class.has-input']
4907
5660
  }] } });
@@ -7727,5 +8480,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
7727
8480
  * Generated bundle index. Do not edit.
7728
8481
  */
7729
8482
 
7730
- export { GridSortable, SHIP_CONFIG, ShipAccordion, ShipAlert, ShipAlertContainer, ShipAlertModule, ShipAlertService, ShipBlueprint, ShipButton, ShipButtonGroup, ShipCard, ShipCheckbox, ShipChip, ShipColorPicker, ShipDatepicker, ShipDatepickerInput, ShipDaterangeInput, ShipDialog, ShipDialogService, ShipDivider, ShipEventCard, ShipFileDragDrop, ShipFileUpload, ShipFormField, ShipFormFieldExperimental, ShipIcon, ShipInputMask, ShipList, ShipMenu, ShipPopover, ShipPreventWheel, ShipProgressBar, ShipRadio, ShipRangeSlider, ShipResize, ShipSelect, ShipSidenav, ShipSort, ShipSortable, ShipSpinner, ShipStepper, ShipStickyColumns, ShipTable, ShipTabs, ShipThemeToggle, ShipToggle, ShipToggleCard, ShipTooltip, ShipTooltipWrapper, ShipVirtualScroll, TEST_NODES, moveIndex, watchHostClass };
8483
+ export { GridSortable, SHIP_CONFIG, ShipAccordion, ShipAlert, ShipAlertContainer, ShipAlertModule, ShipAlertService, ShipBlueprint, ShipButton, ShipButtonGroup, ShipCard, ShipCheckbox, ShipChip, ShipColorPicker, ShipColorPickerInput, ShipDatepicker, ShipDatepickerInput, ShipDaterangeInput, ShipDialog, ShipDialogService, ShipDivider, ShipEventCard, ShipFileDragDrop, ShipFileUpload, ShipFormField, ShipFormFieldExperimental, ShipIcon, ShipInputMask, ShipList, ShipMenu, ShipPopover, ShipPreventWheel, ShipProgressBar, ShipRadio, ShipRangeSlider, ShipResize, ShipSelect, ShipSidenav, ShipSort, ShipSortable, ShipSpinner, ShipStepper, ShipStickyColumns, ShipTable, ShipTabs, ShipThemeToggle, ShipToggle, ShipToggleCard, ShipTooltip, ShipTooltipWrapper, ShipVirtualScroll, TEST_NODES, defaultThemeColors, hslToOklch, hslToRgbExact, hsvToRgbExact, moveIndex, rgbToHex, rgbToHsl, rgbToHsv, rgbToOklch, rgbaToHex8, watchHostClass };
7731
8484
  //# sourceMappingURL=ship-ui-core.mjs.map