@massimo-cassandro/minimo 1.6.0 → 1.7.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 (37) hide show
  1. package/index.js +10 -3
  2. package/package.json +4 -2
  3. package/src/components/autocomplete/autocomplete-engine.js +70 -64
  4. package/src/components/autocomplete/check-autocomplete.js +10 -2
  5. package/src/components/dismiss-alerts.js +9 -2
  6. package/src/components/form-reset-submit.js +12 -4
  7. package/src/components/inner-nav/inner-nav.js +5 -1
  8. package/src/components/modal-alert/customization-templates/custom-modal-alert.js +4 -0
  9. package/src/components/modal-alert/defaults.js +6 -3
  10. package/src/components/modal-alert/modal-alert.js +62 -21
  11. package/src/components/modal-popup/modal-popup.js +51 -29
  12. package/src/components/overlay/overlay.js +13 -1
  13. package/src/components/sf-macro/sf-macro.js +23 -12
  14. package/src/components/snackbar/snackbar.js +32 -17
  15. package/src/components/spinner/spinner.js +6 -2
  16. package/src/components/unsplash-page/fetch-unsplash-data.js +25 -2
  17. package/src/components/unsplash-page/img-breakpoints.js +11 -0
  18. package/src/components/unsplash-page/unsplash-page.js +64 -40
  19. package/src/css/forms/forms.css +4 -0
  20. package/src/utilities/better-text.js +80 -0
  21. package/src/utilities/classnames.js +14 -1
  22. package/src/utilities/cookies.js +28 -0
  23. package/src/utilities/create-id.js +19 -0
  24. package/src/utilities/dom-builder/dom-builder.js +84 -75
  25. package/src/utilities/dom-builder/domBuilderBasicSetup.js +12 -9
  26. package/src/utilities/dom-builder/parseDomString.js +31 -29
  27. package/src/utilities/enable-submit-btns.js +7 -1
  28. package/src/utilities/{escapeHTML.js → escape-html.js} +13 -0
  29. package/src/utilities/linked-dates.js +64 -0
  30. package/src/utilities/relative-date.js +153 -0
  31. package/src/utilities/sentence-case.js +5 -2
  32. package/src/utilities/strip-tags.js +8 -0
  33. package/src/utilities/title-case.js +6 -2
  34. package/src/utilities/truncate-string.js +22 -0
  35. package/src/utilities/validation-error-class.js +14 -7
  36. package/src/web-components/s-datatable-component/s-datatable-component.js +1 -0
  37. package/src/web-components/s-datatable-component/src/parse-cols.js +1 -0
