@ulu/frontend 0.1.0-beta.82 → 0.1.0-beta.84

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 (193) hide show
  1. package/CHANGELOG.md +32 -3
  2. package/dist/ulu-frontend.min.css +1 -1
  3. package/dist/ulu-frontend.min.js +17 -17
  4. package/docs-dev/assets/main.js +494 -7738
  5. package/docs-dev/assets/style.css +166 -221
  6. package/docs-dev/changelog/index.html +294 -3
  7. package/docs-dev/changelog/updates-and-changes/index.html +5109 -0
  8. package/docs-dev/demos/accordion/index.html +240 -0
  9. package/docs-dev/demos/badge/index.html +240 -0
  10. package/docs-dev/demos/breakpoints-manager/index.html +240 -0
  11. package/docs-dev/demos/button/index.html +240 -0
  12. package/docs-dev/demos/button-group/index.html +5502 -0
  13. package/docs-dev/demos/button-verbose/index.html +240 -0
  14. package/docs-dev/demos/callout/index.html +240 -0
  15. package/docs-dev/demos/captioned-figure/index.html +240 -0
  16. package/docs-dev/demos/card/index.html +256 -16
  17. package/docs-dev/demos/card-grid/index.html +240 -0
  18. package/docs-dev/demos/card-new/index.html +5088 -0
  19. package/docs-dev/demos/card-old/index.html +5223 -0
  20. package/docs-dev/demos/card.1/index.html +5223 -0
  21. package/docs-dev/demos/card.TRASH/index.html +5541 -0
  22. package/docs-dev/demos/counter-list/index.html +240 -0
  23. package/docs-dev/demos/css-icons/index.html +240 -0
  24. package/docs-dev/demos/data-grid/index.html +240 -0
  25. package/docs-dev/demos/data-table/index.html +340 -100
  26. package/docs-dev/demos/details-group/index.html +240 -0
  27. package/docs-dev/demos/file-save/index.html +240 -0
  28. package/docs-dev/demos/flipcard/index.html +240 -0
  29. package/docs-dev/demos/form-theme/index.html +240 -0
  30. package/docs-dev/demos/headline-label/index.html +5472 -0
  31. package/docs-dev/demos/index.html +240 -0
  32. package/docs-dev/demos/list-inline/index.html +240 -0
  33. package/docs-dev/demos/list-inline.1/index.html +4727 -0
  34. package/docs-dev/demos/list-lines/index.html +240 -0
  35. package/docs-dev/demos/menu-stack/index.html +240 -0
  36. package/docs-dev/demos/modals/index.html +240 -0
  37. package/docs-dev/demos/nav-strip/index.html +240 -0
  38. package/docs-dev/demos/overlay-section/index.html +240 -0
  39. package/docs-dev/demos/panel/index.html +5532 -0
  40. package/docs-dev/demos/popovers/index.html +240 -0
  41. package/docs-dev/demos/print/index.html +240 -0
  42. package/docs-dev/demos/pull-quote/index.html +240 -0
  43. package/docs-dev/demos/rail/index.html +5568 -0
  44. package/docs-dev/demos/rule/index.html +240 -0
  45. package/docs-dev/demos/scrollpoints/index.html +240 -0
  46. package/docs-dev/demos/spoke-spinner/index.html +240 -0
  47. package/docs-dev/demos/sticky-list/index.html +240 -0
  48. package/docs-dev/demos/tabs/index.html +240 -0
  49. package/docs-dev/demos/tag/index.html +240 -0
  50. package/docs-dev/demos/theme-toggle/index.html +240 -0
  51. package/docs-dev/demos/tiles/index.html +240 -0
  52. package/docs-dev/demos/tooltip/index.html +240 -0
  53. package/docs-dev/guide/building-stylesheet/index.html +240 -0
  54. package/docs-dev/guide/developing-ulu-scss-module/index.html +240 -0
  55. package/docs-dev/guide/index.html +240 -0
  56. package/docs-dev/guide/updates-and-changes/index.html +5033 -0
  57. package/docs-dev/index.html +240 -0
  58. package/docs-dev/javascript/events/index.html +240 -0
  59. package/docs-dev/javascript/index.html +240 -0
  60. package/docs-dev/javascript/settings/index.html +240 -0
  61. package/docs-dev/javascript/ui-breakpoints/index.html +240 -0
  62. package/docs-dev/javascript/ui-collapsible/index.html +240 -0
  63. package/docs-dev/javascript/ui-details-group/index.html +240 -0
  64. package/docs-dev/javascript/ui-dialog/index.html +240 -0
  65. package/docs-dev/javascript/ui-flipcard/index.html +240 -0
  66. package/docs-dev/javascript/ui-grid/index.html +240 -0
  67. package/docs-dev/javascript/ui-modal-builder/index.html +240 -0
  68. package/docs-dev/javascript/ui-overflow-scroller/index.html +240 -0
  69. package/docs-dev/javascript/ui-overflow-scroller-pager/index.html +240 -0
  70. package/docs-dev/javascript/ui-page/index.html +240 -0
  71. package/docs-dev/javascript/ui-popover/index.html +240 -0
  72. package/docs-dev/javascript/ui-print/index.html +240 -0
  73. package/docs-dev/javascript/ui-print-details/index.html +240 -0
  74. package/docs-dev/javascript/ui-programmatic-modal/index.html +240 -0
  75. package/docs-dev/javascript/ui-proxy-click/index.html +240 -0
  76. package/docs-dev/javascript/ui-resizer/index.html +452 -16
  77. package/docs-dev/javascript/ui-scroll-slider/index.html +240 -0
  78. package/docs-dev/javascript/ui-scrollpoint/index.html +240 -0
  79. package/docs-dev/javascript/ui-slider/index.html +240 -0
  80. package/docs-dev/javascript/ui-tabs/index.html +240 -0
  81. package/docs-dev/javascript/ui-theme-toggle/index.html +240 -0
  82. package/docs-dev/javascript/ui-tooltip/index.html +240 -0
  83. package/docs-dev/javascript/utils-class-logger/index.html +240 -0
  84. package/docs-dev/javascript/utils-css/index.html +240 -0
  85. package/docs-dev/javascript/utils-dom/index.html +240 -0
  86. package/docs-dev/javascript/utils-file-save/index.html +240 -0
  87. package/docs-dev/javascript/utils-floating-ui/index.html +240 -0
  88. package/docs-dev/javascript/utils-id/index.html +240 -0
  89. package/docs-dev/javascript/utils-pause-youtube-video/index.html +240 -0
  90. package/docs-dev/javascript/utils-system/index.html +240 -0
  91. package/docs-dev/sass/base/color/index.html +240 -0
  92. package/docs-dev/sass/base/elements/index.html +240 -0
  93. package/docs-dev/sass/base/index/index.html +240 -0
  94. package/docs-dev/sass/base/index.html +240 -0
  95. package/docs-dev/sass/base/keyframes/index.html +240 -0
  96. package/docs-dev/sass/base/layout/index.html +240 -0
  97. package/docs-dev/sass/base/normalize/index.html +240 -0
  98. package/docs-dev/sass/base/print/index.html +240 -0
  99. package/docs-dev/sass/base/root/index.html +240 -0
  100. package/docs-dev/sass/base/typography/index.html +240 -0
  101. package/docs-dev/sass/components/accordion/index.html +240 -0
  102. package/docs-dev/sass/components/adaptive-spacing/index.html +240 -0
  103. package/docs-dev/sass/components/badge/index.html +240 -0
  104. package/docs-dev/sass/components/basic-hero/index.html +240 -0
  105. package/docs-dev/sass/components/button/index.html +240 -0
  106. package/docs-dev/sass/components/button-group/index.html +5653 -0
  107. package/docs-dev/sass/components/button-verbose/index.html +240 -0
  108. package/docs-dev/sass/components/callout/index.html +240 -0
  109. package/docs-dev/sass/components/captioned-figure/index.html +240 -0
  110. package/docs-dev/sass/components/card/index.html +240 -0
  111. package/docs-dev/sass/components/card-grid/index.html +240 -0
  112. package/docs-dev/sass/components/counter-list/index.html +240 -0
  113. package/docs-dev/sass/components/css-icon/index.html +240 -0
  114. package/docs-dev/sass/components/data-grid/index.html +254 -14
  115. package/docs-dev/sass/components/data-table/index.html +240 -0
  116. package/docs-dev/sass/components/fill-context/index.html +240 -0
  117. package/docs-dev/sass/components/flipcard/index.html +240 -0
  118. package/docs-dev/sass/components/flipcard-grid/index.html +240 -0
  119. package/docs-dev/sass/components/form-theme/index.html +240 -0
  120. package/docs-dev/sass/components/headline-label/index.html +5683 -0
  121. package/docs-dev/sass/components/hero/index.html +240 -0
  122. package/docs-dev/sass/components/horizontal-rule/index.html +240 -0
  123. package/docs-dev/sass/components/image-grid/index.html +240 -0
  124. package/docs-dev/sass/components/index/index.html +254 -10
  125. package/docs-dev/sass/components/index.html +240 -0
  126. package/docs-dev/sass/components/links/index.html +240 -0
  127. package/docs-dev/sass/components/list-inline/index.html +240 -0
  128. package/docs-dev/sass/components/list-lines/index.html +240 -0
  129. package/docs-dev/sass/components/list-ordered/index.html +240 -0
  130. package/docs-dev/sass/components/list-unordered/index.html +240 -0
  131. package/docs-dev/sass/components/menu-stack/index.html +240 -0
  132. package/docs-dev/sass/components/modal/index.html +240 -0
  133. package/docs-dev/sass/components/nav-strip/index.html +240 -0
  134. package/docs-dev/sass/components/overlay-section/index.html +240 -0
  135. package/docs-dev/sass/components/pager/index.html +240 -0
  136. package/docs-dev/sass/components/panel/index.html +5883 -0
  137. package/docs-dev/sass/components/placeholder-block/index.html +240 -0
  138. package/docs-dev/sass/components/popover/index.html +240 -0
  139. package/docs-dev/sass/components/pull-quote/index.html +240 -0
  140. package/docs-dev/sass/components/rail/index.html +5670 -0
  141. package/docs-dev/sass/components/ratio-box/index.html +240 -0
  142. package/docs-dev/sass/components/rule/index.html +240 -0
  143. package/docs-dev/sass/components/scroll-slider/index.html +240 -0
  144. package/docs-dev/sass/components/skip-link/index.html +240 -0
  145. package/docs-dev/sass/components/slider/index.html +240 -0
  146. package/docs-dev/sass/components/spoke-spinner/index.html +240 -0
  147. package/docs-dev/sass/components/sticky-list/index.html +240 -0
  148. package/docs-dev/sass/components/tabs/index.html +240 -0
  149. package/docs-dev/sass/components/tag/index.html +240 -0
  150. package/docs-dev/sass/components/tile-button/index.html +240 -0
  151. package/docs-dev/sass/components/tile-grid/index.html +240 -0
  152. package/docs-dev/sass/components/tile-grid-overlay/index.html +240 -0
  153. package/docs-dev/sass/components/vignette/index.html +240 -0
  154. package/docs-dev/sass/components/wysiwyg/index.html +240 -0
  155. package/docs-dev/sass/core/breakpoint/index.html +241 -1
  156. package/docs-dev/sass/core/button/index.html +272 -30
  157. package/docs-dev/sass/core/color/index.html +240 -0
  158. package/docs-dev/sass/core/cssvar/index.html +240 -0
  159. package/docs-dev/sass/core/element/index.html +346 -30
  160. package/docs-dev/sass/core/index.html +240 -0
  161. package/docs-dev/sass/core/layout/index.html +240 -0
  162. package/docs-dev/sass/core/path/index.html +240 -0
  163. package/docs-dev/sass/core/selector/index.html +240 -0
  164. package/docs-dev/sass/core/typography/index.html +240 -0
  165. package/docs-dev/sass/core/units/index.html +240 -0
  166. package/docs-dev/sass/core/utils/index.html +358 -76
  167. package/docs-dev/sass/helpers/color/index.html +240 -0
  168. package/docs-dev/sass/helpers/display/index.html +240 -0
  169. package/docs-dev/sass/helpers/index/index.html +240 -0
  170. package/docs-dev/sass/helpers/index.html +240 -0
  171. package/docs-dev/sass/helpers/typography/index.html +240 -0
  172. package/docs-dev/sass/helpers/units/index.html +240 -0
  173. package/docs-dev/sass/helpers/utilities/index.html +240 -0
  174. package/docs-dev/sass/index.html +240 -0
  175. package/js/ui/modal-builder.js +2 -2
  176. package/js/ui/resizer.js +331 -112
  177. package/js/utils/font-awesome.js +1 -1
  178. package/package.json +1 -1
  179. package/scss/_breakpoint.scss +1 -1
  180. package/scss/_button.scss +7 -5
  181. package/scss/_element.scss +16 -0
  182. package/scss/_utils.scss +7 -0
  183. package/scss/components/_button-group.scss +90 -0
  184. package/scss/components/_data-grid.scss +0 -3
  185. package/scss/components/_headline-label.scss +83 -0
  186. package/scss/components/_index.scss +24 -0
  187. package/scss/components/_panel.scss +246 -0
  188. package/scss/components/_rail.scss +120 -0
  189. package/types/ui/dialog.d.ts.map +1 -1
  190. package/types/ui/index.d.ts +1 -1
  191. package/types/ui/resizer.d.ts +85 -12
  192. package/types/ui/resizer.d.ts.map +1 -1
  193. package/types/utils/index.d.ts +1 -1
