@per-diem-calculator/vanilla 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/.prettierrc +17 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1 -0
  4. package/eslint.config.js +29 -0
  5. package/index.html +11 -0
  6. package/package.json +49 -0
  7. package/public/output.css +2503 -0
  8. package/src/css/_styles.css +8 -0
  9. package/src/css/colors.css +45 -0
  10. package/src/css/fonts.css +9 -0
  11. package/src/css/rows/_heights.css +6 -0
  12. package/src/css/rows/_index.css +15 -0
  13. package/src/css/rows/add.css +18 -0
  14. package/src/css/rows/animate-btns.css +18 -0
  15. package/src/css/rows/animate-row-close.css +18 -0
  16. package/src/css/rows/animate-row-open.css +14 -0
  17. package/src/css/rows/animate-row-other.css +5 -0
  18. package/src/css/rows/btn-add-row.css +41 -0
  19. package/src/css/rows/btn-delete.css +22 -0
  20. package/src/css/rows/btn-expenses-calculate.css +22 -0
  21. package/src/css/rows/btn-expenses-category.css +22 -0
  22. package/src/css/rows/delete.css +10 -0
  23. package/src/css/rows/details.css +22 -0
  24. package/src/css/rows/expense.css +18 -0
  25. package/src/css/rows/location.css +34 -0
  26. package/src/css/rows/summary.css +22 -0
  27. package/src/css/tom-select/defaults.css +530 -0
  28. package/src/css/tom-select/overrides.css +55 -0
  29. package/src/css/tw-shadow-props.css +50 -0
  30. package/src/index.ts +1 -0
  31. package/src/ts/components/Button/Button.ts +50 -0
  32. package/src/ts/components/Button/template.html +34 -0
  33. package/src/ts/components/ExpenseRow/ExpenseRow.ts +397 -0
  34. package/src/ts/components/ExpenseRow/template.html +260 -0
  35. package/src/ts/components/Label/Label.ts +45 -0
  36. package/src/ts/components/Label/template.html +1 -0
  37. package/src/ts/components/LocationCategory/LocationCategory.ts +226 -0
  38. package/src/ts/components/LocationCategory/template.html +520 -0
  39. package/src/ts/components/LocationDate/LocationDate.ts +366 -0
  40. package/src/ts/components/LocationDate/template.html +27 -0
  41. package/src/ts/components/LocationSelect/LocationSelect.ts +299 -0
  42. package/src/ts/components/LocationSelect/template.html +45 -0
  43. package/src/ts/components/index.ts +6 -0
  44. package/src/ts/controller.ts +193 -0
  45. package/src/ts/model.ts +163 -0
  46. package/src/ts/types/config.ts +22 -0
  47. package/src/ts/types/dates.ts +82 -0
  48. package/src/ts/types/expenses.ts +73 -0
  49. package/src/ts/types/locations.ts +25 -0
  50. package/src/ts/utils/config/configDefault.ts +13 -0
  51. package/src/ts/utils/config/index.ts +12 -0
  52. package/src/ts/utils/config/numbers.ts +24 -0
  53. package/src/ts/utils/config/sanitizeConfig.ts +39 -0
  54. package/src/ts/utils/dates/INPUT_DATE_MINMAX.ts +5 -0
  55. package/src/ts/utils/dates/YEAR_REGEX.ts +4 -0
  56. package/src/ts/utils/dates/getDateSlice.ts +54 -0
  57. package/src/ts/utils/dates/getValidAPIYear.ts +17 -0
  58. package/src/ts/utils/dates/index.ts +19 -0
  59. package/src/ts/utils/dates/isDateRaw.ts +90 -0
  60. package/src/ts/utils/dates/isShortMonth.ts +24 -0
  61. package/src/ts/utils/dates/isYYYY.ts +10 -0
  62. package/src/ts/utils/dates/offsetDateString.ts +17 -0
  63. package/src/ts/utils/expenses/INTL_MIE_RATES.ts +2125 -0
  64. package/src/ts/utils/expenses/createExpenseObjs.ts +35 -0
  65. package/src/ts/utils/expenses/getLodgingRateDomestic.ts +73 -0
  66. package/src/ts/utils/expenses/getLodgingRateIntl.ts +119 -0
  67. package/src/ts/utils/expenses/getMieRates.ts +84 -0
  68. package/src/ts/utils/expenses/index.ts +5 -0
  69. package/src/ts/utils/expenses/parseIntlLodgingRates.ts +124 -0
  70. package/src/ts/utils/expenses/returnValidStateExpense.ts +46 -0
  71. package/src/ts/utils/fetch/fetchJsonGSA.ts +29 -0
  72. package/src/ts/utils/fetch/fetchXmlDOD.ts +38 -0
  73. package/src/ts/utils/fetch/index.ts +3 -0
  74. package/src/ts/utils/fetch/memoize.ts +46 -0
  75. package/src/ts/utils/fetch/parseXml.ts +19 -0
  76. package/src/ts/utils/locations/getCitiesDomestic.ts +48 -0
  77. package/src/ts/utils/locations/getCitiesIntl.ts +63 -0
  78. package/src/ts/utils/locations/getCountriesDomestic.ts +237 -0
  79. package/src/ts/utils/locations/getCountriesIntl.ts +34 -0
  80. package/src/ts/utils/locations/index.ts +6 -0
  81. package/src/ts/utils/locations/keepUniqueLocations.ts +12 -0
  82. package/src/ts/utils/locations/locationKeys.ts +10 -0
  83. package/src/ts/utils/locations/returnValidStateLocation.ts +13 -0
  84. package/src/ts/utils/locations/sortLocations.ts +19 -0
  85. package/src/ts/utils/misc/USD.ts +4 -0
  86. package/src/ts/utils/misc/debounce.ts +22 -0
  87. package/src/ts/utils/misc/handlePointerDown.ts +3 -0
  88. package/src/ts/utils/misc/handlePointerUp.ts +22 -0
  89. package/src/ts/utils/misc/inPrimitiveType.ts +4 -0
  90. package/src/ts/utils/misc/index.ts +6 -0
  91. package/src/ts/utils/misc/wait.ts +4 -0
  92. package/src/ts/utils/styles/applyStyles.ts +19 -0
  93. package/src/ts/utils/styles/highlightInput.ts +15 -0
  94. package/src/ts/utils/styles/index.ts +3 -0
  95. package/src/ts/utils/styles/removeStyles.ts +14 -0
  96. package/src/ts/views/Expense/Expense.ts +465 -0
  97. package/src/ts/views/Expense/template.html +176 -0
  98. package/src/ts/views/Location/Location.ts +763 -0
  99. package/src/ts/views/Location/template-row.html +146 -0
  100. package/src/ts/views/Location/template.html +130 -0
  101. package/src/ts/views/index.ts +2 -0
  102. package/tsconfig.json +27 -0
  103. package/vite.config.ts +12 -0