@@ -0,0 +1,64 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * Links date/datetime-local input fields so that one field constrains
5
+ * the min/max of another.
6
+ *
7
+ * The `data-max` attribute on a date field must contain the `id` of the field
8
+ * whose value sets the `max` constraint of the current field (start date).
9
+ * The `data-min` attribute on a date field must contain the `id` of the field
10
+ * whose value sets the `min` constraint of the current field (end date).
11
+ *
12
+ * If the linked field is disabled, the constraint is removed on focus
13
+ * and restored when the linked field is re-enabled.
14
+ *
15
+ * @param {Document|Element} context - root element to search within (defaults to `document`)
16
+ * @returns {void}
17
+ */
18
+ export function linkedDates(context = document) {
19
+
20
+ // start date fields: constrained by a max value from a linked field
21
+ context.querySelectorAll('input[type="date"][data-max], input[type="datetime-local"][data-max]').forEach(el => {
22
+ const maxId = /** @type {HTMLInputElement} */ (el).dataset.max;
23
+ let linked_field = maxId ? document.getElementById(maxId) : null;
24
+
25
+ if(linked_field) {
26
+ el.setAttribute('max', /** @type {HTMLInputElement} */ (linked_field).value);
27
+
28
+ linked_field.addEventListener('change', () => {
29
+ el.setAttribute('max', /** @type {HTMLInputElement} */ (linked_field).value);
30
+ });
31
+
32
+ // remove max if the linked field is disabled; restore it when re-focused enabled
33
+ el.addEventListener('focus', () => {
34
+ if(/** @type {HTMLInputElement} */ (linked_field).disabled) {
35
+ el.removeAttribute('max');
36
+ } else {
37
+ el.setAttribute('max', /** @type {HTMLInputElement} */ (linked_field).value);
38
+ }
39
+ }, false);
40
+ }
41
+ });
42
+
43
+ // end date fields: constrained by a min value from a linked field
44
+ context.querySelectorAll('input[type="date"][data-min], input[type="datetime-local"][data-min]').forEach(el => {
45
+ const minId = /** @type {HTMLInputElement} */ (el).dataset.min;
46
+ let linked_field = minId ? document.getElementById(minId) : null;
47
+
48
+ if(linked_field) {
49
+ linked_field.addEventListener('change', () => {
50
+ el.setAttribute('min', /** @type {HTMLInputElement} */ (linked_field).value);
51
+ });
52
+
53
+ // remove min if the linked field is disabled; restore it when re-focused enabled
54
+ el.addEventListener('focus', () => {
55
+ if(/** @type {HTMLInputElement} */ (linked_field).disabled) {
56
+ el.removeAttribute('min');
57
+ } else {
58
+ el.setAttribute('min', /** @type {HTMLInputElement} */ (linked_field).value);
59
+ }
60
+ }, false);
61
+ }
62
+ });
63
+
64
+ }
@@ -0,0 +1,153 @@
1
+ // @ts-check
2
+
3
+ // Displays a date in relative form to the current date if within the set limits.
4
+ // Relative dates are automatically updated every minute.
5
+
6
+ /**
7
+ * @typedef {object} RelativeDateOptions
8
+ * @property {boolean} [useRelativeTime=true] - Show times near the date as "in xx minutes" (before) or "now" (within `relativeTimeMinutesAfter`)
9
+ * @property {string} [nowString] - String to display when the date is right now
10
+ * @property {number} [relativeTimeMinutesBefore] - Minutes before the date within which to show "in xx minutes"
11
+ * @property {number} [relativeTimeMinutesAfter] - Minutes after the date within which to show the now string
12
+ * @property {boolean} [relativeTimeShowTime] - Show the actual time alongside relative date labels
13
+ * @property {string} [relativeTimeShowTimeMarkup] - HTML markup for the time portion; `@@time@@` is replaced with the localized time string
14
+ * @property {boolean} [firstLetterUpperCase] - Capitalize the first letter of the returned string
15
+ * @property {Intl.DateTimeFormatOptions} [dateFormat] - Format for non-relative dates (beyond day after tomorrow)
16
+ * @property {Intl.DateTimeFormatOptions} [timeFormat] - Format for time displayed in relative date labels
17
+ * @property {string[]} [relativeStrings] - Labels for yesterday / today / tomorrow / day after tomorrow (in chronological order)
18
+ * @property {string} [locale] - Locale string for date/time formatting
19
+ */
20
+
21
+ /**
22
+ * Returns an HTML `<time>` element string displaying the date in relative or absolute form.
23
+ * Relative dates are auto-refreshed every minute via a shared `setInterval`.
24
+ *
25
+ * @param {Date | string} date - Date to display (Date object or ISO string)
26
+ * @param {RelativeDateOptions} [options={}]
27
+ * @returns {string} HTML `<time>` element as a string
28
+ *
29
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat
30
+ */
31
+
32
+ // TODO: Add option to include start time in relative dates
33
+ // TODO: alternative solution in place of `@@time@@` placeholder
34
+ // TODO reconsider and evaluate the use of Intl.RelativeTimeFormat Intl.RelativeTimeFormat
35
+
36
+ export function relativeDate(date, options = {}) {
37
+
38
+ /** @type {Required<RelativeDateOptions>} */
39
+ const default_options = {
40
+ useRelativeTime: true,
41
+ nowString: 'adesso',
42
+ relativeTimeMinutesBefore: 30,
43
+ relativeTimeMinutesAfter: 30,
44
+ relativeTimeShowTime: true,
45
+ relativeTimeShowTimeMarkup: ' <span class="time-info">(alle @@time@@)</span>',
46
+ firstLetterUpperCase: true,
47
+ dateFormat: {
48
+ weekday: 'short',
49
+ year: 'numeric',
50
+ month: 'short',
51
+ day: 'numeric',
52
+ hour12: false,
53
+ hour: '2-digit',
54
+ minute: '2-digit'
55
+ },
56
+ timeFormat: {
57
+ hour12: false,
58
+ hour: '2-digit',
59
+ minute: '2-digit'
60
+ },
61
+ relativeStrings: [ // NB: in chronological order
62
+ 'ieri alle', 'oggi alle', 'domani alle', 'dopodomani alle'
63
+ ],
64
+ locale: 'it-IT'
65
+ };
66
+
67
+ let isRelative = false;
68
+
69
+ /**
70
+ * @param {Date | string} dateInput
71
+ * @param {RelativeDateOptions} [overrideOptions={}]
72
+ * @returns {string}
73
+ */
74
+ const parseDate = (dateInput, overrideOptions = {}) => {
75
+
76
+ const opts = { ...default_options, ...overrideOptions };
77
+
78
+ const d = typeof dateInput === 'string' ? new Date(dateInput) : dateInput
79
+ , now = new Date()
80
+ , minutesDiff = (d.getTime() - now.getTime()) / 60000 // difference between the two dates in minutes
81
+ , daysDiff = (
82
+ new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0).getTime() -
83
+ new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0).getTime()
84
+ ) / 86400000
85
+ , rtf = new Intl.RelativeTimeFormat('it', { style: 'long' })
86
+ ;
87
+
88
+ /** @type {string} */
89
+ let result;
90
+
91
+ if (opts.useRelativeTime && Math.floor(Math.abs(minutesDiff)) === 0) {
92
+
93
+ result = opts.nowString;
94
+ isRelative = true;
95
+
96
+ } else if (opts.useRelativeTime && (
97
+ (Math.abs(minutesDiff) <= opts.relativeTimeMinutesAfter && minutesDiff < 0) ||
98
+ (minutesDiff > 0 && minutesDiff <= opts.relativeTimeMinutesBefore)
99
+ )) {
100
+
101
+ // formatToParts + reduce needed to strip decimal fractions from the value
102
+ const parts = rtf.formatToParts(minutesDiff, 'minute').reduce((acc, curr) => {
103
+ if (curr.type !== 'decimal') { // decimal separator
104
+ if (curr.type === 'fraction') { // fractional part
105
+ const intPart = acc.find(i => i.type === 'integer');
106
+ if (intPart) intPart.value += `.${curr.value}`;
107
+ } else {
108
+ acc.push(curr);
109
+ }
110
+ }
111
+ return acc;
112
+ }, /** @type {Intl.RelativeTimeFormatPart[]} */ ([]));
113
+
114
+ result = parts.map(i => i.type !== 'literal' ? Math.round(+i.value) : i.value).join('') +
115
+ (opts.relativeTimeShowTime ? opts.relativeTimeShowTimeMarkup?.replace('@@time@@', d.toLocaleString(opts.locale, opts.timeFormat)) : '');
116
+
117
+ isRelative = true;
118
+
119
+ // between -1 day and +2 days
120
+ } else if (daysDiff >= -1 && daysDiff <= 2) {
121
+
122
+ result = opts.relativeStrings[daysDiff + 1] + ' ' + d.toLocaleString(opts.locale, opts.timeFormat);
123
+ isRelative = daysDiff === 0;
124
+
125
+ } else {
126
+ result = d.toLocaleString(opts.locale, opts.dateFormat);
127
+ }
128
+
129
+ if (opts.firstLetterUpperCase && result) {
130
+ result = result.charAt(0).toUpperCase() + result.slice(1).toLowerCase();
131
+ }
132
+
133
+ return `<time datetime="${d.toISOString()}"${isRelative ? ` data-relative-date-opts="${encodeURIComponent(JSON.stringify(overrideOptions))}"` : ''}>${result}</time>`;
134
+
135
+ }; // end parseDate
136
+
137
+ const result = parseDate(date, options);
138
+
139
+ if (isRelative && !(/** @type {any} */ (window)).relativeDateUpd) {
140
+ /** @type {any} */ (window).relativeDateUpd = true;
141
+ setInterval(() => {
142
+ document.querySelectorAll('time[data-relative-date-opts]').forEach(item => {
143
+ const datetime = item.getAttribute('datetime');
144
+ const optsAttr = /** @type {HTMLElement} */ (item).dataset.relativeDateOpts;
145
+ if (datetime && optsAttr) {
146
+ item.outerHTML = parseDate(datetime, JSON.parse(decodeURIComponent(optsAttr)));
147
+ }
148
+ });
149
+ }, 60000);
150
+ }
151
+
152
+ return result;
153
+ }
@@ -1,5 +1,8 @@
1
-
2
- // prima lettera maiuscola, il resto minuscolo
1
+ /**
2
+ * Converts a string to sentence case (first letter uppercase, rest lowercase).
3
+ * @param {string | null | undefined} str
4
+ * @returns {string | null | undefined}
5
+ */
3
6
  export function sentenceCase(str) {
4
7
  if(str) {
5
8
  return str.charAt(0).toUpperCase() + str.substr(1).toLowerCase();
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Strips HTML tags from a string.
3
+ * @param {string | null | undefined} str
4
+ * @returns {string}
5
+ */
6
+ export function stripTags(str) {
7
+ return (str || '')?.replace(/(<([^>]+)>)/gi, '').trim();
8
+ }
@@ -1,4 +1,8 @@
1
- // imposta un testo in cui ogni parola è minuscola tranne la prima lettera
1
+ /**
2
+ * Converts a string to title case (first letter of each word uppercase, rest lowercase).
3
+ * @param {string | null | undefined} str
4
+ * @returns {string | null | undefined}
5
+ */
2
6
  export function titleCase(str) {
3
7
 
4
8
  if(str) {
@@ -11,4 +15,4 @@ export function titleCase(str) {
11
15
  }
12
16
 
13
17
  return str;
14
- } // end title_case
18
+ }
@@ -0,0 +1,22 @@
1
+
2
+ // @ts-check
3
+
4
+ /**
5
+ * Truncates a string to the desired length adding an option suffix
6
+ * from https://www.codegrepper.com/code-examples/javascript/javascript+truncate+string+full+word
7
+ *
8
+ * @param {string} str - string yo be truncated
9
+ * @param {number} maxLength - truncated string max length
10
+ * @param {string} suffix
11
+ * @returns {string|undefined} truncated string, or undefined if str is falsy
12
+ *
13
+ */
14
+
15
+ export function truncateString(str, maxLength, suffix) {
16
+
17
+ suffix = suffix || '…';
18
+ if(str) {
19
+ return str.length < maxLength ? str :
20
+ `${str.substring(0, str.substring(0, maxLength - suffix.length).lastIndexOf(' '))}${suffix}`;
21
+ }
22
+ }
@@ -1,9 +1,8 @@
1
-
2
- // uso
3
- // throw new ValidationError('Sono presenti valori duplicati', campiErrore );
1
+ // usage:
2
+ // throw new ValidationError('Duplicate values found', errorFields);
4
3
 
5
4
  /*
6
- esempio:
5
+ example:
7
6
 
8
7
  const form = document.getElementById('form-__xxxx___');
9
8
  form.addEventListener('submit', e => {
@@ -13,7 +12,7 @@ form.addEventListener('submit', e => {
13
12
  form.classList.add('was-validated');
14
13
  try {
15
14
  if( ... ) {
16
- throw new ValidationError('__messaggio__', [campiErrore, ...] );
15
+ throw new ValidationError('__message__', [errorFields, ...] );
17
16
  }
18
17
  } catch( error ) {
19
18
  e.preventDefault();
@@ -35,10 +34,18 @@ form.addEventListener('submit', e => {
35
34
  });
36
35
  */
37
36
 
37
+ /**
38
+ * Custom error class for form validation failures.
39
+ * Extends the native Error with an array of invalid form fields.
40
+ */
38
41
  export class ValidationError extends Error {
42
+ /**
43
+ * @param {string} message - Error message.
44
+ * @param {Element[]} fields - Array of invalid form field elements.
45
+ */
39
46
  constructor(message, fields) {
40
- super(message); // Passa il messaggio alla classe Error originale
47
+ super(message);
41
48
  this.name = 'ValidationError';
42
- this.fields = fields; // Parametro custom
49
+ this.fields = fields;
43
50
  }
44
51
  }
@@ -1,3 +1,4 @@
1
+ // @ts-nocheck
1
2
  import { DataTable } from 'simple-datatables';
2
3
 
3
4
  import { parseCols } from './src/parse-cols.js';
@@ -1,3 +1,4 @@
1
+ // @ts-nocheck
1
2
 
2
3
  // pre-elaborazione tipi dati predefiniti
3
4
  // le chiavi che cominciano con '_' sono di questa applicazione, le altre sono di simple-datatable