package/js/ui/resizer.js CHANGED
@@ -2,9 +2,15 @@
2
2
  * @module ui/resizer
3
3
  */
4
4
 
5
+ // Note: Gzip Test = 1.6kb (7-28-25), which seems appropriate
6
+ // based on how many options/events it handles
7
+
5
8
  import { createEvent } from "../events/index.js";
6
- import { logError, log } from "../utils/class-logger.js"; // Assuming this utility exists
9
+ import { logError, log } from "../utils/class-logger.js";
7
10
 
11
+ /**
12
+ * Class for creating/controlling a container size with a handle/control
13
+ */
8
14
  export class Resizer {
9
15
  static defaults = {
10
16
  debug: false,
@@ -22,192 +28,405 @@ export class Resizer {
22
28
  * `null` means no horizontal resizing.
23
29
  * - Default null
24
30
  */
25
- fromX: null,
31
+ fromX: null,
26
32
  /**
27
33
  * @type {"top"|"bottom"|null}
28
34
  * Specifies the vertical edge from which resizing occurs.
29
35
  * - `null` means no vertical resizing.
30
36
  * - Default null
31
37
  */
32
- fromY: null
38
+ fromY: null,
39
+ /**
40
+ * The step in pixels for keyboard resizing with arrow keys.
41
+ */
42
+ keyboardStep: 10,
43
+ /**
44
+ * Debounce time in milliseconds for ending a keyboard resize.
45
+ */
46
+ keyboardDebounceTime: 200,
47
+ /**
48
+ * If true, the Resizer instance will automatically bind its own DOM event listeners
49
+ * (pointerdown, keydown) to the control element. If `false`, the user is
50
+ * responsible for calling `resizerInstance.onPointerdown(event)` and
51
+ * `resizerInstance.onKeydown(event)` from their own listeners.
52
+ * Default: true
53
+ */
54
+ manageEvents: true,
55
+ /**
56
+ * If true, the Resizer instance will automatically manage the `aria-label`
57
+ * attribute of the control element. If `false`, the user is responsible
58
+ * for setting this attribute.
59
+ * Default: false
60
+ */
61
+ manageAriaLabel: false,
62
+ /**
63
+ * If true, pointer events (mouse/touch) will enable resizing.
64
+ * Default: true
65
+ */
66
+ enablePointerResizing: true,
67
+ /**
68
+ * If true, keyboard events (arrow keys) will enable resizing.
69
+ * Default: true
70
+ */
71
+ enableKeyboardResizing: true
33
72
  };
34
73
 
35
- // Declare any runtime populated private fields
36
- #handlerPointerdown;
74
+ // Declare private fields without initial assignments
75
+ #handlerPointerdownInternal;
76
+ #handlerKeydownInternal;
77
+ #keyboardResizeTimeout;
78
+ #initialContainerDimensions;
79
+ #accumulatedKeyboardDeltaX;
80
+ #accumulatedKeyboardDeltaY;
81
+ #isResizingActive;
82
+ #pointerStartX;
83
+ #pointerStartY;
37
84
 
38
85
  /**
39
86
  * @param {Node} container Container to be resized
40
- * @param {Node} control Resize handle element
41
- * @param {Object} options Options to configure the resizer.
42
- * @param {Boolean} options.debug Enable non-essential debugging logs.
43
- * @param {Boolean} options.overrideMaxDimensions When script is activated by handle, remove the element's max-width/max-height and allow the resize to exceed them (default false).
44
- * @param {"left"|"right"|null} [options.fromX=null] Horizontal resizing direction.
45
- * @param {"top"|"bottom"|null} [options.fromY=null] Vertical resizing direction.
87
+ * @param {HTMLElement} control Resize handle element (should be focusable like a button)
88
+ * @param {Object} config Options to configure the resizer.
89
+ * @param {Boolean} [config.debug=false] Enable non-essential debugging logs.
90
+ * @param {Boolean} [config.multiplier=1] Amount to increase size by (ie. pointer movement * multiplier).
91
+ * @param {Boolean} [config.overrideMaxDimensions=false] When script is activated by handle, remove the element's max-width/max-height and allow the resize to exceed them.
92
+ * @param {"left"|"right"|null} [config.fromX=null] Horizontal resizing direction.
93
+ * @param {"top"|"bottom"|null} [config.fromY=null] Vertical resizing direction.
94
+ * @param {number} [config.keyboardStep=10] The step in pixels for keyboard resizing.
95
+ * @param {number} [config.keyboardDebounceTime=200] Debounce time for keyboard resize end.
96
+ * @param {boolean} [config.manageEvents=true] If true, the Resizer will automatically bind its own events.
97
+ * @param {boolean} [config.manageAriaLabel=false] If true, the Resizer will manage the control's aria-label.
98
+ * @param {boolean} [config.enablePointerResizing=true] If true, pointer events will enable resizing.
99
+ * @param {boolean} [config.enableKeyboardResizing=true] If true, keyboard events will enable resizing.
46
100
  */
47
- constructor(container, control, options) {
101
+ constructor(container, control, config) {
48
102
  if (!control || !container) {
49
103
  logError(this, "Missing required elements: control, container");
50
104
  return;
51
105
  }
52
- this.options = Object.assign({}, Resizer.defaults, options);
53
- log(this, "Resolved options", this.options);
54
-
106
+ const options = Object.assign({}, Resizer.defaults, config);
107
+ this.options = options;
55
108
  this.container = container;
56
109
  this.control = control;
57
- this.debug = this.options.debug; // for logger
110
+ this.debug = options.debug;
58
111
 
59
- // Validate and normalize fromX/fromY
60
112
  const validX = ["left", "right"];
61
113
  const validY = ["top", "bottom"];
62
-
63
- const { fromX, fromY } = this.options;
114
+ const { fromX, fromY } = options;
64
115
 
65
116
  if (!validX.includes(fromX) && fromX !== null) {
66
117
  logError(this, `Invalid fromX: ${ fromX } (left|right|null)`);
67
- return;
118
+ return;
68
119
  }
69
120
  if (!validY.includes(fromY) && fromY !== null) {
70
121
  logError(this, `Invalid fromY: ${ fromY } (top|bottom|null)`);
71
122
  return;
72
123
  }
73
-
74
124
  if (!fromX && !fromY) {
75
- logError(this, "Invalid fromX/fromY, failed to setup resizer");
125
+ logError(this, "Invalid fromX/fromY, failed to setup resizer (at least one of fromX or fromY must be set)");
76
126
  return;
77
127
  }
78
128
 
79
- // Determine effective resizing directions based on fromX/fromY being non-null
80
- this.resizeHorizontal = this.options.fromX !== null;
81
- this.resizeVertical = this.options.fromY !== null;
129
+ this.resizeHorizontal = options.fromX !== null;
130
+ this.resizeVertical = options.fromY !== null;
131
+
132
+ // Bind internal listeners only if manageEvents is true AND the respective event type is enabled
133
+ if (options.manageEvents) {
134
+ this.#handlerPointerdownInternal = this.onPointerdown.bind(this);
135
+ this.#handlerKeydownInternal = this.onKeydown.bind(this);
136
+
137
+ if (options.enablePointerResizing) {
138
+ control.addEventListener("pointerdown", this.#handlerPointerdownInternal);
139
+ }
140
+ if (options.enableKeyboardResizing) {
141
+ control.addEventListener("keydown", this.#handlerKeydownInternal);
142
+ }
143
+ }
82
144
 
83
- // Bind event handlers
84
- this.#handlerPointerdown = this.#onPointerdown.bind(this);
145
+ this.#resetInternalState();
85
146
 
86
- // Attach event listener
87
- this.control.addEventListener("pointerdown", this.#handlerPointerdown);
147
+ if (options.manageAriaLabel) {
148
+ control.setAttribute("aria-label", this.getAriaLabel());
149
+ }
88
150
  }
89
151
 
90
152
  /**
91
- * Cleans up event listeners to prevent memory leaks.
153
+ * Resets all internal state properties to their default/inactive values.
154
+ * This centralizes state cleanup and initial setup.
155
+ * @private
156
+ */
157
+ #resetInternalState() {
158
+ this.#keyboardResizeTimeout = null;
159
+ this.#initialContainerDimensions = { width: 0, height: 0 };
160
+ this.#accumulatedKeyboardDeltaX = 0;
161
+ this.#accumulatedKeyboardDeltaY = 0;
162
+ this.#isResizingActive = false;
163
+ this.#pointerStartX = 0;
164
+ this.#pointerStartY = 0;
165
+ }
166
+
167
+ /**
168
+ * Cleans up event listeners and internal state to prevent memory leaks.
92
169
  */
93
170
  destroy() {
94
- this.control.removeEventListener("pointerdown", this.#handlerPointerdown);
171
+ const { control, options } = this;
172
+
173
+ if (options.manageEvents) {
174
+ if (options.enablePointerResizing) {
175
+ control.removeEventListener("pointerdown", this.#handlerPointerdownInternal);
176
+ }
177
+ if (options.enableKeyboardResizing) {
178
+ control.removeEventListener("keydown", this.#handlerKeydownInternal);
179
+ }
180
+ }
181
+
182
+ if (this.#keyboardResizeTimeout) {
183
+ clearTimeout(this.#keyboardResizeTimeout);
184
+ }
185
+
186
+ this.#resetInternalState();
187
+
188
+ if (options.manageAriaLabel) {
189
+ control.removeAttribute("aria-label");
190
+ }
191
+ log(this, "Resizer destroyed.");
95
192
  }
96
193
 
97
194
  /**
98
- * Handles the pointerdown event on the resize control.
99
- * @param {PointerEvent} e The pointerdown event.
195
+ * Initiates a resize operation.
196
+ * This sets initial dimensions and dispatches the 'resizer:start' event.
197
+ * @param {Object} eventDetails Additional details about the initiating event.
100
198
  * @private
101
199
  */
102
- #onPointerdown(e) {
103
- e.preventDefault(); // Prevent default browser drag behavior
200
+ #startResize(eventDetails) {
201
+ const { container, options } = this;
104
202
 
105
- const { overrideMaxDimensions, fromX, fromY, multiplier } = this.options; // Destructure fromX, fromY
106
- const doc = document.documentElement;
107
- const win = document.defaultView;
108
- const containerStyle = win.getComputedStyle(this.container);
109
-
110
- // Initial pointer coordinates
111
- const startX = e.clientX;
112
- const startY = e.clientY;
203
+ if (this.#isResizingActive) {
204
+ if (options.overrideMaxDimensions) {
205
+ if (this.resizeHorizontal) {
206
+ container.style.maxWidth = "none";
207
+ }
208
+ if (this.resizeVertical) {
209
+ container.style.maxHeight = "none";
210
+ }
211
+ }
212
+ return;
213
+ }
113
214
 
114
- // Initial dimensions of the container
115
- const initialWidth = parseInt(containerStyle.width, 10);
116
- const initialHeight = parseInt(containerStyle.height, 10);
215
+ const win = document.defaultView;
216
+ const containerStyle = win.getComputedStyle(container);
117
217
 
118
- // Set pointer capture on the control element
119
- this.control.setPointerCapture(e.pointerId);
218
+ this.#initialContainerDimensions.width = parseInt(containerStyle.width, 10);
219
+ this.#initialContainerDimensions.height = parseInt(containerStyle.height, 10);
120
220
 
121
- // Optionally remove max-width/max-height to allow unrestricted resizing
122
- if (overrideMaxDimensions) {
221
+ if (options.overrideMaxDimensions) {
123
222
  if (this.resizeHorizontal) {
124
- this.container.style.maxWidth = "none";
223
+ container.style.maxWidth = "none";
125
224
  }
126
225
  if (this.resizeVertical) {
127
- this.container.style.maxHeight = "none";
226
+ container.style.maxHeight = "none";
128
227
  }
129
228
  }
130
229
 
131
- const initialInfo = {
132
- event: e,
133
- startX,
134
- startY,
135
- initialWidth,
136
- initialHeight,
137
- fromX, // Log fromX and fromY separately
138
- fromY,
139
- pointerId: e.pointerId
140
- };
230
+ this.#isResizingActive = true;
231
+ this.dispatchEvent("resizer:start", eventDetails);
232
+ log(this, "Resize started.", {
233
+ initialWidth: this.#initialContainerDimensions.width,
234
+ initialHeight: this.#initialContainerDimensions.height,
235
+ ...eventDetails
236
+ });
237
+ }
141
238
 
142
- this.dispatchEvent("resizer:start", initialInfo);
143
- log(this, "Pointerdown initiated/captured.", initialInfo);
239
+ /**
240
+ * Ends a resize operation.
241
+ * Dispatches 'resizer:end' event and resets internal state.
242
+ * @private
243
+ */
244
+ #endResize() {
245
+ if (!this.#isResizingActive) return;
144
246
 
145
- /**
146
- * Handles the pointermove event to resize the container.
147
- * @param {PointerEvent} event The pointermove event.
148
- */
149
- const pointermove = (event) => {
150
- let newWidth = initialWidth;
151
- let newHeight = initialHeight;
247
+ this.dispatchEvent("resizer:end");
248
+ this.#resetInternalState();
249
+ log(this, "Resize ended.");
250
+ }
152
251
 
153
- const deltaX = event.clientX - startX;
154
- const deltaY = event.clientY - startY;
252
+ /**
253
+ * Core logic for calculating and applying the new size of the container.
254
+ * This method is called by both pointer and keyboard event handlers.
255
+ *
256
+ * @param {number} totalDeltaX The total horizontal displacement from the start of the resize.
257
+ * @param {number} totalDeltaY The total vertical displacement from the start of the resize.
258
+ * @param {Event} originalEvent The original DOM event (PointerEvent or KeyboardEvent) that triggered the update.
259
+ * @private
260
+ */
261
+ #updateSize(totalDeltaX, totalDeltaY, originalEvent) {
262
+ let newWidth = this.#initialContainerDimensions.width;
263
+ let newHeight = this.#initialContainerDimensions.height;
264
+ const { fromX, fromY, multiplier } = this.options;
155
265
 
156
- // Handle horizontal resizing
157
- if (this.resizeHorizontal) {
158
- if (fromX === "right") {
159
- newWidth = (initialWidth + (deltaX * multiplier));
160
- } else if (fromX === "left") {
161
- newWidth = (initialWidth - (deltaX * multiplier));
162
- }
163
- this.container.style.width = `${ Math.max(0, newWidth) }px`; // Ensure non-negative width
266
+ if (this.resizeHorizontal) {
267
+ if (fromX === "right") {
268
+ newWidth = (this.#initialContainerDimensions.width + (totalDeltaX * multiplier));
269
+ } else if (fromX === "left") {
270
+ newWidth = (this.#initialContainerDimensions.width - (totalDeltaX * multiplier));
164
271
  }
272
+ this.container.style.width = `${ Math.max(0, newWidth) }px`;
273
+ }
165
274
 
166
- // Handle vertical resizing
167
- if (this.resizeVertical) {
168
- if (fromY === "bottom") { // Use fromY directly
169
- newHeight = (initialHeight + (deltaY * multiplier));
170
- } else if (fromY === "top") { // Use fromY directly
171
- newHeight = (initialHeight - (deltaY * multiplier));
172
- }
173
- this.container.style.height = `${ Math.max(0, newHeight) }px`; // Ensure non-negative height
275
+ if (this.resizeVertical) {
276
+ if (fromY === "bottom") {
277
+ newHeight = (this.#initialContainerDimensions.height + (totalDeltaY * multiplier));
278
+ } else if (fromY === "top") {
279
+ newHeight = (this.#initialContainerDimensions.height - (totalDeltaY * multiplier));
174
280
  }
281
+ this.container.style.height = `${ Math.max(0, newHeight) }px`;
282
+ }
283
+
284
+ const updateInfo = {
285
+ newWidth: newWidth,
286
+ newHeight: newHeight,
287
+ totalDeltaX: totalDeltaX,
288
+ totalDeltaY: totalDeltaY,
289
+ event: originalEvent
290
+ };
291
+
292
+ this.dispatchEvent("resizer:update", updateInfo);
293
+ log(this, "Resizing update.", updateInfo);
294
+ }
295
+
296
+ /**
297
+ * Public handler for pointerdown events. Call this method from your own event listeners
298
+ * if `manageEvents` is false. Its logic will only execute if `enablePointerResizing` is true.
299
+ * @param {PointerEvent} e The pointerdown event.
300
+ */
301
+ onPointerdown(e) {
302
+ if (!this.options.enablePointerResizing) {
303
+ log(this, "Pointer resizing disabled. Ignoring pointerdown event.");
304
+ return;
305
+ }
306
+
307
+ e.preventDefault();
308
+ const doc = document.documentElement;
175
309
 
176
- const updateInfo = {
177
- clientX: event.clientX,
178
- clientY: event.clientY,
179
- newWidth,
180
- newHeight,
181
- event
182
- };
310
+ this.#pointerStartX = e.clientX;
311
+ this.#pointerStartY = e.clientY;
312
+
313
+ this.#startResize({
314
+ inputType: 'pointer',
315
+ startX: e.clientX,
316
+ startY: e.clientY,
317
+ pointerId: e.pointerId
318
+ });
319
+
320
+ this.control.setPointerCapture(e.pointerId);
183
321
 
184
- this.dispatchEvent("resizer:update", updateInfo);
185
- log(this, "Pointermove.", updateInfo);
322
+ const pointermove = (event) => {
323
+ const totalDeltaX = event.clientX - this.#pointerStartX;
324
+ const totalDeltaY = event.clientY - this.#pointerStartY;
325
+ this.#updateSize(totalDeltaX, totalDeltaY, event);
186
326
  };
187
327
 
188
- /**
189
- * Cleans up event listeners after the pointerup event.
190
- * @param {PointerEvent} event The pointerup event.
191
- */
192
328
  const cleanup = (event) => {
193
329
  doc.removeEventListener("pointermove", pointermove, false);
194
330
  doc.removeEventListener("pointerup", cleanup, { capture: true, once: true });
195
331
 
196
- // Release pointer capture from the control element
197
- this.control.releasePointerCapture(event.pointerId);
198
- this.dispatchEvent("resizer:end");
199
-
200
- log(this, "Pointerup cleanup complete. Pointer released.", {
201
- pointerId: event.pointerId
202
- });
203
-
332
+ if (this.control.hasPointerCapture(event.pointerId)) {
333
+ this.control.releasePointerCapture(event.pointerId);
334
+ }
335
+ this.#endResize();
204
336
  };
205
337
 
206
- // Attach global event listeners for dragging
207
338
  doc.addEventListener("pointermove", pointermove, false);
208
339
  doc.addEventListener("pointerup", cleanup, { capture: true, once: true });
209
340
  }
210
- dispatchEvent(type, data) {
341
+
342
+ /**
343
+ * Public handler for keydown events. Call this method from your own event listeners
344
+ * if `manageEvents` is false. Its logic will only execute if `enableKeyboardResizing` is true.
345
+ * @param {KeyboardEvent} e The keydown event.
346
+ */
347
+ onKeydown(e) {
348
+ if (!this.options.enableKeyboardResizing) {
349
+ log(this, "Keyboard resizing disabled. Ignoring keydown event.");
350
+ return;
351
+ }
352
+
353
+ const { key } = e;
354
+ const { keyboardStep, keyboardDebounceTime } = this.options;
355
+
356
+ let stepDeltaX = 0;
357
+ let stepDeltaY = 0;
358
+ let isResizeKey = false;
359
+
360
+ if (this.resizeHorizontal) {
361
+ if (key === "ArrowLeft") {
362
+ // ArrowLeft should always move the active horizontal edge to the left
363
+ stepDeltaX = -keyboardStep;
364
+ isResizeKey = true;
365
+ } else if (key === "ArrowRight") {
366
+ // ArrowRight should always move the active horizontal edge to the right
367
+ stepDeltaX = keyboardStep;
368
+ isResizeKey = true;
369
+ }
370
+ }
371
+
372
+ if (this.resizeVertical) {
373
+ if (key === "ArrowUp") {
374
+ // ArrowUp should always move the active vertical edge up
375
+ stepDeltaY = -keyboardStep;
376
+ isResizeKey = true;
377
+ } else if (key === "ArrowDown") {
378
+ // ArrowDown should always move the active vertical edge down
379
+ stepDeltaY = keyboardStep;
380
+ isResizeKey = true;
381
+ }
382
+ }
383
+
384
+ if (isResizeKey) {
385
+ e.preventDefault();
386
+ e.stopPropagation();
387
+
388
+ if (!this.#isResizingActive || this.#keyboardResizeTimeout === null) {
389
+ this.#startResize({ inputType: 'keyboard', keyboardKey: key });
390
+ }
391
+
392
+ this.#accumulatedKeyboardDeltaX += stepDeltaX;
393
+ this.#accumulatedKeyboardDeltaY += stepDeltaY;
394
+
395
+ this.#updateSize(this.#accumulatedKeyboardDeltaX, this.#accumulatedKeyboardDeltaY, e);
396
+
397
+ if (this.#keyboardResizeTimeout) {
398
+ clearTimeout(this.#keyboardResizeTimeout);
399
+ }
400
+ this.#keyboardResizeTimeout = setTimeout(() => {
401
+ this.#endResize();
402
+ this.#keyboardResizeTimeout = null;
403
+ }, keyboardDebounceTime);
404
+ }
405
+ }
406
+
407
+ /**
408
+ * Generates an accessible label for the resize control based on its configuration.
409
+ * This is a convenience function that can be used by the consumer if `manageAriaLabel` is false.
410
+ * @returns {string} The suggested aria-label for the control.
411
+ */
412
+ getAriaLabel() {
413
+ const { fromY, fromX } = this.options;
414
+ const directions = [fromY, fromX].filter(v => v);
415
+
416
+ if (directions.length === 0) {
417
+ return "Resize control"; // Fallback for invalid configuration (should be caught by constructor)
418
+ }
419
+
420
+ // Join all directions with a space and append " edge".
421
+ return `Resize from ${directions.join(' ')} edge`;
422
+ }
423
+
424
+ /**
425
+ * Dispatches a custom event on the container element.
426
+ * @param {string} type The event type (e.g., "resizer:start", "resizer:update", "resizer:end").
427
+ * @param {Object} [data={}] Optional data to attach to the event's detail property.
428
+ */
429
+ dispatchEvent(type, data = {}) {
211
430
  this.container.dispatchEvent(createEvent(type, data));
212
431
  }
213
432
  }
@@ -12,7 +12,7 @@ export function configureIcons() {
12
12
  updateSettings({
13
13
  iconClassClose: "fas fa-xmark",
14
14
  iconClassDragX: "fas fa-solid fa-grip-lines-vertical",
15
- iconClassDragBoth: "fas fa-solid fa-grip", // Not really any good icons for this (no diagonal arrows, etc)
15
+ // iconClassDragBoth: "fas fa-solid fa-grip", // Not really any good icons for this (no diagonal arrows, etc)
16
16
  iconClassPrevious: "fas fa-solid fa-chevron-left",
17
17
  iconClassNext: "fas fa-solid fa-chevron-right"
18
18
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulu/frontend",
3
- "version": "0.1.0-beta.82",
3
+ "version": "0.1.0-beta.84",
4
4
  "description": "A versatile SCSS and JavaScript component library offering configurable, accessible components and flexible integration into any project, with SCSS modules suitable for modern JS frameworks.",
5
5
  "browser": "js/index.js",
6
6
  "main": "index.js",
@@ -224,7 +224,7 @@ $sizes: (
224
224
  /// @include breakpoints.fromEach($breakpoints) using ($props) {
225
225
  /// width: map.get($props, "width");
226
226
  /// }
227
- /// @param {String} $breakpoints A map with breakpoints direction will be pulled from each items "direction" property, if direction is missing and no breakpoint will wrap the code
227
+ /// @param {String} $breakpoints A map with breakpoints direction will be pulled from each items "direction" property, if direction is missing no breakpoint will wrap the code (as convention we call the default non-breakpoint direction "default")
228
228
  /// @param {String} $options A map with options to change the behavior
229
229
  /// @param {Boolean} $options.directionRequired Require direction throw error if missing direction
230
230
 
package/scss/_button.scss CHANGED
@@ -94,7 +94,7 @@ $config: (
94
94
 
95
95
  $sizes: (
96
96
  "small" : (
97
- "padding": (0.35em 1.5em),
97
+ "padding": (0.35em 1em),
98
98
  "min-width": 0,
99
99
  "icon-size": 2rem,
100
100
  "icon-font-size": 1rem
@@ -118,9 +118,9 @@ $styles: (
118
118
  "border-color" : transparent,
119
119
  "box-shadow" : none,
120
120
  "hover" : (
121
- "background-color" : "white",
122
- "color" : inherit,
123
- "border-color" : transparent,
121
+ "background-color" : "control-background",
122
+ "color" : "control",
123
+ "border-color" : "control-border",
124
124
  )
125
125
  ),
126
126
  "outline" : (
@@ -129,7 +129,9 @@ $styles: (
129
129
  "border-color" : "rule-light",
130
130
  "box-shadow" : none,
131
131
  "hover" : (
132
- "background-color" : "white",
132
+ "background-color" : "control-background",
133
+ "color" : "control",
134
+ "border-color" : "control-border",
133
135
  )
134
136
  ),
135
137
  ) !default;
@@ -124,11 +124,27 @@ $rule-margins: (
124
124
 
125
125
  /// Get a rule style
126
126
  /// @param {Map} $changes Map of changes
127
+ /// @return {CssValue} Rule style (css border value)
127
128
 
128
129
  @function get-rule-style($name: "default") {
129
130
  @return utils.require-map-get($rule-styles, $name, "element [rule style]");
130
131
  }
131
132
 
133
+ /// Get an optional rule style (which is a border)
134
+ /// - If the value is a string we return the rule style, else we return the value
135
+ /// - Used for things where configuration for say a border can be a user defined border or a string (they want an existing rule-style)
136
+ /// - Except when passing "none"/none this will return as-is (since it's a border property)
137
+ /// @param {*} $value The value to check
138
+ /// @return {*} Rule style if string, else value
139
+
140
+ @function get-optional-rule-style($value) {
141
+ @if (meta.type-of($value) == "string") {
142
+ @return get-rule-style($value);
143
+ } @else {
144
+ @return $value;
145
+ }
146
+ }
147
+
132
148
  /// Sets rule margin
133
149
  /// @param {Map} $changes Map of changes
134
150
 
package/scss/_utils.scss CHANGED
@@ -438,6 +438,13 @@ $config: (
438
438
  @return $value;
439
439
  }
440
440
 
441
+ /// If the value passed is equal to true use the default, else return the value
442
+ /// @param {*} $value The value to check
443
+ /// @param {*} $default The value to return when true
444
+ @function default($value, $default) {
445
+ @return if($value == true, $default, $value);
446
+ }
447
+
441
448
  /// Replaces all or one occurrence of a string within a string
442
449
  /// @param {String} $string String to operate on
443
450
  /// @param {String} $find String to find within string