@stimulus-plumbers/controllers 0.2.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.
@@ -0,0 +1,136 @@
1
+ import Plumber from './plumber';
2
+ import { visibilityConfig } from './plumber/support';
3
+
4
+ const defaultOptions = {
5
+ visibility: 'visibility',
6
+ onShown: 'shown',
7
+ onHidden: 'hidden',
8
+ };
9
+
10
+ export class Visibility extends Plumber {
11
+ /**
12
+ * Creates a new Visibility plumber instance.
13
+ * @param {Object} controller - Stimulus controller instance
14
+ * @param {Object} [options] - Configuration options
15
+ * @param {string} [options.visibility='visibility'] - Namespace for visibility helpers
16
+ * @param {string} [options.onShown='shown'] - Method name on plumber instance called after showing
17
+ * @param {string} [options.onHidden='hidden'] - Method name on plumber instance called after hiding
18
+ */
19
+ constructor(controller, options = {}) {
20
+ const { visibility, onShown, onHidden, activator } = Object.assign({}, defaultOptions, options);
21
+
22
+ const namespace = typeof visibility === 'string' ? visibility : defaultOptions.namespace;
23
+ const resolver = typeof options.visible === 'string' ? options.visible : 'isVisible';
24
+ if (typeof options.visible !== 'boolean' || options.visible) {
25
+ options.visible = `${namespace}.${resolver}`;
26
+ }
27
+ super(controller, options);
28
+
29
+ this.visibility = namespace;
30
+ this.visibilityResolver = resolver;
31
+ this.onShown = onShown;
32
+ this.onHidden = onHidden;
33
+ this.activator = activator instanceof HTMLElement ? activator : null;
34
+
35
+ this.enhance();
36
+
37
+ if (this.element instanceof HTMLElement) this.activate(this.isVisible(this.element));
38
+ }
39
+
40
+ /**
41
+ * Checks if a target element is visible.
42
+ * @param {HTMLElement} target - Element to check
43
+ * @returns {boolean} True if element is visible
44
+ */
45
+ isVisible(target) {
46
+ if (!(target instanceof HTMLElement)) return false;
47
+
48
+ const hiddenClass = visibilityConfig.hiddenClass;
49
+ return hiddenClass ? !target.classList.contains(hiddenClass) : !target.hasAttribute('hidden');
50
+ }
51
+
52
+ /**
53
+ * Toggles element visibility using class or hidden attribute.
54
+ * @param {HTMLElement} target - Element to toggle
55
+ * @param {boolean} visible - True to show, false to hide
56
+ */
57
+ toggle(target, visible) {
58
+ if (!(target instanceof HTMLElement)) return;
59
+
60
+ const hiddenClass = visibilityConfig.hiddenClass;
61
+ if (hiddenClass) {
62
+ if (visible) target.classList.remove(hiddenClass);
63
+ else target.classList.add(hiddenClass);
64
+ } else {
65
+ if (visible) target.removeAttribute('hidden');
66
+ else target.setAttribute('hidden', true);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Sets aria-expanded on the activator element.
72
+ * @param {boolean} isExpanded - True to mark as expanded, false to mark as collapsed
73
+ */
74
+ activate(isExpanded) {
75
+ if (this.activator) this.activator.setAttribute('aria-expanded', isExpanded ? 'true' : 'false');
76
+ }
77
+
78
+ /**
79
+ * Shows the element and dispatches show events.
80
+ * @returns {Promise<void>}
81
+ */
82
+ async show() {
83
+ if (!(this.element instanceof HTMLElement) || this.isVisible(this.element)) return;
84
+
85
+ this.dispatch('show');
86
+ this.toggle(this.element, true);
87
+ this.activate(true);
88
+
89
+ await this.awaitCallback(this.onShown, { target: this.element });
90
+ this.dispatch('shown');
91
+ }
92
+
93
+ /**
94
+ * Hides the element and dispatches hide events.
95
+ * @returns {Promise<void>}
96
+ */
97
+ async hide() {
98
+ if (!(this.element instanceof HTMLElement) || !this.isVisible(this.element)) return;
99
+
100
+ this.dispatch('hide');
101
+ this.toggle(this.element, false);
102
+ this.activate(false);
103
+
104
+ await this.awaitCallback(this.onHidden, { target: this.element });
105
+ this.dispatch('hidden');
106
+ }
107
+
108
+ enhance() {
109
+ const context = this;
110
+ const helpers = {
111
+ show: context.show.bind(context),
112
+ hide: context.hide.bind(context),
113
+ };
114
+ Object.defineProperty(helpers, 'visible', {
115
+ get() {
116
+ return context.isVisible(context.element);
117
+ },
118
+ });
119
+ Object.defineProperty(helpers, this.visibilityResolver, {
120
+ value: context.isVisible.bind(context),
121
+ });
122
+ Object.defineProperty(this.controller, this.visibility, {
123
+ get() {
124
+ return helpers;
125
+ },
126
+ });
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Factory function to create and attach a Visibility plumber to a controller.
132
+ * @param {Object} controller - Stimulus controller instance
133
+ * @param {Object} [options] - Configuration options
134
+ * @returns {Visibility} Visibility plumber instance
135
+ */
136
+ export const attachVisibility = (controller, options) => new Visibility(controller, options);