iss-ctrl 0.0.21 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/fesm2022/iss-ctrl-attachment.viewer-DVUL39xd.mjs +110 -0
- package/fesm2022/iss-ctrl-attachment.viewer-DVUL39xd.mjs.map +1 -0
- package/fesm2022/iss-ctrl-base.mjs +682 -0
- package/fesm2022/iss-ctrl-base.mjs.map +1 -0
- package/fesm2022/iss-ctrl-checkbox.field-CILXADas.mjs +88 -0
- package/fesm2022/iss-ctrl-checkbox.field-CILXADas.mjs.map +1 -0
- package/fesm2022/iss-ctrl-checkbox.mjs +146 -0
- package/fesm2022/iss-ctrl-checkbox.mjs.map +1 -0
- package/fesm2022/iss-ctrl-checkbox.viewer-C3SEUszR.mjs +31 -0
- package/fesm2022/iss-ctrl-checkbox.viewer-C3SEUszR.mjs.map +1 -0
- package/fesm2022/iss-ctrl-chips.field-445Ds3V7.mjs +76 -0
- package/fesm2022/iss-ctrl-chips.field-445Ds3V7.mjs.map +1 -0
- package/fesm2022/iss-ctrl-chips.mjs +168 -0
- package/fesm2022/iss-ctrl-chips.mjs.map +1 -0
- package/fesm2022/iss-ctrl-chips.viewer-BcKT2hcq.mjs +38 -0
- package/fesm2022/iss-ctrl-chips.viewer-BcKT2hcq.mjs.map +1 -0
- package/fesm2022/iss-ctrl-date-picker.field-Djhh1FsR.mjs +98 -0
- package/fesm2022/iss-ctrl-date-picker.field-Djhh1FsR.mjs.map +1 -0
- package/fesm2022/iss-ctrl-date-picker.mjs +200 -0
- package/fesm2022/iss-ctrl-date-picker.mjs.map +1 -0
- package/fesm2022/iss-ctrl-date-picker.viewer-Dsmsf91t.mjs +40 -0
- package/fesm2022/iss-ctrl-date-picker.viewer-Dsmsf91t.mjs.map +1 -0
- package/fesm2022/iss-ctrl-date-range-picker.field-Dv636SAm.mjs +97 -0
- package/fesm2022/iss-ctrl-date-range-picker.field-Dv636SAm.mjs.map +1 -0
- package/fesm2022/iss-ctrl-date-range-picker.mjs +307 -0
- package/fesm2022/iss-ctrl-date-range-picker.mjs.map +1 -0
- package/fesm2022/iss-ctrl-date-range-picker.viewer-tmTQGPFh.mjs +41 -0
- package/fesm2022/iss-ctrl-date-range-picker.viewer-tmTQGPFh.mjs.map +1 -0
- package/fesm2022/iss-ctrl-datetime-picker.field-DldY8vke.mjs +98 -0
- package/fesm2022/iss-ctrl-datetime-picker.field-DldY8vke.mjs.map +1 -0
- package/fesm2022/iss-ctrl-datetime-picker.mjs +226 -0
- package/fesm2022/iss-ctrl-datetime-picker.mjs.map +1 -0
- package/fesm2022/iss-ctrl-datetime-picker.viewer-BeUyEBKo.mjs +42 -0
- package/fesm2022/iss-ctrl-datetime-picker.viewer-BeUyEBKo.mjs.map +1 -0
- package/fesm2022/iss-ctrl-dropdownlist.field-DikBAtc_.mjs +87 -0
- package/fesm2022/iss-ctrl-dropdownlist.field-DikBAtc_.mjs.map +1 -0
- package/fesm2022/iss-ctrl-dropdownlist.mjs +398 -0
- package/fesm2022/iss-ctrl-dropdownlist.mjs.map +1 -0
- package/fesm2022/iss-ctrl-dropdownlist.viewer-OWJxDpr8.mjs +30 -0
- package/fesm2022/iss-ctrl-dropdownlist.viewer-OWJxDpr8.mjs.map +1 -0
- package/fesm2022/iss-ctrl-file-picker.field-BMZaJxvj.mjs +210 -0
- package/fesm2022/iss-ctrl-file-picker.field-BMZaJxvj.mjs.map +1 -0
- package/fesm2022/iss-ctrl-file-picker.mjs +274 -0
- package/fesm2022/iss-ctrl-file-picker.mjs.map +1 -0
- package/fesm2022/iss-ctrl-input.field-BiwBFYx3.mjs +104 -0
- package/fesm2022/iss-ctrl-input.field-BiwBFYx3.mjs.map +1 -0
- package/fesm2022/iss-ctrl-input.mjs +204 -0
- package/fesm2022/iss-ctrl-input.mjs.map +1 -0
- package/fesm2022/iss-ctrl-input.viewer-BOZnW0_e.mjs +22 -0
- package/fesm2022/iss-ctrl-input.viewer-BOZnW0_e.mjs.map +1 -0
- package/fesm2022/iss-ctrl-iss-ctrl-BG-QWWdq.mjs +1770 -0
- package/fesm2022/iss-ctrl-iss-ctrl-BG-QWWdq.mjs.map +1 -0
- package/fesm2022/{iss-ctrl-load-event-bOt6IlIo.mjs → iss-ctrl-load-event-T4L3k9XO.mjs} +4 -3
- package/fesm2022/iss-ctrl-load-event-T4L3k9XO.mjs.map +1 -0
- package/fesm2022/{iss-ctrl-load-options-event-CxyAyNMe.mjs → iss-ctrl-load-options-event-tG258cbP.mjs} +3 -3
- package/fesm2022/{iss-ctrl-load-options-event-CxyAyNMe.mjs.map → iss-ctrl-load-options-event-tG258cbP.mjs.map} +1 -1
- package/fesm2022/{iss-ctrl-load-value-event-DJKJ5S3h.mjs → iss-ctrl-load-value-event--7zt90Dl.mjs} +3 -3
- package/fesm2022/{iss-ctrl-load-value-event-DJKJ5S3h.mjs.map → iss-ctrl-load-value-event--7zt90Dl.mjs.map} +1 -1
- package/fesm2022/iss-ctrl-mobile.field-DLccnWKX.mjs +77 -0
- package/fesm2022/iss-ctrl-mobile.field-DLccnWKX.mjs.map +1 -0
- package/fesm2022/iss-ctrl-mobile.mjs +2465 -0
- package/fesm2022/iss-ctrl-mobile.mjs.map +1 -0
- package/fesm2022/iss-ctrl-mobile.viewer-DQgxAcR-.mjs +22 -0
- package/fesm2022/iss-ctrl-mobile.viewer-DQgxAcR-.mjs.map +1 -0
- package/fesm2022/{iss-ctrl-note.field-B_Lbp28X.mjs → iss-ctrl-note.field-CT1wVmfF.mjs} +2 -2
- package/fesm2022/{iss-ctrl-note.field-B_Lbp28X.mjs.map → iss-ctrl-note.field-CT1wVmfF.mjs.map} +1 -1
- package/fesm2022/iss-ctrl-plate.field-CQQY8rnE.mjs +92 -0
- package/fesm2022/iss-ctrl-plate.field-CQQY8rnE.mjs.map +1 -0
- package/fesm2022/iss-ctrl-plate.mjs +493 -0
- package/fesm2022/iss-ctrl-plate.mjs.map +1 -0
- package/fesm2022/iss-ctrl-plate.viewer-B8PUBLH8.mjs +179 -0
- package/fesm2022/iss-ctrl-plate.viewer-B8PUBLH8.mjs.map +1 -0
- package/fesm2022/iss-ctrl-radio.field-Dp5D-AXQ.mjs +88 -0
- package/fesm2022/iss-ctrl-radio.field-Dp5D-AXQ.mjs.map +1 -0
- package/fesm2022/iss-ctrl-radio.mjs +104 -0
- package/fesm2022/iss-ctrl-radio.mjs.map +1 -0
- package/fesm2022/iss-ctrl-radio.viewer-EiDliZro.mjs +30 -0
- package/fesm2022/iss-ctrl-radio.viewer-EiDliZro.mjs.map +1 -0
- package/fesm2022/{iss-ctrl-replace-api-keys-C0XYQW4i.mjs → iss-ctrl-replace-api-keys-COUv4Kin.mjs} +2 -2
- package/fesm2022/{iss-ctrl-replace-api-keys-C0XYQW4i.mjs.map → iss-ctrl-replace-api-keys-COUv4Kin.mjs.map} +1 -1
- package/fesm2022/{iss-ctrl-set-value-event-CgXhi0Bv.mjs → iss-ctrl-set-value-event-D0-_qIyS.mjs} +2 -2
- package/fesm2022/{iss-ctrl-set-value-event-CgXhi0Bv.mjs.map → iss-ctrl-set-value-event-D0-_qIyS.mjs.map} +1 -1
- package/fesm2022/iss-ctrl-slide-toggle.field-vCJHOiSG.mjs +85 -0
- package/fesm2022/iss-ctrl-slide-toggle.field-vCJHOiSG.mjs.map +1 -0
- package/fesm2022/iss-ctrl-slide-toggle.mjs +66 -0
- package/fesm2022/iss-ctrl-slide-toggle.mjs.map +1 -0
- package/fesm2022/iss-ctrl-slide-toggle.viewer-hsIHwDj3.mjs +34 -0
- package/fesm2022/iss-ctrl-slide-toggle.viewer-hsIHwDj3.mjs.map +1 -0
- package/fesm2022/{iss-ctrl-table.field-B5FxF6hn.mjs → iss-ctrl-table.field-DdFjH-D8.mjs} +142 -140
- package/fesm2022/iss-ctrl-table.field-DdFjH-D8.mjs.map +1 -0
- package/fesm2022/iss-ctrl-textarea.field-Dqhhu2m4.mjs +82 -0
- package/fesm2022/iss-ctrl-textarea.field-Dqhhu2m4.mjs.map +1 -0
- package/fesm2022/iss-ctrl-textarea.mjs +115 -0
- package/fesm2022/iss-ctrl-textarea.mjs.map +1 -0
- package/fesm2022/iss-ctrl-textarea.viewer-CpmLauik.mjs +22 -0
- package/fesm2022/iss-ctrl-textarea.viewer-CpmLauik.mjs.map +1 -0
- package/fesm2022/iss-ctrl-time-picker.field-CdmxRiH8.mjs +77 -0
- package/fesm2022/iss-ctrl-time-picker.field-CdmxRiH8.mjs.map +1 -0
- package/fesm2022/iss-ctrl-time-picker.mjs +170 -0
- package/fesm2022/iss-ctrl-time-picker.mjs.map +1 -0
- package/fesm2022/iss-ctrl-time-picker.viewer-CJeFLrdI.mjs +22 -0
- package/fesm2022/iss-ctrl-time-picker.viewer-CJeFLrdI.mjs.map +1 -0
- package/fesm2022/iss-ctrl-toggle-button.field-rAT7dVFC.mjs +101 -0
- package/fesm2022/iss-ctrl-toggle-button.field-rAT7dVFC.mjs.map +1 -0
- package/fesm2022/iss-ctrl-toggle-button.mjs +158 -0
- package/fesm2022/iss-ctrl-toggle-button.mjs.map +1 -0
- package/fesm2022/iss-ctrl-toggle-button.viewer-CC-8_Dy4.mjs +30 -0
- package/fesm2022/iss-ctrl-toggle-button.viewer-CC-8_Dy4.mjs.map +1 -0
- package/fesm2022/iss-ctrl-tokens.mjs +25 -0
- package/fesm2022/iss-ctrl-tokens.mjs.map +1 -0
- package/fesm2022/{iss-ctrl-trigger-based-on-json-value-2v1kLVHZ.mjs → iss-ctrl-trigger-based-on-json-value-I92SNsv9.mjs} +2 -2
- package/fesm2022/{iss-ctrl-trigger-based-on-json-value-2v1kLVHZ.mjs.map → iss-ctrl-trigger-based-on-json-value-I92SNsv9.mjs.map} +1 -1
- package/fesm2022/{iss-ctrl-user-picker.field-BH63sZ-X.mjs → iss-ctrl-user-picker.field-DpmYCfpD.mjs} +154 -153
- package/fesm2022/iss-ctrl-user-picker.field-DpmYCfpD.mjs.map +1 -0
- package/fesm2022/iss-ctrl.mjs +3 -1
- package/fesm2022/iss-ctrl.mjs.map +1 -1
- package/package.json +69 -1
- package/types/iss-ctrl-base.d.ts +318 -0
- package/types/iss-ctrl-checkbox.d.ts +46 -0
- package/types/iss-ctrl-chips.d.ts +41 -0
- package/types/iss-ctrl-date-picker.d.ts +75 -0
- package/types/iss-ctrl-date-range-picker.d.ts +95 -0
- package/types/iss-ctrl-datetime-picker.d.ts +77 -0
- package/types/iss-ctrl-dropdownlist.d.ts +78 -0
- package/types/iss-ctrl-file-picker.d.ts +95 -0
- package/types/iss-ctrl-input.d.ts +76 -0
- package/types/iss-ctrl-mobile.d.ts +91 -0
- package/types/iss-ctrl-plate.d.ts +54 -0
- package/types/iss-ctrl-radio.d.ts +34 -0
- package/types/iss-ctrl-slide-toggle.d.ts +26 -0
- package/types/iss-ctrl-textarea.d.ts +30 -0
- package/types/iss-ctrl-time-picker.d.ts +48 -0
- package/types/iss-ctrl-toggle-button.d.ts +41 -0
- package/types/iss-ctrl-tokens.d.ts +21 -0
- package/types/iss-ctrl.d.ts +303 -2005
- package/fesm2022/iss-ctrl-iss-ctrl-BKu6a5HF.mjs +0 -9683
- package/fesm2022/iss-ctrl-iss-ctrl-BKu6a5HF.mjs.map +0 -1
- package/fesm2022/iss-ctrl-load-event-bOt6IlIo.mjs.map +0 -1
- package/fesm2022/iss-ctrl-table.field-B5FxF6hn.mjs.map +0 -1
- package/fesm2022/iss-ctrl-user-picker.field-BH63sZ-X.mjs.map +0 -1
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, signal, output, inject, Injector, Component, CUSTOM_ELEMENTS_SCHEMA, Injectable, Pipe } from '@angular/core';
|
|
3
|
+
import { FormControl, NgControl } from '@angular/forms';
|
|
4
|
+
import { MatHint } from '@angular/material/form-field';
|
|
5
|
+
import { NativeDateAdapter, MAT_DATE_LOCALE, DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core';
|
|
6
|
+
import { UI_LANGUAGE } from 'iss-ctrl/tokens';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Base class for every `iss-ctrl-*` control.
|
|
10
|
+
*
|
|
11
|
+
* Controls are the presentational layer: they know nothing about {@link IssField} and are
|
|
12
|
+
* driven purely by bindings, so they can be used standalone inside any reactive form.
|
|
13
|
+
* Every control below inherits the properties declared here and adds its own on top.
|
|
14
|
+
*
|
|
15
|
+
* ### Inherited inputs
|
|
16
|
+
* | Property | Type | Default | Description |
|
|
17
|
+
* | --- | --- | --- | --- |
|
|
18
|
+
* | `formControlName` | `string` | `""` | Name of the control in the surrounding `FormGroup`. |
|
|
19
|
+
* | `placeholder` | `string` | `""` | Placeholder text shown while the field is empty. |
|
|
20
|
+
* | `readonly` | `boolean` | `false` | Renders the value but blocks every interaction. |
|
|
21
|
+
* | `multiple` | `boolean \| undefined` | `false` | Allows more than one value, where the control supports it. |
|
|
22
|
+
* | `min` | `number \| string \| null` | `null` | Lower bound (number or date/time string). |
|
|
23
|
+
* | `max` | `number \| string \| null` | `null` | Upper bound (number or date/time string). |
|
|
24
|
+
* | `minLength` | `number \| null` | `null` | Minimum number of characters. |
|
|
25
|
+
* | `maxLength` | `number \| null` | `null` | Maximum number of characters. |
|
|
26
|
+
* | `hints` | `Hint[]` | `[]` | Helper messages rendered under the field. |
|
|
27
|
+
* | `className` | `string \| null` | `null` | Extra CSS classes for the host. |
|
|
28
|
+
*
|
|
29
|
+
* ### Inherited outputs
|
|
30
|
+
* | Event | Payload | Description |
|
|
31
|
+
* | --- | --- | --- |
|
|
32
|
+
* | `(onChange)` | `any` | Fires after every committed value change. |
|
|
33
|
+
*
|
|
34
|
+
* ### Not inputs
|
|
35
|
+
* `disabled` and `touched` are signals owned by the control. Disable a control through the
|
|
36
|
+
* forms API (`ctrl.disable()` or a `disabled` field config), never with a template binding.
|
|
37
|
+
*
|
|
38
|
+
* ### Content projection
|
|
39
|
+
* Controls that render a `mat-form-field` project `<label>` into the floating label.
|
|
40
|
+
* `upgrade.scss` reveals that space only while the projected `<label>` carries text, so an
|
|
41
|
+
* omitted label — or one that interpolates to an empty string — collapses it. No label markup
|
|
42
|
+
* is removed at runtime, so a label that arrives later simply shows up.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```html
|
|
46
|
+
* <iss-ctrl-input formControlName="email" [readonly]="false" (onChange)="save($event)">
|
|
47
|
+
* <label>Email</label>
|
|
48
|
+
* </iss-ctrl-input>
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
class ControlBase {
|
|
52
|
+
/**
|
|
53
|
+
* Name of this control inside the surrounding `FormGroup`.
|
|
54
|
+
*/
|
|
55
|
+
formControlName = input("", ...(ngDevMode ? [{ debugName: "formControlName" }] : /* istanbul ignore next */ []));
|
|
56
|
+
/**
|
|
57
|
+
* Lower bound forwarded to the underlying input — a number, or a date/time string.
|
|
58
|
+
*/
|
|
59
|
+
min = input(null, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
|
|
60
|
+
/**
|
|
61
|
+
* Upper bound forwarded to the underlying input — a number, or a date/time string.
|
|
62
|
+
*/
|
|
63
|
+
max = input(null, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
|
|
64
|
+
/**
|
|
65
|
+
* Minimum number of characters accepted by the underlying input.
|
|
66
|
+
*/
|
|
67
|
+
minLength = input(null, ...(ngDevMode ? [{ debugName: "minLength" }] : /* istanbul ignore next */ []));
|
|
68
|
+
/**
|
|
69
|
+
* Maximum number of characters accepted by the underlying input.
|
|
70
|
+
*/
|
|
71
|
+
maxLength = input(null, ...(ngDevMode ? [{ debugName: "maxLength" }] : /* istanbul ignore next */ []));
|
|
72
|
+
/**
|
|
73
|
+
* Disabled state. **Not an input** — it is set by the forms API through
|
|
74
|
+
* {@link ControlBase.setDisabledState}. Disable via `ctrl.disable()` or the field config.
|
|
75
|
+
*/
|
|
76
|
+
disabled = signal(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
77
|
+
/**
|
|
78
|
+
* Renders the current value but blocks every user interaction.
|
|
79
|
+
*/
|
|
80
|
+
readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
|
|
81
|
+
/**
|
|
82
|
+
* Allows selecting/entering more than one value, on controls that support it.
|
|
83
|
+
*/
|
|
84
|
+
multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
|
|
85
|
+
/**
|
|
86
|
+
* Placeholder text shown while the control has no value.
|
|
87
|
+
*/
|
|
88
|
+
placeholder = input("", ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
89
|
+
/**
|
|
90
|
+
* Helper messages rendered beneath the field. See {@link Hint}.
|
|
91
|
+
*/
|
|
92
|
+
hints = input([], ...(ngDevMode ? [{ debugName: "hints" }] : /* istanbul ignore next */ []));
|
|
93
|
+
/**
|
|
94
|
+
* Extra CSS class names applied to the control host.
|
|
95
|
+
*/
|
|
96
|
+
className = input(null, ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
|
|
97
|
+
/**
|
|
98
|
+
* Whether the user has interacted with the control. Owned by the control, not an input.
|
|
99
|
+
*/
|
|
100
|
+
touched = signal(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
|
|
101
|
+
/**
|
|
102
|
+
* Emits the committed value after every change.
|
|
103
|
+
*/
|
|
104
|
+
onChange = output();
|
|
105
|
+
ctrl = new FormControl(null);
|
|
106
|
+
/**
|
|
107
|
+
* Error state for the inner `mat-form-field`, derived from the **host** control.
|
|
108
|
+
*
|
|
109
|
+
* Material reads `errorState` off the `NgControl` bound to its own `matInput` — here that is
|
|
110
|
+
* {@link ControlBase.ctrl}, which carries no validators and is therefore always valid. Every
|
|
111
|
+
* error a consumer cares about (the field config's validators plus the control's own
|
|
112
|
+
* {@link ControlBase.validate}) lives on the host control instead, so without this matcher an
|
|
113
|
+
* invalid `iss-ctrl-*` would keep rendering the valid outline. Bind it on the `matInput` as
|
|
114
|
+
* `[errorStateMatcher]="errorStateMatcher"`.
|
|
115
|
+
*/
|
|
116
|
+
errorStateMatcher = {
|
|
117
|
+
isErrorState: (_control, form) => {
|
|
118
|
+
const host = this.hostControl();
|
|
119
|
+
if (!host) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return !!host.invalid && (!!host.touched || this.touched() || !!form?.submitted);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
#injector = inject(Injector);
|
|
126
|
+
// Resolved lazily: injecting NgControl in the constructor would close the DI cycle through
|
|
127
|
+
// NG_VALUE_ACCESSOR, which every control provides with `useExisting`.
|
|
128
|
+
#hostControl = undefined;
|
|
129
|
+
registerOnChange(fn) {
|
|
130
|
+
this.onPropagateValue = fn;
|
|
131
|
+
}
|
|
132
|
+
registerOnTouched(fn) {
|
|
133
|
+
this.onTouch = fn;
|
|
134
|
+
}
|
|
135
|
+
setDisabledState(isDisabled) {
|
|
136
|
+
this.disabled.set(isDisabled);
|
|
137
|
+
if (isDisabled) {
|
|
138
|
+
this.ctrl.disable();
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
this.ctrl.enable();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
writeValue(value) {
|
|
145
|
+
this.ctrl.setValue(value, { emitEvent: false });
|
|
146
|
+
}
|
|
147
|
+
markAsTouched() {
|
|
148
|
+
this.touched.set(true);
|
|
149
|
+
this.onTouch();
|
|
150
|
+
}
|
|
151
|
+
validate(control) {
|
|
152
|
+
return this.ctrl.errors;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The `NgControl` this component is bound to from the outside (`formControlName`,
|
|
156
|
+
* `[formControl]` or `[ngModel]`), or `null` when it is used without one.
|
|
157
|
+
*/
|
|
158
|
+
hostControl() {
|
|
159
|
+
if (this.#hostControl === undefined) {
|
|
160
|
+
this.#hostControl = this.#injector.get(NgControl, null, { self: true });
|
|
161
|
+
}
|
|
162
|
+
return this.#hostControl;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Whether the control currently holds no value.
|
|
166
|
+
*
|
|
167
|
+
* Reflected on the host as `iss-ctrl-empty`, which is what `upgrade.scss` uses to keep the
|
|
168
|
+
* resting outline of an empty field: Material's own `mat-form-field-hide-placeholder` drops
|
|
169
|
+
* off the moment the field is focused, so the border would change colour on focus alone.
|
|
170
|
+
* Override it in a control whose value does not live on {@link ControlBase.ctrl}.
|
|
171
|
+
*/
|
|
172
|
+
isEmpty() {
|
|
173
|
+
const value = this.ctrl.getRawValue();
|
|
174
|
+
if (Array.isArray(value)) {
|
|
175
|
+
return value.length === 0;
|
|
176
|
+
}
|
|
177
|
+
return value === null || value === undefined || value === "";
|
|
178
|
+
}
|
|
179
|
+
onTouch = () => { };
|
|
180
|
+
onPropagateValue = (_) => { };
|
|
181
|
+
emitValue(value) {
|
|
182
|
+
let emissionValue = this.ctrl.getRawValue();
|
|
183
|
+
if (value !== undefined) {
|
|
184
|
+
emissionValue = value;
|
|
185
|
+
}
|
|
186
|
+
this.onPropagateValue(emissionValue);
|
|
187
|
+
this.emitOnChangeEvent(emissionValue);
|
|
188
|
+
}
|
|
189
|
+
propagateValue(value) {
|
|
190
|
+
let emissionValue = this.ctrl.getRawValue();
|
|
191
|
+
if (value !== undefined) {
|
|
192
|
+
emissionValue = value;
|
|
193
|
+
}
|
|
194
|
+
this.onPropagateValue(emissionValue);
|
|
195
|
+
}
|
|
196
|
+
emitOnChangeEvent($event) {
|
|
197
|
+
let emissionValue = this.ctrl.getRawValue();
|
|
198
|
+
if ($event !== undefined) {
|
|
199
|
+
emissionValue = $event;
|
|
200
|
+
}
|
|
201
|
+
this.onChange.emit(emissionValue);
|
|
202
|
+
}
|
|
203
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ControlBase, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
204
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.9", type: ControlBase, isStandalone: true, selector: "ng-component", inputs: { formControlName: { classPropertyName: "formControlName", publicName: "formControlName", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, minLength: { classPropertyName: "minLength", publicName: "minLength", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, hints: { classPropertyName: "hints", publicName: "hints", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onChange: "onChange" }, host: { properties: { "class.iss-ctrl-empty": "isEmpty()" }, classAttribute: "iss-ctrl" }, ngImport: i0, template: ``, isInline: true });
|
|
205
|
+
}
|
|
206
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ControlBase, decorators: [{
|
|
207
|
+
type: Component,
|
|
208
|
+
args: [{
|
|
209
|
+
template: ``,
|
|
210
|
+
host: { "class": "iss-ctrl", "[class.iss-ctrl-empty]": "isEmpty()" }
|
|
211
|
+
}]
|
|
212
|
+
}], propDecorators: { formControlName: [{ type: i0.Input, args: [{ isSignal: true, alias: "formControlName", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], minLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "minLength", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], hints: [{ type: i0.Input, args: [{ isSignal: true, alias: "hints", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], onChange: [{ type: i0.Output, args: ["onChange"] }] } });
|
|
213
|
+
|
|
214
|
+
const DATE_FORMAT_ADAPTER = {
|
|
215
|
+
parse: {
|
|
216
|
+
dateInput: "dd/MM/yyyy",
|
|
217
|
+
timeInput: "hh:mm:ss aa",
|
|
218
|
+
},
|
|
219
|
+
display: {
|
|
220
|
+
dateInput: "dd/MM/yyyy",
|
|
221
|
+
monthYearLabel: "MMMM yyyy",
|
|
222
|
+
dateA11yLabel: "dd/MM/yyyy",
|
|
223
|
+
monthYearA11yLabel: "MMMM yyyy",
|
|
224
|
+
timeInput: "hh:mm aa",
|
|
225
|
+
timeOptionLabel: "hh:mm aa",
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
const DATETIME_FORMAT = "dd/MM/yyyy hh:mm a";
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Umm al-Qura Hijri calendar helpers.
|
|
232
|
+
*
|
|
233
|
+
* Every function here works on a plain `Date`: a `Date` is an instant, and the Hijri
|
|
234
|
+
* calendar is only another way of naming that instant. Nothing stores Hijri numbers on the
|
|
235
|
+
* date itself, so a value converted to Hijri for display or emission is still the same day.
|
|
236
|
+
*
|
|
237
|
+
* The conversion is done by `Intl` with the `islamic-umalqura` calendar, which is the
|
|
238
|
+
* calendar used officially in Saudi Arabia.
|
|
239
|
+
*/
|
|
240
|
+
/** Locale used by the calendar UI when the Hijri calendar is on. */
|
|
241
|
+
const HIJRI_LOCALE = "ar-SA-u-ca-islamic-umalqura";
|
|
242
|
+
/** Latin-digit locale used internally to read the Hijri parts of a date. */
|
|
243
|
+
const HIJRI_PARTS_LOCALE = "en-US-u-ca-islamic-umalqura";
|
|
244
|
+
const MS_PER_DAY = 86_400_000;
|
|
245
|
+
/** 1 Muharram 1445 AH, the anchor the month estimate starts from. */
|
|
246
|
+
const ANCHOR_GREGORIAN = { year: 2023, month: 6, day: 19 };
|
|
247
|
+
const ANCHOR_HIJRI_YEAR = 1445;
|
|
248
|
+
/** Mean length of a Hijri month, used only to get close before the exact walk. */
|
|
249
|
+
const MEAN_HIJRI_MONTH = 29.530588;
|
|
250
|
+
const HIJRI_MONTH_NAMES = {
|
|
251
|
+
ar: ["محرم", "صفر", "ربيع الأول", "ربيع الآخر", "جمادى الأولى", "جمادى الآخرة", "رجب", "شعبان", "رمضان", "شوال", "ذو القعدة", "ذو الحجة"],
|
|
252
|
+
en: ["Muharram", "Safar", "Rabi' al-Awwal", "Rabi' al-Thani", "Jumada al-Ula", "Jumada al-Akhirah", "Rajab", "Sha'ban", "Ramadan", "Shawwal", "Dhu al-Qi'dah", "Dhu al-Hijjah"],
|
|
253
|
+
enShort: ["Muh", "Saf", "Rab I", "Rab II", "Jum I", "Jum II", "Raj", "Sha", "Ram", "Shaw", "Dhul-Q", "Dhul-H"],
|
|
254
|
+
};
|
|
255
|
+
const partsFormatter = new Intl.DateTimeFormat(HIJRI_PARTS_LOCALE, {
|
|
256
|
+
day: "numeric",
|
|
257
|
+
month: "numeric",
|
|
258
|
+
year: "numeric",
|
|
259
|
+
});
|
|
260
|
+
/**
|
|
261
|
+
* Reads the Hijri year, month and day a `Date` falls on.
|
|
262
|
+
*/
|
|
263
|
+
function getHijriParts(date) {
|
|
264
|
+
const parts = partsFormatter.formatToParts(date);
|
|
265
|
+
const read = (type) => parseInt(parts.find((part) => part.type === type)?.value ?? "0", 10);
|
|
266
|
+
return { year: read("year"), month: read("month") - 1, day: read("day") };
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* The Gregorian `Date` of the first day of the Hijri month `date` falls in, at midday.
|
|
270
|
+
*
|
|
271
|
+
* Midday, not midnight, so a daylight-saving shift can never push the result into the
|
|
272
|
+
* previous day while the month is being walked.
|
|
273
|
+
*/
|
|
274
|
+
function startOfHijriMonth(date) {
|
|
275
|
+
const start = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 12);
|
|
276
|
+
start.setDate(start.getDate() - (getHijriParts(start).day - 1));
|
|
277
|
+
return start;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Number of days in the Hijri month `date` falls in — 29 or 30, decided by Umm al-Qura.
|
|
281
|
+
*/
|
|
282
|
+
function getHijriDaysInMonth(date) {
|
|
283
|
+
const start = startOfHijriMonth(date);
|
|
284
|
+
// +30 days from the first of a 29 or 30 day month always lands inside the next one
|
|
285
|
+
const insideNextMonth = new Date(start.getTime());
|
|
286
|
+
insideNextMonth.setDate(insideNextMonth.getDate() + 30);
|
|
287
|
+
return Math.round((startOfHijriMonth(insideNextMonth).getTime() - start.getTime()) / MS_PER_DAY);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Builds the `Date` for a Hijri year/month/day. The month is zero based, and a day past the
|
|
291
|
+
* end of the month is clamped to the last day of that month.
|
|
292
|
+
*/
|
|
293
|
+
function createHijriDate(year, month, day) {
|
|
294
|
+
const targetYear = year + Math.floor(month / 12);
|
|
295
|
+
const targetMonth = ((month % 12) + 12) % 12;
|
|
296
|
+
const monthsFromAnchor = (targetYear - ANCHOR_HIJRI_YEAR) * 12 + targetMonth;
|
|
297
|
+
const cursor = new Date(ANCHOR_GREGORIAN.year, ANCHOR_GREGORIAN.month, ANCHOR_GREGORIAN.day, 12);
|
|
298
|
+
cursor.setDate(cursor.getDate() + Math.round(monthsFromAnchor * MEAN_HIJRI_MONTH));
|
|
299
|
+
let start = startOfHijriMonth(cursor);
|
|
300
|
+
// the mean-month estimate drifts by a month or so over long spans; step whole months until it matches
|
|
301
|
+
for (let attempt = 0; attempt < 8; attempt++) {
|
|
302
|
+
const parts = getHijriParts(start);
|
|
303
|
+
const monthsOff = (targetYear - parts.year) * 12 + (targetMonth - parts.month);
|
|
304
|
+
if (monthsOff === 0) {
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
// 29 days plus a nudge in the direction of travel always lands inside the neighbouring month
|
|
308
|
+
start.setDate(start.getDate() + monthsOff * 29 + Math.sign(monthsOff));
|
|
309
|
+
start = startOfHijriMonth(start);
|
|
310
|
+
}
|
|
311
|
+
const clampedDay = Math.min(Math.max(day, 1), getHijriDaysInMonth(start));
|
|
312
|
+
start.setDate(start.getDate() + clampedDay - 1);
|
|
313
|
+
start.setHours(0, 0, 0, 0);
|
|
314
|
+
return start;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Formats a date as a Hijri `yyyy-MM-dd` string — the shape the form emits when
|
|
318
|
+
* `emitHijri` is on.
|
|
319
|
+
*/
|
|
320
|
+
function formatHijriDate(date) {
|
|
321
|
+
const parts = getHijriParts(date);
|
|
322
|
+
return `${pad(parts.year, 4)}-${pad(parts.month + 1, 2)}-${pad(parts.day, 2)}`;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Reads a Hijri date back into a `Date`, accepting both the emitted `yyyy-MM-dd` shape and
|
|
326
|
+
* the `dd/MM/yyyy` shape shown in the input. Returns `null` for anything else.
|
|
327
|
+
*/
|
|
328
|
+
function parseHijriDate(value) {
|
|
329
|
+
if (!value) {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
// strip the bidi marks Intl adds around Arabic-locale dates
|
|
333
|
+
const clean = value.replace(/[]/g, "").trim();
|
|
334
|
+
const iso = clean.match(/^(\d{3,4})-(\d{1,2})-(\d{1,2})/);
|
|
335
|
+
if (iso) {
|
|
336
|
+
return createHijriDate(+iso[1], +iso[2] - 1, +iso[3]);
|
|
337
|
+
}
|
|
338
|
+
const slashed = clean.match(/^(\d{1,2})[/-](\d{1,2})[/-](\d{3,4})$/);
|
|
339
|
+
if (slashed) {
|
|
340
|
+
return createHijriDate(+slashed[3], +slashed[2] - 1, +slashed[1]);
|
|
341
|
+
}
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
function pad(value, length) {
|
|
345
|
+
return String(value).padStart(length, "0");
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Renders a stored form value as a Hijri `dd/MM/yyyy` string for the read-only viewers, or
|
|
349
|
+
* returns `null` when the field is not on the Hijri calendar and the value should be shown
|
|
350
|
+
* as an ordinary Gregorian date.
|
|
351
|
+
*
|
|
352
|
+
* The two Hijri modes store different things, so they are read differently: with `emitHijri`
|
|
353
|
+
* the value already carries Hijri numbers and only its notation changes, while with
|
|
354
|
+
* `hijriCalendar` alone the value is Gregorian and has to be converted.
|
|
355
|
+
*/
|
|
356
|
+
function toHijriDisplay(value, options) {
|
|
357
|
+
if (!value || (!options.hijriCalendar && !options.emitHijri)) {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
let datePart;
|
|
361
|
+
let hours;
|
|
362
|
+
let minutes;
|
|
363
|
+
if (options.emitHijri) {
|
|
364
|
+
const matched = value.match(/^(\d{3,4})-(\d{1,2})-(\d{1,2})(?:[T ](\d{1,2}):(\d{2}))?/);
|
|
365
|
+
if (!matched) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
datePart = `${pad(+matched[3], 2)}/${pad(+matched[2], 2)}/${pad(+matched[1], 4)}`;
|
|
369
|
+
hours = +(matched[4] ?? 0);
|
|
370
|
+
minutes = +(matched[5] ?? 0);
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
const date = new Date(value);
|
|
374
|
+
if (Number.isNaN(date.getTime())) {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
const parts = getHijriParts(date);
|
|
378
|
+
datePart = `${pad(parts.day, 2)}/${pad(parts.month + 1, 2)}/${pad(parts.year, 4)}`;
|
|
379
|
+
hours = date.getHours();
|
|
380
|
+
minutes = date.getMinutes();
|
|
381
|
+
}
|
|
382
|
+
if (!options.withTime) {
|
|
383
|
+
return datePart;
|
|
384
|
+
}
|
|
385
|
+
return `${datePart} ${pad(hours % 12 || 12, 2)}:${pad(minutes, 2)} ${hours < 12 ? "AM" : "PM"}`;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
class Hints {
|
|
389
|
+
hints = input([], ...(ngDevMode ? [{ debugName: "hints" }] : /* istanbul ignore next */ []));
|
|
390
|
+
touched = input(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
|
|
391
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: Hints, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
392
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: Hints, isStandalone: true, selector: "iss-ctrl-hints", inputs: { hints: { classPropertyName: "hints", publicName: "hints", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
393
|
+
@if (hints().length) {
|
|
394
|
+
<div class="d-flex align-items-center flex-wrap gap-2 mb-3">
|
|
395
|
+
@for (hint of hints(); track $index) {
|
|
396
|
+
@if (hint) {
|
|
397
|
+
<mat-hint [align]="hint.align" class="d-flex align-items-center gap-1 fs-12">
|
|
398
|
+
@if (hint.valid === undefined) {
|
|
399
|
+
<ds-icon icon="info" class="fs-24 fc-dark-gray"></ds-icon>
|
|
400
|
+
<span class="fc-dark-gray text-break">{{ hint.label }}</span>
|
|
401
|
+
} @else {
|
|
402
|
+
<ds-icon
|
|
403
|
+
icon="{{ touched() ? (hint.valid ? 'check-circle-f' : 'close-circle') : 'check-circle-o' }}"
|
|
404
|
+
class="fs-24 {{ touched() ? (hint.valid ? 'fc-green' : 'fc-red') : 'fc-dark-gray' }}"
|
|
405
|
+
>
|
|
406
|
+
</ds-icon>
|
|
407
|
+
<span
|
|
408
|
+
class="{{ touched() ? (hint.valid ? 'fc-green' : 'fc-red') : 'fc-dark-gray' }} text-break"
|
|
409
|
+
>{{ hint.label }}</span>
|
|
410
|
+
}
|
|
411
|
+
</mat-hint>
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
</div>
|
|
415
|
+
}
|
|
416
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: MatHint, selector: "mat-hint", inputs: ["align", "id"] }] });
|
|
417
|
+
}
|
|
418
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: Hints, decorators: [{
|
|
419
|
+
type: Component,
|
|
420
|
+
args: [{
|
|
421
|
+
selector: "iss-ctrl-hints",
|
|
422
|
+
imports: [MatHint,],
|
|
423
|
+
template: `
|
|
424
|
+
@if (hints().length) {
|
|
425
|
+
<div class="d-flex align-items-center flex-wrap gap-2 mb-3">
|
|
426
|
+
@for (hint of hints(); track $index) {
|
|
427
|
+
@if (hint) {
|
|
428
|
+
<mat-hint [align]="hint.align" class="d-flex align-items-center gap-1 fs-12">
|
|
429
|
+
@if (hint.valid === undefined) {
|
|
430
|
+
<ds-icon icon="info" class="fs-24 fc-dark-gray"></ds-icon>
|
|
431
|
+
<span class="fc-dark-gray text-break">{{ hint.label }}</span>
|
|
432
|
+
} @else {
|
|
433
|
+
<ds-icon
|
|
434
|
+
icon="{{ touched() ? (hint.valid ? 'check-circle-f' : 'close-circle') : 'check-circle-o' }}"
|
|
435
|
+
class="fs-24 {{ touched() ? (hint.valid ? 'fc-green' : 'fc-red') : 'fc-dark-gray' }}"
|
|
436
|
+
>
|
|
437
|
+
</ds-icon>
|
|
438
|
+
<span
|
|
439
|
+
class="{{ touched() ? (hint.valid ? 'fc-green' : 'fc-red') : 'fc-dark-gray' }} text-break"
|
|
440
|
+
>{{ hint.label }}</span>
|
|
441
|
+
}
|
|
442
|
+
</mat-hint>
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
</div>
|
|
446
|
+
}
|
|
447
|
+
`,
|
|
448
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
|
449
|
+
}]
|
|
450
|
+
}], propDecorators: { hints: [{ type: i0.Input, args: [{ isSignal: true, alias: "hints", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }] } });
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* The single date adapter behind every `iss-ctrl-*` picker.
|
|
454
|
+
*
|
|
455
|
+
* It behaves as a plain Gregorian adapter — `dd/MM/yyyy` in and out — until its locale is
|
|
456
|
+
* switched to {@link HIJRI_LOCALE}, at which point the whole calendar grid becomes Umm
|
|
457
|
+
* al-Qura Hijri: the month lengths, the year and month labels and the day numbers all come
|
|
458
|
+
* from the Hijri calendar.
|
|
459
|
+
*
|
|
460
|
+
* The value never changes shape. The adapter always works on a `Date`, so a Hijri calendar
|
|
461
|
+
* still hands the control an ordinary `Date`, and it stays the control's decision whether to
|
|
462
|
+
* emit that as a Gregorian or a Hijri string.
|
|
463
|
+
*
|
|
464
|
+
* Switch modes through {@link setHijri}, never by assigning `locale` — `setLocale` is what
|
|
465
|
+
* tells an open calendar to re-render.
|
|
466
|
+
*/
|
|
467
|
+
class IssDateAdapter extends NativeDateAdapter {
|
|
468
|
+
/** The Gregorian locale to fall back to when the Hijri calendar is turned off. */
|
|
469
|
+
gregorianLocale = "en-GB";
|
|
470
|
+
#language = inject(UI_LANGUAGE, { optional: true });
|
|
471
|
+
/** Whether the adapter is currently presenting the Hijri calendar. */
|
|
472
|
+
get isHijri() {
|
|
473
|
+
return typeof this.locale === "string" && this.locale.includes("islamic");
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Turns the Hijri calendar on or off. Goes through `setLocale` so that a calendar that is
|
|
477
|
+
* already open redraws itself.
|
|
478
|
+
*/
|
|
479
|
+
setHijri(hijri) {
|
|
480
|
+
const locale = hijri ? HIJRI_LOCALE : this.gregorianLocale;
|
|
481
|
+
if (this.locale !== locale) {
|
|
482
|
+
this.setLocale(locale);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
getYear(date) {
|
|
486
|
+
return this.isHijri ? getHijriParts(date).year : date.getFullYear();
|
|
487
|
+
}
|
|
488
|
+
getMonth(date) {
|
|
489
|
+
return this.isHijri ? getHijriParts(date).month : date.getMonth();
|
|
490
|
+
}
|
|
491
|
+
getDate(date) {
|
|
492
|
+
return this.isHijri ? getHijriParts(date).day : date.getDate();
|
|
493
|
+
}
|
|
494
|
+
getMonthNames(style) {
|
|
495
|
+
if (!this.isHijri) {
|
|
496
|
+
return super.getMonthNames(style);
|
|
497
|
+
}
|
|
498
|
+
if (this.#language === "en") {
|
|
499
|
+
return style === "long" ? HIJRI_MONTH_NAMES.en : HIJRI_MONTH_NAMES.enShort;
|
|
500
|
+
}
|
|
501
|
+
return HIJRI_MONTH_NAMES.ar;
|
|
502
|
+
}
|
|
503
|
+
getDateNames() {
|
|
504
|
+
// a Hijri month never runs past 30 days, but the calendar body indexes this array by
|
|
505
|
+
// day number, so it must stay as long as the Gregorian one
|
|
506
|
+
return this.isHijri ? Array.from({ length: 31 }, (_, index) => String(index + 1)) : super.getDateNames();
|
|
507
|
+
}
|
|
508
|
+
getDayOfWeekNames(style) {
|
|
509
|
+
if (!this.isHijri) {
|
|
510
|
+
return super.getDayOfWeekNames(style);
|
|
511
|
+
}
|
|
512
|
+
// the week is the same week in both calendars, so only the language of the names changes
|
|
513
|
+
const names = new Intl.DateTimeFormat(this.#language === "en" ? "en-GB" : "ar", { weekday: style, timeZone: "utc" });
|
|
514
|
+
return Array.from({ length: 7 }, (_, index) => names.format(new Date(Date.UTC(2017, 0, index + 1))));
|
|
515
|
+
}
|
|
516
|
+
getYearName(date) {
|
|
517
|
+
return this.isHijri ? String(getHijriParts(date).year) : String(date.getFullYear());
|
|
518
|
+
}
|
|
519
|
+
getFirstDayOfWeek() {
|
|
520
|
+
return this.isHijri ? 0 : super.getFirstDayOfWeek();
|
|
521
|
+
}
|
|
522
|
+
getNumDaysInMonth(date) {
|
|
523
|
+
return this.isHijri ? getHijriDaysInMonth(date) : super.getNumDaysInMonth(date);
|
|
524
|
+
}
|
|
525
|
+
createDate(year, month, date) {
|
|
526
|
+
return this.isHijri ? createHijriDate(year, month, date) : super.createDate(year, month, date);
|
|
527
|
+
}
|
|
528
|
+
addCalendarYears(date, years) {
|
|
529
|
+
return this.addCalendarMonths(date, years * 12);
|
|
530
|
+
}
|
|
531
|
+
addCalendarMonths(date, months) {
|
|
532
|
+
if (!this.isHijri) {
|
|
533
|
+
return super.addCalendarMonths(date, months);
|
|
534
|
+
}
|
|
535
|
+
const parts = getHijriParts(date);
|
|
536
|
+
return this.#withTimeOf(createHijriDate(parts.year, parts.month + months, parts.day), date);
|
|
537
|
+
}
|
|
538
|
+
addCalendarDays(date, days) {
|
|
539
|
+
if (!this.isHijri) {
|
|
540
|
+
return super.addCalendarDays(date, days);
|
|
541
|
+
}
|
|
542
|
+
// a Hijri day is the same day, so this is plain Gregorian day arithmetic
|
|
543
|
+
const shifted = new Date(date.getTime());
|
|
544
|
+
shifted.setDate(shifted.getDate() + days);
|
|
545
|
+
return shifted;
|
|
546
|
+
}
|
|
547
|
+
parse(value, parseFormat) {
|
|
548
|
+
if (typeof value !== "string") {
|
|
549
|
+
return super.parse(value, parseFormat);
|
|
550
|
+
}
|
|
551
|
+
const clean = value.replace(/[]/g, "").trim();
|
|
552
|
+
if (!clean) {
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
if (this.isHijri) {
|
|
556
|
+
return parseHijriDate(clean) ?? this.invalid();
|
|
557
|
+
}
|
|
558
|
+
const slashed = clean.match(/^(\d{1,2})[/-](\d{1,2})[/-](\d{4})$/);
|
|
559
|
+
if (slashed) {
|
|
560
|
+
return new Date(+slashed[3], +slashed[2] - 1, +slashed[1]);
|
|
561
|
+
}
|
|
562
|
+
return super.parse(clean, parseFormat);
|
|
563
|
+
}
|
|
564
|
+
format(date, displayFormat) {
|
|
565
|
+
if (!this.isValid(date)) {
|
|
566
|
+
throw Error("IssDateAdapter: Cannot format invalid date.");
|
|
567
|
+
}
|
|
568
|
+
const year = this.getYear(date);
|
|
569
|
+
const month = this.getMonth(date);
|
|
570
|
+
const day = this.getDate(date);
|
|
571
|
+
// NativeDateAdapter formats through Intl in UTC, which shifts the day for anyone west of
|
|
572
|
+
// Greenwich; formatting the parts we already read keeps the rendered day the picked one.
|
|
573
|
+
// One pass, so a substituted month name can never be re-read as a token.
|
|
574
|
+
return (displayFormat ?? "dd/MM/yyyy").replace(/yyyy|MMMM|MMM|MM|dd/g, (token) => {
|
|
575
|
+
switch (token) {
|
|
576
|
+
case "yyyy":
|
|
577
|
+
return String(year).padStart(4, "0");
|
|
578
|
+
case "MMMM":
|
|
579
|
+
return this.getMonthNames("long")[month];
|
|
580
|
+
case "MMM":
|
|
581
|
+
return this.getMonthNames("short")[month];
|
|
582
|
+
case "MM":
|
|
583
|
+
return String(month + 1).padStart(2, "0");
|
|
584
|
+
default:
|
|
585
|
+
return String(day).padStart(2, "0");
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
deserialize(value) {
|
|
590
|
+
// `yyyy-MM-dd` is a calendar day, not an instant: NativeDateAdapter reads it as UTC
|
|
591
|
+
// midnight, which renders as the previous day in any negative-offset zone
|
|
592
|
+
if (typeof value === "string") {
|
|
593
|
+
const iso = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
594
|
+
if (iso) {
|
|
595
|
+
return new Date(+iso[1], +iso[2] - 1, +iso[3]);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return super.deserialize(value);
|
|
599
|
+
}
|
|
600
|
+
toIso8601(date) {
|
|
601
|
+
return [String(date.getFullYear()).padStart(4, "0"), String(date.getMonth() + 1).padStart(2, "0"), String(date.getDate()).padStart(2, "0")].join("-");
|
|
602
|
+
}
|
|
603
|
+
#withTimeOf(date, source) {
|
|
604
|
+
const result = new Date(date.getTime());
|
|
605
|
+
result.setHours(source.getHours(), source.getMinutes(), source.getSeconds(), source.getMilliseconds());
|
|
606
|
+
return result;
|
|
607
|
+
}
|
|
608
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: IssDateAdapter, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
609
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: IssDateAdapter });
|
|
610
|
+
}
|
|
611
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: IssDateAdapter, decorators: [{
|
|
612
|
+
type: Injectable
|
|
613
|
+
}] });
|
|
614
|
+
/**
|
|
615
|
+
* Providers every date-based control needs: the {@link IssDateAdapter} itself, the Gregorian
|
|
616
|
+
* locale it falls back to, and the `dd/MM/yyyy` formats both of its calendars are rendered in.
|
|
617
|
+
*
|
|
618
|
+
* Declared per component, so each picker owns its adapter instance and one field switching to
|
|
619
|
+
* the Hijri calendar leaves every other field on the form alone.
|
|
620
|
+
*/
|
|
621
|
+
function provideIssDateAdapter() {
|
|
622
|
+
return [
|
|
623
|
+
{ provide: MAT_DATE_LOCALE, useValue: "en-GB" },
|
|
624
|
+
{ provide: DateAdapter, useClass: IssDateAdapter },
|
|
625
|
+
{ provide: MAT_DATE_FORMATS, useValue: DATE_FORMAT_ADAPTER },
|
|
626
|
+
];
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
class SelectStatusPipe {
|
|
630
|
+
transform(option, ...args) {
|
|
631
|
+
const selectedOptions = args[0];
|
|
632
|
+
return selectedOptions.has(option.value);
|
|
633
|
+
}
|
|
634
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SelectStatusPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
635
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: SelectStatusPipe, isStandalone: true, name: "selectStatus" });
|
|
636
|
+
}
|
|
637
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SelectStatusPipe, decorators: [{
|
|
638
|
+
type: Pipe,
|
|
639
|
+
args: [{ name: "selectStatus" }]
|
|
640
|
+
}] });
|
|
641
|
+
|
|
642
|
+
class TranslatePipe {
|
|
643
|
+
currentLanguage = inject(UI_LANGUAGE);
|
|
644
|
+
transform(value, locale = "") {
|
|
645
|
+
if (typeof value === "string") {
|
|
646
|
+
return value; // Return the string as is, if it's a string
|
|
647
|
+
}
|
|
648
|
+
if (value && locale) {
|
|
649
|
+
return value[locale] ?? "";
|
|
650
|
+
}
|
|
651
|
+
return value?.[this.currentLanguage] ?? "";
|
|
652
|
+
}
|
|
653
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: TranslatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
654
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: TranslatePipe, isStandalone: true, name: "translate" });
|
|
655
|
+
}
|
|
656
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: TranslatePipe, decorators: [{
|
|
657
|
+
type: Pipe,
|
|
658
|
+
args: [{ name: "translate" }]
|
|
659
|
+
}] });
|
|
660
|
+
|
|
661
|
+
class ViewerBase {
|
|
662
|
+
field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
|
|
663
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ViewerBase, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
664
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.9", type: ViewerBase, isStandalone: true, selector: "ng-component", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: ``, isInline: true });
|
|
665
|
+
}
|
|
666
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ViewerBase, decorators: [{
|
|
667
|
+
type: Component,
|
|
668
|
+
args: [{ template: `` }]
|
|
669
|
+
}], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }] } });
|
|
670
|
+
|
|
671
|
+
// The pieces every control is built from.
|
|
672
|
+
//
|
|
673
|
+
// These live in their own entry point so each control can be published as one too without
|
|
674
|
+
// depending on the primary entry point — which owns the COMPONENTS/VIEWERS indexes and
|
|
675
|
+
// would therefore close a dependency cycle. Nothing here may import from `iss-ctrl`.
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Generated bundle index. Do not edit.
|
|
679
|
+
*/
|
|
680
|
+
|
|
681
|
+
export { ControlBase, DATETIME_FORMAT, DATE_FORMAT_ADAPTER, HIJRI_LOCALE, HIJRI_MONTH_NAMES, Hints, IssDateAdapter, SelectStatusPipe, TranslatePipe, ViewerBase, createHijriDate, formatHijriDate, getHijriDaysInMonth, getHijriParts, parseHijriDate, provideIssDateAdapter, startOfHijriMonth, toHijriDisplay };
|
|
682
|
+
//# sourceMappingURL=iss-ctrl-base.mjs.map
|