tyrell-components 1.0.0-RC13 → 1.0.0-RC14
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 +1 -1
- package/css/tyrell-theme.css +167 -178
- package/css/tyrell.css +19 -0
- package/dist/tyrell-theme.css +167 -178
- package/dist/tyrell.css +19 -0
- package/dist/tyrell.js +1 -1
- package/lib/base/ty-component.d.ts +20 -10
- package/lib/base/ty-component.js +21 -144
- package/lib/components/button.d.ts +2 -16
- package/lib/components/button.js +0 -36
- package/lib/components/calendar-month.d.ts +2 -48
- package/lib/components/calendar-month.js +4 -166
- package/lib/components/calendar-navigation.d.ts +3 -48
- package/lib/components/calendar-navigation.js +3 -126
- package/lib/components/calendar.d.ts +3 -80
- package/lib/components/calendar.js +8 -230
- package/lib/components/checkbox.d.ts +4 -71
- package/lib/components/checkbox.js +7 -151
- package/lib/components/copy.d.ts +3 -56
- package/lib/components/copy.js +1 -120
- package/lib/components/date-picker.d.ts +13 -162
- package/lib/components/date-picker.js +41 -592
- package/lib/components/file-upload.d.ts +9 -15
- package/lib/components/file-upload.js +19 -41
- package/lib/components/icon.d.ts +3 -24
- package/lib/components/icon.js +1 -90
- package/lib/components/input.d.ts +7 -107
- package/lib/components/input.js +5 -193
- package/lib/components/modal.d.ts +17 -26
- package/lib/components/modal.js +57 -201
- package/lib/components/option.d.ts +2 -9
- package/lib/components/option.js +0 -48
- package/lib/components/popup.d.ts +2 -17
- package/lib/components/popup.js +8 -139
- package/lib/components/radio.d.ts +3 -23
- package/lib/components/radio.js +10 -65
- package/lib/components/resize-observer.d.ts +3 -30
- package/lib/components/resize-observer.js +12 -46
- package/lib/components/scroll-container.d.ts +2 -12
- package/lib/components/scroll-container.js +3 -46
- package/lib/components/select.d.ts +22 -153
- package/lib/components/select.js +33 -590
- package/lib/components/selected-tags.d.ts +4 -47
- package/lib/components/selected-tags.js +1 -95
- package/lib/components/step.d.ts +1 -6
- package/lib/components/step.js +0 -6
- package/lib/components/switch.d.ts +0 -11
- package/lib/components/switch.js +2 -36
- package/lib/components/tab.d.ts +1 -6
- package/lib/components/tab.js +0 -7
- package/lib/components/tabs.d.ts +5 -63
- package/lib/components/tabs.js +20 -283
- package/lib/components/tag.d.ts +2 -36
- package/lib/components/tag.js +5 -105
- package/lib/components/textarea.d.ts +6 -86
- package/lib/components/textarea.js +2 -173
- package/lib/components/tooltip.d.ts +3 -11
- package/lib/components/tooltip.js +24 -160
- package/lib/components/wizard.d.ts +6 -54
- package/lib/components/wizard.js +15 -228
- package/lib/icons/fontawesome/brands.d.ts +0 -1
- package/lib/icons/fontawesome/brands.js +0 -8
- package/lib/icons/fontawesome/regular.d.ts +0 -1
- package/lib/icons/fontawesome/regular.js +0 -8
- package/lib/icons/fontawesome/solid.d.ts +0 -1
- package/lib/icons/fontawesome/solid.js +0 -8
- package/lib/icons/heroicons/micro.d.ts +0 -1
- package/lib/icons/heroicons/micro.js +0 -8
- package/lib/icons/heroicons/mini.d.ts +0 -1
- package/lib/icons/heroicons/mini.js +0 -8
- package/lib/icons/heroicons/outline.d.ts +0 -1
- package/lib/icons/heroicons/outline.js +0 -8
- package/lib/icons/heroicons/solid.d.ts +0 -1
- package/lib/icons/heroicons/solid.js +0 -8
- package/lib/icons/lucide.d.ts +0 -1
- package/lib/icons/lucide.js +0 -1872
- package/lib/icons/material/filled.d.ts +0 -1
- package/lib/icons/material/filled.js +0 -10
- package/lib/icons/material/outlined.d.ts +0 -1
- package/lib/icons/material/outlined.js +0 -10
- package/lib/icons/material/round.d.ts +0 -1
- package/lib/icons/material/round.js +0 -10
- package/lib/icons/material/sharp.d.ts +0 -1
- package/lib/icons/material/sharp.js +0 -10
- package/lib/icons/material/two-tone.d.ts +0 -1
- package/lib/icons/material/two-tone.js +0 -10
- package/lib/index.d.ts +0 -1
- package/lib/index.js +0 -20
- package/lib/styles/button.d.ts +2 -20
- package/lib/styles/button.js +22 -122
- package/lib/styles/calendar-month.d.ts +1 -5
- package/lib/styles/calendar-month.js +4 -61
- package/lib/styles/calendar-navigation.d.ts +2 -6
- package/lib/styles/calendar-navigation.js +2 -27
- package/lib/styles/calendar.d.ts +1 -2
- package/lib/styles/calendar.js +1 -14
- package/lib/styles/checkbox.d.ts +0 -1
- package/lib/styles/checkbox.js +1 -23
- package/lib/styles/copy.d.ts +0 -1
- package/lib/styles/copy.js +2 -17
- package/lib/styles/custom-scrollbar.d.ts +1 -2
- package/lib/styles/custom-scrollbar.js +3 -18
- package/lib/styles/date-picker.d.ts +1 -5
- package/lib/styles/date-picker.js +35 -39
- package/lib/styles/file-upload.d.ts +1 -2
- package/lib/styles/file-upload.js +1 -16
- package/lib/styles/icon.d.ts +2 -6
- package/lib/styles/icon.js +7 -21
- package/lib/styles/input.d.ts +1 -6
- package/lib/styles/input.js +5 -54
- package/lib/styles/modal.d.ts +1 -2
- package/lib/styles/modal.js +21 -23
- package/lib/styles/option.d.ts +2 -6
- package/lib/styles/option.js +17 -12
- package/lib/styles/popup.d.ts +1 -2
- package/lib/styles/popup.js +26 -30
- package/lib/styles/radio.d.ts +0 -1
- package/lib/styles/radio.js +2 -16
- package/lib/styles/resize-observer.d.ts +1 -2
- package/lib/styles/resize-observer.js +0 -7
- package/lib/styles/scroll-container.d.ts +1 -2
- package/lib/styles/scroll-container.js +0 -19
- package/lib/styles/select-base.d.ts +0 -1
- package/lib/styles/select-base.js +68 -79
- package/lib/styles/select.d.ts +1 -2
- package/lib/styles/select.js +7 -15
- package/lib/styles/step.d.ts +1 -2
- package/lib/styles/step.js +0 -4
- package/lib/styles/switch.d.ts +0 -1
- package/lib/styles/switch.js +2 -20
- package/lib/styles/tab.d.ts +1 -2
- package/lib/styles/tab.js +0 -5
- package/lib/styles/tabs.d.ts +3 -11
- package/lib/styles/tabs.js +10 -48
- package/lib/styles/tag.d.ts +1 -5
- package/lib/styles/tag.js +2 -31
- package/lib/styles/textarea.d.ts +1 -2
- package/lib/styles/textarea.js +2 -45
- package/lib/styles/tooltip.d.ts +0 -1
- package/lib/styles/tooltip.js +0 -10
- package/lib/styles/wizard.d.ts +5 -21
- package/lib/styles/wizard.js +8 -88
- package/lib/types/common.d.ts +0 -2
- package/lib/types/common.js +0 -12
- package/lib/utils/calendar-utils.d.ts +4 -21
- package/lib/utils/calendar-utils.js +4 -177
- package/lib/utils/custom-scrollbar.d.ts +6 -20
- package/lib/utils/custom-scrollbar.js +1 -45
- package/lib/utils/flavor-sheet.d.ts +0 -1
- package/lib/utils/flavor-sheet.js +0 -23
- package/lib/utils/icon-registry.d.ts +0 -2
- package/lib/utils/icon-registry.js +1 -107
- package/lib/utils/icons.d.ts +0 -1
- package/lib/utils/icons.js +0 -9
- package/lib/utils/loader-registry.d.ts +0 -1
- package/lib/utils/loader-registry.js +0 -32
- package/lib/utils/locale.d.ts +2 -23
- package/lib/utils/locale.js +0 -86
- package/lib/utils/mobile.d.ts +0 -1
- package/lib/utils/mobile.js +0 -17
- package/lib/utils/number-format.d.ts +6 -3
- package/lib/utils/number-format.js +11 -67
- package/lib/utils/positioning.d.ts +50 -7
- package/lib/utils/positioning.js +52 -62
- package/lib/utils/property-capture.d.ts +5 -27
- package/lib/utils/property-capture.js +0 -108
- package/lib/utils/property-manager.d.ts +0 -1
- package/lib/utils/property-manager.js +0 -70
- package/lib/utils/resize-observer.d.ts +0 -1
- package/lib/utils/resize-observer.js +0 -36
- package/lib/utils/scroll-lock.d.ts +4 -11
- package/lib/utils/scroll-lock.js +0 -107
- package/lib/utils/styles.d.ts +0 -1
- package/lib/utils/styles.js +0 -24
- package/lib/version.d.ts +1 -2
- package/lib/version.js +1 -11
- package/package.json +2 -2
- package/lib/base/ty-component.d.ts.map +0 -1
- package/lib/base/ty-component.js.map +0 -1
- package/lib/components/button.d.ts.map +0 -1
- package/lib/components/button.js.map +0 -1
- package/lib/components/calendar-month.d.ts.map +0 -1
- package/lib/components/calendar-month.js.map +0 -1
- package/lib/components/calendar-navigation.d.ts.map +0 -1
- package/lib/components/calendar-navigation.js.map +0 -1
- package/lib/components/calendar.d.ts.map +0 -1
- package/lib/components/calendar.js.map +0 -1
- package/lib/components/checkbox.d.ts.map +0 -1
- package/lib/components/checkbox.js.map +0 -1
- package/lib/components/copy.d.ts.map +0 -1
- package/lib/components/copy.js.map +0 -1
- package/lib/components/date-picker.d.ts.map +0 -1
- package/lib/components/date-picker.js.map +0 -1
- package/lib/components/file-upload.d.ts.map +0 -1
- package/lib/components/file-upload.js.map +0 -1
- package/lib/components/icon.d.ts.map +0 -1
- package/lib/components/icon.js.map +0 -1
- package/lib/components/input.d.ts.map +0 -1
- package/lib/components/input.js.map +0 -1
- package/lib/components/modal.d.ts.map +0 -1
- package/lib/components/modal.js.map +0 -1
- package/lib/components/option.d.ts.map +0 -1
- package/lib/components/option.js.map +0 -1
- package/lib/components/popup.d.ts.map +0 -1
- package/lib/components/popup.js.map +0 -1
- package/lib/components/radio.d.ts.map +0 -1
- package/lib/components/radio.js.map +0 -1
- package/lib/components/resize-observer.d.ts.map +0 -1
- package/lib/components/resize-observer.js.map +0 -1
- package/lib/components/scroll-container.d.ts.map +0 -1
- package/lib/components/scroll-container.js.map +0 -1
- package/lib/components/select.d.ts.map +0 -1
- package/lib/components/select.js.map +0 -1
- package/lib/components/selected-tags.d.ts.map +0 -1
- package/lib/components/selected-tags.js.map +0 -1
- package/lib/components/step.d.ts.map +0 -1
- package/lib/components/step.js.map +0 -1
- package/lib/components/switch.d.ts.map +0 -1
- package/lib/components/switch.js.map +0 -1
- package/lib/components/tab.d.ts.map +0 -1
- package/lib/components/tab.js.map +0 -1
- package/lib/components/tabs.d.ts.map +0 -1
- package/lib/components/tabs.js.map +0 -1
- package/lib/components/tag.d.ts.map +0 -1
- package/lib/components/tag.js.map +0 -1
- package/lib/components/textarea.d.ts.map +0 -1
- package/lib/components/textarea.js.map +0 -1
- package/lib/components/tooltip.d.ts.map +0 -1
- package/lib/components/tooltip.js.map +0 -1
- package/lib/components/wizard.d.ts.map +0 -1
- package/lib/components/wizard.js.map +0 -1
- package/lib/icons/fontawesome/brands.d.ts.map +0 -1
- package/lib/icons/fontawesome/brands.js.map +0 -1
- package/lib/icons/fontawesome/regular.d.ts.map +0 -1
- package/lib/icons/fontawesome/regular.js.map +0 -1
- package/lib/icons/fontawesome/solid.d.ts.map +0 -1
- package/lib/icons/fontawesome/solid.js.map +0 -1
- package/lib/icons/heroicons/micro.d.ts.map +0 -1
- package/lib/icons/heroicons/micro.js.map +0 -1
- package/lib/icons/heroicons/mini.d.ts.map +0 -1
- package/lib/icons/heroicons/mini.js.map +0 -1
- package/lib/icons/heroicons/outline.d.ts.map +0 -1
- package/lib/icons/heroicons/outline.js.map +0 -1
- package/lib/icons/heroicons/solid.d.ts.map +0 -1
- package/lib/icons/heroicons/solid.js.map +0 -1
- package/lib/icons/lucide.d.ts.map +0 -1
- package/lib/icons/lucide.js.map +0 -1
- package/lib/icons/material/filled.d.ts.map +0 -1
- package/lib/icons/material/filled.js.map +0 -1
- package/lib/icons/material/outlined.d.ts.map +0 -1
- package/lib/icons/material/outlined.js.map +0 -1
- package/lib/icons/material/round.d.ts.map +0 -1
- package/lib/icons/material/round.js.map +0 -1
- package/lib/icons/material/sharp.d.ts.map +0 -1
- package/lib/icons/material/sharp.js.map +0 -1
- package/lib/icons/material/two-tone.d.ts.map +0 -1
- package/lib/icons/material/two-tone.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/styles/button.d.ts.map +0 -1
- package/lib/styles/button.js.map +0 -1
- package/lib/styles/calendar-month.d.ts.map +0 -1
- package/lib/styles/calendar-month.js.map +0 -1
- package/lib/styles/calendar-navigation.d.ts.map +0 -1
- package/lib/styles/calendar-navigation.js.map +0 -1
- package/lib/styles/calendar.d.ts.map +0 -1
- package/lib/styles/calendar.js.map +0 -1
- package/lib/styles/checkbox.d.ts.map +0 -1
- package/lib/styles/checkbox.js.map +0 -1
- package/lib/styles/copy.d.ts.map +0 -1
- package/lib/styles/copy.js.map +0 -1
- package/lib/styles/custom-scrollbar.d.ts.map +0 -1
- package/lib/styles/custom-scrollbar.js.map +0 -1
- package/lib/styles/date-picker.d.ts.map +0 -1
- package/lib/styles/date-picker.js.map +0 -1
- package/lib/styles/file-upload.d.ts.map +0 -1
- package/lib/styles/file-upload.js.map +0 -1
- package/lib/styles/icon.d.ts.map +0 -1
- package/lib/styles/icon.js.map +0 -1
- package/lib/styles/input.d.ts.map +0 -1
- package/lib/styles/input.js.map +0 -1
- package/lib/styles/modal.d.ts.map +0 -1
- package/lib/styles/modal.js.map +0 -1
- package/lib/styles/multiselect.d.ts +0 -8
- package/lib/styles/multiselect.d.ts.map +0 -1
- package/lib/styles/multiselect.js +0 -849
- package/lib/styles/multiselect.js.map +0 -1
- package/lib/styles/option.d.ts.map +0 -1
- package/lib/styles/option.js.map +0 -1
- package/lib/styles/popup.d.ts.map +0 -1
- package/lib/styles/popup.js.map +0 -1
- package/lib/styles/radio.d.ts.map +0 -1
- package/lib/styles/radio.js.map +0 -1
- package/lib/styles/resize-observer.d.ts.map +0 -1
- package/lib/styles/resize-observer.js.map +0 -1
- package/lib/styles/scroll-container.d.ts.map +0 -1
- package/lib/styles/scroll-container.js.map +0 -1
- package/lib/styles/select-base.d.ts.map +0 -1
- package/lib/styles/select-base.js.map +0 -1
- package/lib/styles/select.d.ts.map +0 -1
- package/lib/styles/select.js.map +0 -1
- package/lib/styles/step.d.ts.map +0 -1
- package/lib/styles/step.js.map +0 -1
- package/lib/styles/switch.d.ts.map +0 -1
- package/lib/styles/switch.js.map +0 -1
- package/lib/styles/tab.d.ts.map +0 -1
- package/lib/styles/tab.js.map +0 -1
- package/lib/styles/tabs.d.ts.map +0 -1
- package/lib/styles/tabs.js.map +0 -1
- package/lib/styles/tag.d.ts.map +0 -1
- package/lib/styles/tag.js.map +0 -1
- package/lib/styles/textarea.d.ts.map +0 -1
- package/lib/styles/textarea.js.map +0 -1
- package/lib/styles/tooltip.d.ts.map +0 -1
- package/lib/styles/tooltip.js.map +0 -1
- package/lib/styles/wizard.d.ts.map +0 -1
- package/lib/styles/wizard.js.map +0 -1
- package/lib/types/common.d.ts.map +0 -1
- package/lib/types/common.js.map +0 -1
- package/lib/utils/calendar-utils.d.ts.map +0 -1
- package/lib/utils/calendar-utils.js.map +0 -1
- package/lib/utils/custom-scrollbar.d.ts.map +0 -1
- package/lib/utils/custom-scrollbar.js.map +0 -1
- package/lib/utils/flavor-sheet.d.ts.map +0 -1
- package/lib/utils/flavor-sheet.js.map +0 -1
- package/lib/utils/icon-registry.d.ts.map +0 -1
- package/lib/utils/icon-registry.js.map +0 -1
- package/lib/utils/icons.d.ts.map +0 -1
- package/lib/utils/icons.js.map +0 -1
- package/lib/utils/loader-registry.d.ts.map +0 -1
- package/lib/utils/loader-registry.js.map +0 -1
- package/lib/utils/locale.d.ts.map +0 -1
- package/lib/utils/locale.js.map +0 -1
- package/lib/utils/mobile.d.ts.map +0 -1
- package/lib/utils/mobile.js.map +0 -1
- package/lib/utils/number-format.d.ts.map +0 -1
- package/lib/utils/number-format.js.map +0 -1
- package/lib/utils/parse-boolean.d.ts +0 -39
- package/lib/utils/parse-boolean.d.ts.map +0 -1
- package/lib/utils/parse-boolean.js +0 -58
- package/lib/utils/parse-boolean.js.map +0 -1
- package/lib/utils/positioning.d.ts.map +0 -1
- package/lib/utils/positioning.js.map +0 -1
- package/lib/utils/property-capture.d.ts.map +0 -1
- package/lib/utils/property-capture.js.map +0 -1
- package/lib/utils/property-manager.d.ts.map +0 -1
- package/lib/utils/property-manager.js.map +0 -1
- package/lib/utils/resize-observer.d.ts.map +0 -1
- package/lib/utils/resize-observer.js.map +0 -1
- package/lib/utils/scroll-lock.d.ts.map +0 -1
- package/lib/utils/scroll-lock.js.map +0 -1
- package/lib/utils/styles.d.ts.map +0 -1
- package/lib/utils/styles.js.map +0 -1
- package/lib/version.d.ts.map +0 -1
- package/lib/version.js.map +0 -1
package/lib/components/modal.js
CHANGED
|
@@ -1,92 +1,43 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Modal Component
|
|
3
|
-
*
|
|
4
|
-
* Pure wrapper for modal dialogs using the native <dialog> element.
|
|
5
|
-
* Provides backdrop, focus management, scroll locking, and keyboard interaction
|
|
6
|
-
* without imposing any styling on content.
|
|
7
|
-
*
|
|
8
|
-
* Features:
|
|
9
|
-
* - Native dialog element wrapper
|
|
10
|
-
* - Dual API: declarative (open attribute) and imperative (show/hide methods)
|
|
11
|
-
* - Cancellable `beforeclose` lifecycle event for unsaved-state flows
|
|
12
|
-
* - Auto-hiding close button on hover (desktop only)
|
|
13
|
-
* - Scroll locking with unique modal IDs
|
|
14
|
-
* - Customizable backdrop via CSS variables
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* <ty-modal id="my-modal">
|
|
18
|
-
* <div class="ty-elevated p-6 rounded-lg max-w-md">
|
|
19
|
-
* <h3 class="text-lg font-semibold mb-4">Modal Title</h3>
|
|
20
|
-
* <p class="ty-text- mb-4">Modal content here</p>
|
|
21
|
-
* <ty-button onclick="this.closest('ty-modal').hide()">
|
|
22
|
-
* Close
|
|
23
|
-
* </ty-button>
|
|
24
|
-
* </div>
|
|
25
|
-
* </ty-modal>
|
|
26
|
-
*/
|
|
27
1
|
import { lockScroll, unlockScroll } from '../utils/scroll-lock.js';
|
|
28
2
|
import { ensureStyles } from '../utils/styles.js';
|
|
29
3
|
import { modalStyles } from '../styles/modal.js';
|
|
30
|
-
// ============================================================================
|
|
31
|
-
// WeakMaps for State Management
|
|
32
|
-
// ============================================================================
|
|
33
4
|
const backdropClickHandlers = new WeakMap();
|
|
34
5
|
const escapeKeyHandlers = new WeakMap();
|
|
35
6
|
const closeButtonHandlers = new WeakMap();
|
|
36
7
|
const hoverEnterHandlers = new WeakMap();
|
|
37
8
|
const hoverLeaveHandlers = new WeakMap();
|
|
38
|
-
const modalIds = new WeakMap();
|
|
39
|
-
|
|
40
|
-
// Constants
|
|
41
|
-
// ============================================================================
|
|
42
|
-
/**
|
|
43
|
-
* SVG icon for the close button (Lucide X icon)
|
|
44
|
-
*/
|
|
9
|
+
const modalIds = new WeakMap();
|
|
10
|
+
const pendingCloseReasons = new WeakMap();
|
|
45
11
|
const CLOSE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>`;
|
|
46
|
-
// ============================================================================
|
|
47
|
-
// Helper Functions
|
|
48
|
-
// ============================================================================
|
|
49
|
-
/**
|
|
50
|
-
* Parse boolean attribute
|
|
51
|
-
* Boolean attribute semantics: presence -> true, explicit 'false' -> false
|
|
52
|
-
* Matches ClojureScript implementation
|
|
53
|
-
*/
|
|
54
12
|
function parseBoolAttr(el, name) {
|
|
55
13
|
if (!el.hasAttribute(name)) {
|
|
56
14
|
return false;
|
|
57
15
|
}
|
|
58
16
|
const value = el.getAttribute(name);
|
|
59
|
-
// Explicit "false" string means false
|
|
60
17
|
if (value !== null && value.toLowerCase() === 'false') {
|
|
61
18
|
return false;
|
|
62
19
|
}
|
|
63
|
-
// Attribute present (even empty) means true
|
|
64
20
|
return true;
|
|
65
21
|
}
|
|
66
|
-
/**
|
|
67
|
-
* Read modal attributes with defaults
|
|
68
|
-
*/
|
|
69
22
|
function getModalAttributes(el) {
|
|
70
23
|
return {
|
|
71
24
|
open: parseBoolAttr(el, 'open'),
|
|
72
25
|
backdrop: el.hasAttribute('backdrop') ? parseBoolAttr(el, 'backdrop') : true,
|
|
73
|
-
closeOnOutsideClick: el
|
|
74
|
-
?
|
|
75
|
-
:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
26
|
+
closeOnOutsideClick: parseBoolAttr(el, 'prevent-outside-click')
|
|
27
|
+
? false
|
|
28
|
+
: el.hasAttribute('close-on-outside-click')
|
|
29
|
+
? parseBoolAttr(el, 'close-on-outside-click')
|
|
30
|
+
: true,
|
|
31
|
+
closeOnEscape: parseBoolAttr(el, 'prevent-escape')
|
|
32
|
+
? false
|
|
33
|
+
: el.hasAttribute('close-on-escape')
|
|
34
|
+
? parseBoolAttr(el, 'close-on-escape')
|
|
35
|
+
: true,
|
|
79
36
|
};
|
|
80
37
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Get the modal dialog element from shadow root
|
|
83
|
-
*/
|
|
84
38
|
function getModalDialog(shadowRoot) {
|
|
85
39
|
return shadowRoot.querySelector('.ty-modal-dialog');
|
|
86
40
|
}
|
|
87
|
-
/**
|
|
88
|
-
* Get or create a unique modal ID for scroll locking
|
|
89
|
-
*/
|
|
90
41
|
function getModalId(el) {
|
|
91
42
|
let id = modalIds.get(el);
|
|
92
43
|
if (!id) {
|
|
@@ -95,29 +46,16 @@ function getModalId(el) {
|
|
|
95
46
|
}
|
|
96
47
|
return id;
|
|
97
48
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Check if device is mobile (for hover behavior)
|
|
100
|
-
*/
|
|
101
49
|
function isMobile() {
|
|
102
50
|
return window.innerWidth < 768 || navigator.maxTouchPoints > 0;
|
|
103
51
|
}
|
|
104
|
-
/**
|
|
105
|
-
* Dispatch custom modal event
|
|
106
|
-
*/
|
|
107
52
|
function dispatchModalEvent(el, eventType, detail) {
|
|
108
53
|
const event = new CustomEvent(eventType, {
|
|
109
54
|
detail,
|
|
110
|
-
bubbles: true,
|
|
111
55
|
cancelable: true,
|
|
112
56
|
});
|
|
113
57
|
el.dispatchEvent(event);
|
|
114
58
|
}
|
|
115
|
-
// ============================================================================
|
|
116
|
-
// Dialog Structure Creation
|
|
117
|
-
// ============================================================================
|
|
118
|
-
/**
|
|
119
|
-
* Ensure the internal dialog structure exists in shadow DOM
|
|
120
|
-
*/
|
|
121
59
|
function ensureInternalDialog(shadowRoot) {
|
|
122
60
|
let dialog = getModalDialog(shadowRoot);
|
|
123
61
|
if (!dialog) {
|
|
@@ -125,81 +63,48 @@ function ensureInternalDialog(shadowRoot) {
|
|
|
125
63
|
const closeButton = document.createElement('button');
|
|
126
64
|
const contentDiv = document.createElement('div');
|
|
127
65
|
const slot = document.createElement('slot');
|
|
128
|
-
// Setup dialog structure
|
|
129
66
|
dialog.className = 'ty-modal-dialog';
|
|
130
|
-
// Setup close button with SVG icon
|
|
131
67
|
closeButton.className = 'close-button';
|
|
132
68
|
closeButton.type = 'button';
|
|
133
69
|
closeButton.setAttribute('aria-label', 'Close modal');
|
|
134
70
|
closeButton.innerHTML = CLOSE_ICON;
|
|
135
|
-
// Setup content container
|
|
136
71
|
contentDiv.className = 'modal-content';
|
|
137
72
|
contentDiv.appendChild(slot);
|
|
138
|
-
// Assemble: dialog > [close-button, content-div]
|
|
139
73
|
dialog.appendChild(closeButton);
|
|
140
74
|
dialog.appendChild(contentDiv);
|
|
141
75
|
shadowRoot.appendChild(dialog);
|
|
142
76
|
}
|
|
143
77
|
return dialog;
|
|
144
78
|
}
|
|
145
|
-
// ============================================================================
|
|
146
|
-
// Event Handlers
|
|
147
|
-
// ============================================================================
|
|
148
|
-
/**
|
|
149
|
-
* Close modal with cancellable beforeclose lifecycle.
|
|
150
|
-
*
|
|
151
|
-
* 1. Emit `beforeclose` (cancellable, bubbles, composed). Consumers can
|
|
152
|
-
* `event.preventDefault()` to abort the close — render your own UI for
|
|
153
|
-
* unsaved-state flows.
|
|
154
|
-
* 2. Remove the `open` attribute, which triggers the actual close.
|
|
155
|
-
*
|
|
156
|
-
* Callers pass `reason` so consumers can decide differently based on intent
|
|
157
|
-
* (e.g. "OK to discard via ESC, but not via .hide() during a save").
|
|
158
|
-
*/
|
|
159
79
|
function closeModal(el, reason = 'programmatic', opts) {
|
|
160
80
|
const shadowRoot = el.shadowRoot;
|
|
161
81
|
const dialog = shadowRoot ? getModalDialog(shadowRoot) : null;
|
|
162
82
|
if (!dialog || !dialog.open)
|
|
163
83
|
return;
|
|
164
|
-
// `force: true` bypasses the cancellable event. Used by consumers AFTER
|
|
165
|
-
// they've shown their own confirm UI and the user said "yes, close" —
|
|
166
|
-
// calling `.hide()` again without force would re-trigger their intercept.
|
|
167
84
|
if (!opts?.force) {
|
|
168
85
|
const beforeClose = new CustomEvent('beforeclose', {
|
|
169
86
|
detail: { reason },
|
|
170
|
-
bubbles: true,
|
|
171
|
-
composed: true,
|
|
172
87
|
cancelable: true,
|
|
173
88
|
});
|
|
174
89
|
el.dispatchEvent(beforeClose);
|
|
175
90
|
if (beforeClose.defaultPrevented)
|
|
176
91
|
return;
|
|
177
92
|
}
|
|
178
|
-
|
|
93
|
+
pendingCloseReasons.set(el, reason);
|
|
179
94
|
el.removeAttribute('open');
|
|
180
95
|
}
|
|
181
|
-
/**
|
|
182
|
-
* Open modal programmatically
|
|
183
|
-
*/
|
|
184
96
|
function openModal(el) {
|
|
185
97
|
el.setAttribute('open', 'true');
|
|
186
98
|
}
|
|
187
|
-
/**
|
|
188
|
-
* Handle backdrop click
|
|
189
|
-
*/
|
|
190
99
|
function handleBackdropClick(el, event) {
|
|
191
100
|
event.stopPropagation();
|
|
192
101
|
const shadowRoot = el.shadowRoot;
|
|
193
102
|
const dialog = shadowRoot ? getModalDialog(shadowRoot) : null;
|
|
194
|
-
// Only close if clicking on dialog backdrop (not content)
|
|
195
103
|
if (dialog && event.target === dialog) {
|
|
196
104
|
event.preventDefault();
|
|
197
105
|
closeModal(el, 'backdrop');
|
|
198
106
|
}
|
|
199
107
|
}
|
|
200
|
-
/**
|
|
201
|
-
* Handle escape key press
|
|
202
|
-
*/
|
|
203
108
|
function handleEscapeKey(el, event) {
|
|
204
109
|
event.stopPropagation();
|
|
205
110
|
if (event.key === 'Escape') {
|
|
@@ -207,17 +112,11 @@ function handleEscapeKey(el, event) {
|
|
|
207
112
|
closeModal(el, 'escape');
|
|
208
113
|
}
|
|
209
114
|
}
|
|
210
|
-
/**
|
|
211
|
-
* Handle close button click
|
|
212
|
-
*/
|
|
213
115
|
function handleCloseButtonClick(el, event) {
|
|
214
116
|
event.preventDefault();
|
|
215
117
|
event.stopPropagation();
|
|
216
118
|
closeModal(el, 'close-button');
|
|
217
119
|
}
|
|
218
|
-
/**
|
|
219
|
-
* Handle hover enter on modal content (hide close button)
|
|
220
|
-
*/
|
|
221
120
|
function handleHoverEnter(_el, event) {
|
|
222
121
|
const content = event.currentTarget;
|
|
223
122
|
const dialog = content.closest('.ty-modal-dialog');
|
|
@@ -228,9 +127,6 @@ function handleHoverEnter(_el, event) {
|
|
|
228
127
|
}
|
|
229
128
|
}
|
|
230
129
|
}
|
|
231
|
-
/**
|
|
232
|
-
* Handle hover leave on modal content (show close button)
|
|
233
|
-
*/
|
|
234
130
|
function handleHoverLeave(_el, event) {
|
|
235
131
|
const content = event.currentTarget;
|
|
236
132
|
const dialog = content.closest('.ty-modal-dialog');
|
|
@@ -241,75 +137,63 @@ function handleHoverLeave(_el, event) {
|
|
|
241
137
|
}
|
|
242
138
|
}
|
|
243
139
|
}
|
|
244
|
-
/**
|
|
245
|
-
* Setup backdrop click handling
|
|
246
|
-
*/
|
|
247
140
|
function setupBackdropClick(el, dialog, enabled) {
|
|
248
|
-
// Remove existing listener
|
|
249
141
|
const existingHandler = backdropClickHandlers.get(el);
|
|
250
142
|
if (existingHandler) {
|
|
251
143
|
dialog.removeEventListener('click', existingHandler);
|
|
252
144
|
backdropClickHandlers.delete(el);
|
|
253
145
|
}
|
|
254
|
-
// Add new listener if enabled
|
|
255
146
|
if (enabled) {
|
|
256
147
|
const handler = (e) => handleBackdropClick(el, e);
|
|
257
148
|
backdropClickHandlers.set(el, handler);
|
|
258
149
|
dialog.addEventListener('click', handler);
|
|
259
150
|
}
|
|
260
151
|
}
|
|
261
|
-
/**
|
|
262
|
-
* Setup escape key handling
|
|
263
|
-
*/
|
|
264
152
|
function setupEscapeKey(el, dialog, enabled) {
|
|
265
|
-
// Remove existing listener
|
|
266
153
|
const existingHandler = escapeKeyHandlers.get(el);
|
|
267
154
|
if (existingHandler) {
|
|
268
155
|
dialog.removeEventListener('keydown', existingHandler);
|
|
269
156
|
escapeKeyHandlers.delete(el);
|
|
270
157
|
}
|
|
271
|
-
// Add new listener if enabled
|
|
272
158
|
if (enabled) {
|
|
273
159
|
const handler = (e) => handleEscapeKey(el, e);
|
|
274
160
|
escapeKeyHandlers.set(el, handler);
|
|
275
161
|
dialog.addEventListener('keydown', handler);
|
|
276
162
|
}
|
|
163
|
+
else {
|
|
164
|
+
const handler = (e) => {
|
|
165
|
+
if (e.key === 'Escape')
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
};
|
|
168
|
+
escapeKeyHandlers.set(el, handler);
|
|
169
|
+
dialog.addEventListener('keydown', handler);
|
|
170
|
+
}
|
|
277
171
|
}
|
|
278
|
-
/**
|
|
279
|
-
* Setup close button click handling and visibility
|
|
280
|
-
*/
|
|
281
172
|
function setupCloseButton(el, dialog, showButton) {
|
|
282
173
|
const closeButton = dialog.querySelector('.close-button');
|
|
283
174
|
if (!closeButton)
|
|
284
175
|
return;
|
|
285
|
-
// Show or hide close button based on close-on-outside-click setting
|
|
286
176
|
if (showButton) {
|
|
287
177
|
closeButton.style.display = '';
|
|
288
178
|
}
|
|
289
179
|
else {
|
|
290
180
|
closeButton.style.display = 'none';
|
|
291
181
|
}
|
|
292
|
-
// Remove existing listener
|
|
293
182
|
const existingHandler = closeButtonHandlers.get(el);
|
|
294
183
|
if (existingHandler) {
|
|
295
184
|
closeButton.removeEventListener('click', existingHandler);
|
|
296
185
|
closeButtonHandlers.delete(el);
|
|
297
186
|
}
|
|
298
|
-
// Add click listener only if button is visible
|
|
299
187
|
if (showButton) {
|
|
300
188
|
const handler = (e) => handleCloseButtonClick(el, e);
|
|
301
189
|
closeButtonHandlers.set(el, handler);
|
|
302
190
|
closeButton.addEventListener('click', handler);
|
|
303
191
|
}
|
|
304
192
|
}
|
|
305
|
-
/**
|
|
306
|
-
* Setup close button hover handlers (desktop only)
|
|
307
|
-
*/
|
|
308
193
|
function setupCloseButtonHover(el, dialog) {
|
|
309
194
|
const modalContent = dialog.querySelector('.modal-content');
|
|
310
195
|
if (!modalContent)
|
|
311
196
|
return;
|
|
312
|
-
// Remove existing listeners
|
|
313
197
|
const existingEnterHandler = hoverEnterHandlers.get(el);
|
|
314
198
|
const existingLeaveHandler = hoverLeaveHandlers.get(el);
|
|
315
199
|
if (existingEnterHandler) {
|
|
@@ -320,7 +204,6 @@ function setupCloseButtonHover(el, dialog) {
|
|
|
320
204
|
modalContent.removeEventListener('mouseleave', existingLeaveHandler);
|
|
321
205
|
hoverLeaveHandlers.delete(el);
|
|
322
206
|
}
|
|
323
|
-
// Add hover listeners only on desktop
|
|
324
207
|
if (!isMobile()) {
|
|
325
208
|
const enterHandler = (e) => handleHoverEnter(el, e);
|
|
326
209
|
const leaveHandler = (e) => handleHoverLeave(el, e);
|
|
@@ -330,12 +213,6 @@ function setupCloseButtonHover(el, dialog) {
|
|
|
330
213
|
modalContent.addEventListener('mouseleave', leaveHandler);
|
|
331
214
|
}
|
|
332
215
|
}
|
|
333
|
-
// ============================================================================
|
|
334
|
-
// Core Render Function
|
|
335
|
-
// ============================================================================
|
|
336
|
-
/**
|
|
337
|
-
* Render the modal structure and sync state
|
|
338
|
-
*/
|
|
339
216
|
function render(el) {
|
|
340
217
|
const shadowRoot = el.shadowRoot;
|
|
341
218
|
if (!shadowRoot)
|
|
@@ -343,22 +220,14 @@ function render(el) {
|
|
|
343
220
|
const attributes = getModalAttributes(el);
|
|
344
221
|
const dialog = ensureInternalDialog(shadowRoot);
|
|
345
222
|
const modalId = getModalId(el);
|
|
346
|
-
// Ensure styles are loaded
|
|
347
223
|
ensureStyles(shadowRoot, { css: modalStyles, id: 'ty-modal' });
|
|
348
|
-
// Apply dialog class
|
|
349
224
|
dialog.className = 'ty-modal-dialog';
|
|
350
|
-
// Apply backdrop attribute
|
|
351
225
|
if (attributes.backdrop) {
|
|
352
226
|
dialog.setAttribute('data-backdrop', 'true');
|
|
353
227
|
}
|
|
354
228
|
else {
|
|
355
229
|
dialog.removeAttribute('data-backdrop');
|
|
356
230
|
}
|
|
357
|
-
// Accessible name: <dialog> has an implicit role="dialog" for free, but
|
|
358
|
-
// nothing gives it a NAME unless the consumer's own slotted heading
|
|
359
|
-
// happens to get wired up manually. Opt-in `label` attribute (same
|
|
360
|
-
// pattern as ty-select/ty-date-picker's `label` prop) — unset stays
|
|
361
|
-
// exactly as before (no regression, just no fix without opting in).
|
|
362
231
|
const label = el.getAttribute('label');
|
|
363
232
|
if (label) {
|
|
364
233
|
dialog.setAttribute('aria-label', label);
|
|
@@ -366,54 +235,42 @@ function render(el) {
|
|
|
366
235
|
else {
|
|
367
236
|
dialog.removeAttribute('aria-label');
|
|
368
237
|
}
|
|
369
|
-
// Setup event handlers
|
|
370
238
|
setupBackdropClick(el, dialog, attributes.closeOnOutsideClick);
|
|
371
239
|
setupEscapeKey(el, dialog, attributes.closeOnEscape);
|
|
240
|
+
dialog.oncancel = (event) => {
|
|
241
|
+
event.preventDefault();
|
|
242
|
+
if (getModalAttributes(el).closeOnEscape)
|
|
243
|
+
closeModal(el, 'escape');
|
|
244
|
+
};
|
|
372
245
|
setupCloseButton(el, dialog, attributes.closeOnOutsideClick);
|
|
373
246
|
setupCloseButtonHover(el, dialog);
|
|
374
|
-
// Sync open state with native dialog
|
|
375
247
|
if (attributes.open) {
|
|
376
248
|
if (!dialog.open) {
|
|
377
|
-
// Lock scroll
|
|
378
249
|
lockScroll(modalId);
|
|
379
|
-
// Show dialog
|
|
380
250
|
if (attributes.backdrop) {
|
|
381
251
|
dialog.showModal();
|
|
382
252
|
}
|
|
383
253
|
else {
|
|
384
254
|
dialog.show();
|
|
385
255
|
}
|
|
386
|
-
|
|
256
|
+
syncBackdropZoom();
|
|
387
257
|
dispatchModalEvent(el, 'open', {});
|
|
388
258
|
}
|
|
389
259
|
}
|
|
390
260
|
else {
|
|
391
261
|
if (dialog.open) {
|
|
392
|
-
// Unlock scroll
|
|
393
262
|
unlockScroll(modalId);
|
|
394
|
-
// Close dialog
|
|
395
263
|
dialog.close();
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
});
|
|
264
|
+
syncBackdropZoom();
|
|
265
|
+
const reason = pendingCloseReasons.get(el) ?? 'programmatic';
|
|
266
|
+
pendingCloseReasons.delete(el);
|
|
267
|
+
dispatchModalEvent(el, 'close', { reason });
|
|
400
268
|
}
|
|
401
269
|
}
|
|
402
|
-
// Handle dialog's native close event.
|
|
403
|
-
//
|
|
404
|
-
// Child components (ty-select, ty-date-picker) dispatch a
|
|
405
|
-
// bubbling+composed `close` custom event when their popups close. Those
|
|
406
|
-
// events bubble up to *this* dialog and trigger `onclose` too — without this
|
|
407
|
-
// guard, opening a dropdown inside a modal and closing the dropdown would
|
|
408
|
-
// close the whole modal. The native dialog `close` event is not composed
|
|
409
|
-
// and targets the dialog itself, so we only react when `event.target` is
|
|
410
|
-
// the dialog.
|
|
411
270
|
dialog.onclose = (event) => {
|
|
412
271
|
if (event.target !== dialog)
|
|
413
272
|
return;
|
|
414
|
-
// Ensure scroll is unlocked
|
|
415
273
|
unlockScroll(modalId);
|
|
416
|
-
// Sync the open attribute
|
|
417
274
|
if (el.hasAttribute('open')) {
|
|
418
275
|
el.removeAttribute('open');
|
|
419
276
|
dispatchModalEvent(el, 'close', {
|
|
@@ -421,35 +278,37 @@ function render(el) {
|
|
|
421
278
|
returnValue: dialog.returnValue || undefined
|
|
422
279
|
});
|
|
423
280
|
}
|
|
281
|
+
syncBackdropZoom();
|
|
424
282
|
};
|
|
425
283
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
284
|
+
function syncBackdropZoom() {
|
|
285
|
+
const anyOpen = Array.from(document.querySelectorAll('ty-modal[open][backdrop-zoom], ty-dialog[open][backdrop-zoom]')).some((m) => {
|
|
286
|
+
const v = m.getAttribute('backdrop-zoom').toLowerCase().trim();
|
|
287
|
+
return v !== 'false' && v !== '0';
|
|
288
|
+
});
|
|
289
|
+
document.documentElement.classList.toggle('ty-modal-zoom', anyOpen);
|
|
290
|
+
}
|
|
429
291
|
function cleanup(el) {
|
|
430
292
|
const modalId = modalIds.get(el);
|
|
431
293
|
const shadowRoot = el.shadowRoot;
|
|
432
294
|
const dialog = shadowRoot ? getModalDialog(shadowRoot) : null;
|
|
433
|
-
// Force unlock scroll if modal removed while open
|
|
434
295
|
if (modalId) {
|
|
435
296
|
unlockScroll(modalId);
|
|
436
297
|
modalIds.delete(el);
|
|
437
298
|
}
|
|
299
|
+
syncBackdropZoom();
|
|
438
300
|
if (!dialog)
|
|
439
301
|
return;
|
|
440
|
-
// Cleanup backdrop click handler
|
|
441
302
|
const backdropHandler = backdropClickHandlers.get(el);
|
|
442
303
|
if (backdropHandler) {
|
|
443
304
|
dialog.removeEventListener('click', backdropHandler);
|
|
444
305
|
backdropClickHandlers.delete(el);
|
|
445
306
|
}
|
|
446
|
-
// Cleanup escape key handler
|
|
447
307
|
const escapeHandler = escapeKeyHandlers.get(el);
|
|
448
308
|
if (escapeHandler) {
|
|
449
309
|
dialog.removeEventListener('keydown', escapeHandler);
|
|
450
310
|
escapeKeyHandlers.delete(el);
|
|
451
311
|
}
|
|
452
|
-
// Cleanup close button handler
|
|
453
312
|
const closeButton = dialog.querySelector('.close-button');
|
|
454
313
|
if (closeButton) {
|
|
455
314
|
const closeHandler = closeButtonHandlers.get(el);
|
|
@@ -458,7 +317,6 @@ function cleanup(el) {
|
|
|
458
317
|
closeButtonHandlers.delete(el);
|
|
459
318
|
}
|
|
460
319
|
}
|
|
461
|
-
// Cleanup hover handlers
|
|
462
320
|
const modalContent = dialog.querySelector('.modal-content');
|
|
463
321
|
if (modalContent) {
|
|
464
322
|
const enterHandler = hoverEnterHandlers.get(el);
|
|
@@ -473,16 +331,25 @@ function cleanup(el) {
|
|
|
473
331
|
}
|
|
474
332
|
}
|
|
475
333
|
}
|
|
476
|
-
// ============================================================================
|
|
477
|
-
// Component Definition
|
|
478
|
-
// ============================================================================
|
|
479
|
-
/**
|
|
480
|
-
* TyModal Web Component
|
|
481
|
-
*/
|
|
482
334
|
export class TyModal extends HTMLElement {
|
|
483
|
-
/** Observed attributes */
|
|
484
335
|
static get observedAttributes() {
|
|
485
|
-
return ['open', 'backdrop', 'close-on-outside-click', 'close-on-escape', 'label'];
|
|
336
|
+
return ['open', 'backdrop', 'close-on-outside-click', 'close-on-escape', 'prevent-outside-click', 'prevent-escape', 'label'];
|
|
337
|
+
}
|
|
338
|
+
get open() { return parseBoolAttr(this, 'open'); }
|
|
339
|
+
set open(v) { v ? this.setAttribute('open', 'true') : this.removeAttribute('open'); }
|
|
340
|
+
get backdrop() { return getModalAttributes(this).backdrop; }
|
|
341
|
+
set backdrop(v) { this.setAttribute('backdrop', String(!!v)); }
|
|
342
|
+
get closeOnOutsideClick() { return getModalAttributes(this).closeOnOutsideClick; }
|
|
343
|
+
set closeOnOutsideClick(v) { this.setAttribute('close-on-outside-click', String(!!v)); }
|
|
344
|
+
get closeOnEscape() { return getModalAttributes(this).closeOnEscape; }
|
|
345
|
+
set closeOnEscape(v) { this.setAttribute('close-on-escape', String(!!v)); }
|
|
346
|
+
get preventOutsideClick() { return parseBoolAttr(this, 'prevent-outside-click'); }
|
|
347
|
+
set preventOutsideClick(v) {
|
|
348
|
+
v ? this.setAttribute('prevent-outside-click', '') : this.removeAttribute('prevent-outside-click');
|
|
349
|
+
}
|
|
350
|
+
get preventEscape() { return parseBoolAttr(this, 'prevent-escape'); }
|
|
351
|
+
set preventEscape(v) {
|
|
352
|
+
v ? this.setAttribute('prevent-escape', '') : this.removeAttribute('prevent-escape');
|
|
486
353
|
}
|
|
487
354
|
constructor() {
|
|
488
355
|
super();
|
|
@@ -490,7 +357,6 @@ export class TyModal extends HTMLElement {
|
|
|
490
357
|
}
|
|
491
358
|
connectedCallback() {
|
|
492
359
|
render(this);
|
|
493
|
-
// Add public API methods
|
|
494
360
|
this.show = () => openModal(this);
|
|
495
361
|
this.hide = (opts) => closeModal(this, 'programmatic', opts);
|
|
496
362
|
}
|
|
@@ -498,25 +364,15 @@ export class TyModal extends HTMLElement {
|
|
|
498
364
|
cleanup(this);
|
|
499
365
|
}
|
|
500
366
|
attributeChangedCallback(_name, _oldValue, _newValue) {
|
|
501
|
-
// React (and any framework that conditionally mounts an already-open
|
|
502
|
-
// modal) sets initial attributes BEFORE inserting the element into the
|
|
503
|
-
// document, and showModal() on a disconnected <dialog> throws
|
|
504
|
-
// InvalidStateError. Defer — connectedCallback render()s on insertion
|
|
505
|
-
// and applies `open` then.
|
|
506
367
|
if (!this.isConnected)
|
|
507
368
|
return;
|
|
508
|
-
// Re-render on attribute changes
|
|
509
369
|
render(this);
|
|
510
370
|
}
|
|
511
371
|
}
|
|
512
|
-
// Register the custom element
|
|
513
372
|
if (!customElements.get('ty-modal')) {
|
|
514
373
|
customElements.define('ty-modal', TyModal);
|
|
515
374
|
}
|
|
516
|
-
// Also exposed as ty-dialog — the platform/ARIA name (wraps native <dialog>).
|
|
517
|
-
// A constructor can only register once, hence the subclass.
|
|
518
375
|
if (!customElements.get('ty-dialog')) {
|
|
519
376
|
customElements.define('ty-dialog', class TyDialog extends TyModal {
|
|
520
377
|
});
|
|
521
378
|
}
|
|
522
|
-
//# sourceMappingURL=modal.js.map
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TyOption Web Component
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Ty Option Web Component - Rich HTML alternative to native <option>
|
|
6
|
-
* Enhanced with robust property-attribute synchronization for framework compatibility.
|
|
2
|
+
* TyOption Web Component - rich HTML alternative to native <option>.
|
|
3
|
+
* Property-attribute synchronization keeps it compatible with frameworks.
|
|
7
4
|
*/
|
|
8
5
|
import type { TyOptionElement } from '../types/common.js';
|
|
9
6
|
export declare class TyOption extends HTMLElement implements TyOptionElement {
|
|
@@ -31,9 +28,5 @@ export declare class TyOption extends HTMLElement implements TyOptionElement {
|
|
|
31
28
|
set highlighted(value: boolean);
|
|
32
29
|
get hidden(): boolean;
|
|
33
30
|
set hidden(value: boolean);
|
|
34
|
-
/**
|
|
35
|
-
* Render the option component with rich HTML content and property sync
|
|
36
|
-
*/
|
|
37
31
|
private render;
|
|
38
32
|
}
|
|
39
|
-
//# sourceMappingURL=option.d.ts.map
|
package/lib/components/option.js
CHANGED
|
@@ -1,29 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TyOption Web Component
|
|
3
|
-
* PORTED FROM: clj/ty/components/option.cljs
|
|
4
|
-
*
|
|
5
|
-
* Ty Option Web Component - Rich HTML alternative to native <option>
|
|
6
|
-
* Enhanced with robust property-attribute synchronization for framework compatibility.
|
|
7
|
-
*/
|
|
8
1
|
import { ensureStyles } from '../utils/styles.js';
|
|
9
2
|
import { optionStyles } from '../styles/option.js';
|
|
10
|
-
/**
|
|
11
|
-
* Ty Option Component
|
|
12
|
-
*
|
|
13
|
-
* Rich HTML alternative to native <option> element.
|
|
14
|
-
* Supports HTML content, styling, and framework property binding.
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```html
|
|
18
|
-
* <ty-option value="1">Simple Option</ty-option>
|
|
19
|
-
* <ty-option value="2" selected>Selected Option</ty-option>
|
|
20
|
-
* <ty-option value="3" disabled>Disabled Option</ty-option>
|
|
21
|
-
* <ty-option value="4">
|
|
22
|
-
* <strong>Rich</strong> <em>HTML</em> Content
|
|
23
|
-
* </ty-option>
|
|
24
|
-
* ```
|
|
25
|
-
*/
|
|
26
|
-
/** Lucide `check` — shown on selected options (kept inline: shadow DOM can't rely on the icon registry) */
|
|
27
3
|
const CHECK_SVG = `
|
|
28
4
|
<svg class="option-check" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
29
5
|
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
@@ -46,12 +22,9 @@ export class TyOption extends HTMLElement {
|
|
|
46
22
|
return ['value', 'disabled', 'selected', 'highlighted', 'hidden'];
|
|
47
23
|
}
|
|
48
24
|
connectedCallback() {
|
|
49
|
-
// CRITICAL: Reagent/React may set properties BEFORE the element is constructed
|
|
50
|
-
// Check if value was set directly on the instance before our getter/setter was available
|
|
51
25
|
const instanceValue = Object.getOwnPropertyDescriptor(this, 'value');
|
|
52
26
|
if (instanceValue && instanceValue.value !== undefined) {
|
|
53
27
|
this._value = instanceValue.value;
|
|
54
|
-
// Clean up the instance property so our getter/setter works
|
|
55
28
|
delete this.value;
|
|
56
29
|
}
|
|
57
30
|
this.render();
|
|
@@ -78,13 +51,7 @@ export class TyOption extends HTMLElement {
|
|
|
78
51
|
}
|
|
79
52
|
this.render();
|
|
80
53
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Get value from either property or attribute, with textContent fallback.
|
|
83
|
-
* Property takes precedence (for framework compatibility).
|
|
84
|
-
*/
|
|
85
54
|
getOptionValue() {
|
|
86
|
-
// Explicit priority: property > attribute > textContent
|
|
87
|
-
// This correctly handles empty strings
|
|
88
55
|
if (this._value !== undefined) {
|
|
89
56
|
return this._value;
|
|
90
57
|
}
|
|
@@ -94,8 +61,6 @@ export class TyOption extends HTMLElement {
|
|
|
94
61
|
}
|
|
95
62
|
return this.textContent?.trim() || '';
|
|
96
63
|
}
|
|
97
|
-
// Public API - Getters/Setters
|
|
98
|
-
// NOTE: All setters trigger re-render to support property changes from frameworks like React
|
|
99
64
|
get value() {
|
|
100
65
|
return this.getOptionValue();
|
|
101
66
|
}
|
|
@@ -171,42 +136,31 @@ export class TyOption extends HTMLElement {
|
|
|
171
136
|
this.render();
|
|
172
137
|
}
|
|
173
138
|
}
|
|
174
|
-
/**
|
|
175
|
-
* Render the option component with rich HTML content and property sync
|
|
176
|
-
*/
|
|
177
139
|
render() {
|
|
178
140
|
const shadow = this.shadowRoot;
|
|
179
|
-
// Create wrapper once — the check icon alone communicates selection;
|
|
180
|
-
// clicking a selected option again deselects it (see select.ts), so no
|
|
181
|
-
// separate clear affordance is needed on mobile.
|
|
182
141
|
if (!shadow.querySelector('.option-content')) {
|
|
183
142
|
shadow.innerHTML = `<div class="option-content"><slot></slot>${CHECK_SVG}</div>`;
|
|
184
143
|
}
|
|
185
|
-
// Update content wrapper attributes
|
|
186
144
|
const content = shadow.querySelector('.option-content');
|
|
187
145
|
if (content) {
|
|
188
|
-
// Disabled state
|
|
189
146
|
if (this._disabled) {
|
|
190
147
|
content.setAttribute('disabled', '');
|
|
191
148
|
}
|
|
192
149
|
else {
|
|
193
150
|
content.removeAttribute('disabled');
|
|
194
151
|
}
|
|
195
|
-
// Selected state
|
|
196
152
|
if (this._selected) {
|
|
197
153
|
content.setAttribute('selected', '');
|
|
198
154
|
}
|
|
199
155
|
else {
|
|
200
156
|
content.removeAttribute('selected');
|
|
201
157
|
}
|
|
202
|
-
// Highlighted state
|
|
203
158
|
if (this._highlighted) {
|
|
204
159
|
content.setAttribute('highlighted', '');
|
|
205
160
|
}
|
|
206
161
|
else {
|
|
207
162
|
content.removeAttribute('highlighted');
|
|
208
163
|
}
|
|
209
|
-
// Hidden state
|
|
210
164
|
if (this._hidden) {
|
|
211
165
|
content.setAttribute('hidden', '');
|
|
212
166
|
}
|
|
@@ -216,8 +170,6 @@ export class TyOption extends HTMLElement {
|
|
|
216
170
|
}
|
|
217
171
|
}
|
|
218
172
|
}
|
|
219
|
-
// Register the custom element
|
|
220
173
|
if (!customElements.get('ty-option')) {
|
|
221
174
|
customElements.define('ty-option', TyOption);
|
|
222
175
|
}
|
|
223
|
-
//# sourceMappingURL=option.js.map
|