@@ -0,0 +1,45 @@
1
+ // Utils
2
+ import { applyStyles, removeStyles } from '../../utils/styles';
3
+
4
+ // HTML/CSS
5
+ import templateHTML from './template.html?raw';
6
+
7
+ // Template for this Custom Element
8
+ const template = document.createElement('template');
9
+
10
+ // Custom Element
11
+ export class PdcLabel extends HTMLElement {
12
+ /* SETUP
13
+ */
14
+ static observedAttributes = ['text'];
15
+ #styled = false;
16
+ constructor() {
17
+ super();
18
+ this.attachShadow({ mode: 'open' });
19
+ this.#styled = this.getAttribute('styled') === 'true';
20
+
21
+ if (!this.shadowRoot)
22
+ throw new Error(
23
+ 'Failed to render shadowRoot in label custom element.',
24
+ );
25
+
26
+ if (this.#styled) {
27
+ template.innerHTML = templateHTML;
28
+ applyStyles(this.shadowRoot);
29
+ } else template.innerHTML = removeStyles(templateHTML);
30
+
31
+ this.shadowRoot?.appendChild(template.content.cloneNode(true));
32
+ }
33
+
34
+ /* EVENTS
35
+ */
36
+ attributeChangedCallback(
37
+ _name: string,
38
+ _oldValue: string,
39
+ newValue: string,
40
+ ) {
41
+ this.shadowRoot
42
+ ?.querySelector('label')
43
+ ?.insertAdjacentHTML('afterbegin', newValue);
44
+ }
45
+ }
@@ -0,0 +1 @@
1
+ <label class="text-neutral-700"></label>
@@ -0,0 +1,226 @@
1
+ // Types
2
+
3
+ // Utils
4
+ import { handlePointerDown, handlePointerUp } from '../../utils/misc';
5
+ import { applyStyles, removeStyles } from '../../utils/styles';
6
+
7
+ // HTML/CSS
8
+ import templateHTML from './template.html?raw';
9
+
10
+ // Template for this Custom Element
11
+ const template = document.createElement('template');
12
+
13
+ // Custom Element
14
+ export class PdcLocationCategory extends HTMLElement {
15
+ /* SETUP
16
+ */
17
+ static observedAttributes = ['bg'];
18
+
19
+ #valid = false;
20
+ #styled = false;
21
+ #enabled = false;
22
+
23
+ constructor() {
24
+ super();
25
+ this.attachShadow({ mode: 'open' });
26
+ this.#styled = this.getAttribute('styled') === 'true';
27
+ this.#render();
28
+ }
29
+
30
+ #render() {
31
+ if (this.#styled) {
32
+ template.innerHTML = templateHTML;
33
+ applyStyles(this.#shadowRoot);
34
+ } else template.innerHTML = removeStyles(templateHTML);
35
+ this.#shadowRoot.appendChild(template.content.cloneNode(true));
36
+ this.enableTabIndex(false);
37
+ this.#styleEl();
38
+ this.#addEventListeners();
39
+ }
40
+
41
+ /* EVENTS
42
+ */
43
+ attributeChangedCallback(
44
+ _name: string,
45
+ _oldValue: string,
46
+ newValue: string,
47
+ ) {
48
+ if (!this.#styled) return;
49
+ this.#styleEl(`bg-${newValue === 'white' ? 'white' : 'neutral-50'}`);
50
+ }
51
+
52
+ #addEventListeners() {
53
+ // Mouse, touch events
54
+ let pointerStartX = 0;
55
+ let pointerStartY = 0;
56
+
57
+ this.#fieldset.addEventListener('pointerdown', e => {
58
+ if (!(e instanceof PointerEvent)) return;
59
+ const result = handlePointerDown(e);
60
+ pointerStartX = result.pointerStartX;
61
+ pointerStartY = result.pointerStartY;
62
+ });
63
+
64
+ this.#fieldset.addEventListener('pointerup', e => {
65
+ if (e instanceof PointerEvent) {
66
+ const result = handlePointerUp(
67
+ e,
68
+ this.#handleClicks.bind(this),
69
+ pointerStartX,
70
+ pointerStartY,
71
+ );
72
+ pointerStartX = result.pointerStartX;
73
+ pointerStartY = result.pointerStartY;
74
+ }
75
+ });
76
+
77
+ // Keyboard events
78
+ this.#fieldset.addEventListener('keydown', e => {
79
+ if (!(e.key === 'Enter' || e.key === ' ')) return;
80
+ this.#handleClicks(e);
81
+ });
82
+ }
83
+
84
+ #handleClicks(e: Event) {
85
+ if (!this.#enabled) return;
86
+ const target = e.composedPath()[0];
87
+ if (!(target instanceof HTMLElement || target instanceof SVGElement))
88
+ return;
89
+ const label = target.closest('label');
90
+ const labelVal = label?.getAttribute('for');
91
+ // In case the selection was made with keyboard presses, ensure the input fields are properly checked/unchecked so that the CSS can reflect that
92
+ const checkedInput = this.#fieldset.querySelector<HTMLInputElement>(
93
+ `#${labelVal}`,
94
+ );
95
+ const uncheckedInput = this.#fieldset.querySelector<HTMLInputElement>(
96
+ `#${labelVal === 'domestic' ? 'intl' : 'domestic'}`,
97
+ );
98
+ if (!(checkedInput && uncheckedInput)) return;
99
+ uncheckedInput.checked = false;
100
+ checkedInput.checked = true;
101
+ if (labelVal) this.setAttribute('category', labelVal);
102
+ if (this.#styled) {
103
+ label?.classList.add('success');
104
+ this.#renderError(false);
105
+ }
106
+ }
107
+
108
+ /* GET ELS
109
+ */
110
+ get #fieldset() {
111
+ const el = this.#shadowRoot.querySelector('fieldset');
112
+ if (!el)
113
+ throw new Error(
114
+ `Failed to render fieldset in Category custom element`,
115
+ );
116
+ return el;
117
+ }
118
+
119
+ get #inputs() {
120
+ const els = this.#shadowRoot.querySelectorAll('input');
121
+ if (!els)
122
+ throw new Error(
123
+ `Failed to render inputs in Category custom element`,
124
+ );
125
+ return els;
126
+ }
127
+
128
+ get #labels() {
129
+ const els = this.#shadowRoot.querySelectorAll('label');
130
+ if (!els)
131
+ throw new Error(
132
+ `Failed to render labels in Category custom element`,
133
+ );
134
+ return els;
135
+ }
136
+
137
+ get #shadowRoot() {
138
+ if (!this.shadowRoot)
139
+ throw new Error(
140
+ `Failed to render shadowRoot in Category custom element`,
141
+ );
142
+ return this.shadowRoot;
143
+ }
144
+
145
+ get #errorEl() {
146
+ const el = this.closest('#locations-container')?.querySelector(
147
+ '#error',
148
+ );
149
+ if (!el)
150
+ throw new Error(
151
+ `Failed to get View's error element from Category custom element`,
152
+ );
153
+ return el;
154
+ }
155
+
156
+ /* VISUAL METHODS
157
+ */
158
+ #styleEl(bgColor: 'bg-white' | 'bg-neutral-50' | null = null) {
159
+ if (!this.#styled) return;
160
+ this.#fieldset.classList.remove(`bg-white`, `bg-neutral-50`);
161
+ this.#fieldset.classList.add(
162
+ bgColor ? bgColor : `bg-${this.getAttribute('bg')}`,
163
+ );
164
+ }
165
+
166
+ focusEl() {
167
+ [...this.#labels][0].focus();
168
+ }
169
+
170
+ /* UPDATE METHODS
171
+ */
172
+ enable(enable: boolean) {
173
+ this.removeAttribute('category');
174
+ this.#inputs.forEach(input => {
175
+ input.checked = false;
176
+ if (enable) input.removeAttribute('disabled');
177
+ else input.setAttribute('disabled', 'true');
178
+ });
179
+ this.enableTabIndex(enable);
180
+ this.#enabled = enable;
181
+ }
182
+
183
+ enableTabIndex(enable: boolean) {
184
+ const els = this.#fieldset.querySelectorAll('[tabindex]');
185
+ els.forEach(el => el.setAttribute('tabindex', enable ? '0' : '-1'));
186
+ }
187
+
188
+ /* VALIDATION
189
+ */
190
+ #renderError(enable: boolean, msg = `Select a category.`) {
191
+ if (enable) {
192
+ if (this.#styled) {
193
+ this.#labels.forEach(label => label.classList.add('error'));
194
+ this.#errorEl.classList.add('active');
195
+ }
196
+ this.#errorEl.textContent = msg;
197
+ return;
198
+ }
199
+ if (!this.#styled) return;
200
+ this.#labels.forEach(label => label.classList.remove('error'));
201
+ this.#errorEl.classList.remove('active');
202
+ }
203
+
204
+ validate() {
205
+ this.#valid = false;
206
+ if (!this.hasAttribute('category')) {
207
+ this.#renderError(true);
208
+ return this.#valid;
209
+ }
210
+ this.#valid = true;
211
+ return this.#valid;
212
+ }
213
+
214
+ /* GET DATA METHODS
215
+ */
216
+ get isEnabled() {
217
+ return this.#enabled;
218
+ }
219
+
220
+ get pdcValue() {
221
+ const value = this.getAttribute('category');
222
+ return value && (value === 'domestic' || value === 'intl') ?
223
+ value
224
+ : null;
225
+ }
226
+ }