@dodlhuat/basix 1.1.1 → 1.2.1

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 (352) hide show
  1. package/README.md +706 -482
  2. package/css/accordion.scss +86 -87
  3. package/css/alert.scss +137 -137
  4. package/css/badge.scss +104 -0
  5. package/css/bottom-sheet.scss +192 -0
  6. package/css/breadcrumb.scss +158 -0
  7. package/css/button.scss +48 -0
  8. package/css/calendar.scss +957 -0
  9. package/css/card.scss +65 -65
  10. package/css/chart.scss +270 -157
  11. package/css/chat-bubbles.scss +134 -68
  12. package/css/chips.scss +109 -19
  13. package/css/colors.scss +32 -32
  14. package/css/context-menu.scss +182 -0
  15. package/css/datepicker.scss +336 -336
  16. package/css/defaults.scss +90 -90
  17. package/css/docs.scss +529 -0
  18. package/css/editor.scss +664 -461
  19. package/css/file-uploader.scss +1 -1
  20. package/css/flyout-menu.scss +361 -361
  21. package/css/form.scss +124 -0
  22. package/css/gallery.scss +65 -6
  23. package/css/grid.scss +41 -40
  24. package/css/group-picker.scss +345 -0
  25. package/css/guitar-chords.css +250 -250
  26. package/css/icons.scss +330 -330
  27. package/css/parameters.scss +3 -3
  28. package/css/placeholder.scss +33 -33
  29. package/css/popover.scss +206 -0
  30. package/css/progress.scss +76 -32
  31. package/css/properties.scss +51 -36
  32. package/css/push-menu.scss +302 -174
  33. package/css/reset.scss +39 -39
  34. package/css/scrollbar.scss +62 -5
  35. package/css/sidebar-nav.scss +92 -0
  36. package/css/spinner.scss +65 -65
  37. package/css/stepper.scss +248 -0
  38. package/css/style.css +4603 -273
  39. package/css/style.css.map +1 -1
  40. package/css/style.min.css +1 -1
  41. package/css/style.scss +51 -39
  42. package/css/table.scss +199 -199
  43. package/css/tabs.scss +154 -123
  44. package/css/timeline.scss +83 -38
  45. package/css/timepicker.scss +100 -5
  46. package/css/toast.scss +81 -81
  47. package/css/typography.scss +194 -161
  48. package/css/virtual-dropdown.scss +35 -29
  49. package/js/bottom-sheet.js +173 -0
  50. package/js/bottom-sheet.ts +222 -0
  51. package/js/calendar.js +532 -0
  52. package/js/calendar.ts +706 -0
  53. package/js/carousel.js +26 -13
  54. package/js/chart.js +573 -257
  55. package/js/chart.ts +692 -0
  56. package/js/code-viewer.js +10 -10
  57. package/js/code-viewer.ts +188 -188
  58. package/js/context-menu.js +212 -0
  59. package/js/context-menu.ts +252 -0
  60. package/js/datepicker.ts +627 -627
  61. package/js/docs-nav.js +204 -0
  62. package/js/dropdown.ts +179 -179
  63. package/js/editor.js +96 -38
  64. package/js/editor.ts +483 -425
  65. package/js/file-uploader.js +1 -0
  66. package/js/file-uploader.ts +1 -0
  67. package/js/flyout-menu.js +14 -14
  68. package/js/flyout-menu.ts +249 -249
  69. package/js/form-builder.js +106 -106
  70. package/js/gallery.js +13 -6
  71. package/js/gallery.ts +245 -236
  72. package/js/group-picker.js +342 -0
  73. package/js/group-picker.ts +447 -0
  74. package/js/guitar-chords.js +268 -268
  75. package/js/lazy-loader.js +121 -121
  76. package/js/modal.ts +166 -166
  77. package/js/popover.js +163 -0
  78. package/js/popover.ts +219 -0
  79. package/js/position.js +108 -0
  80. package/js/position.ts +111 -0
  81. package/js/push-menu.js +226 -113
  82. package/js/push-menu.ts +284 -145
  83. package/js/request.js +50 -50
  84. package/js/scroll.ts +47 -47
  85. package/js/scrollbar.js +13 -0
  86. package/js/scrollbar.ts +324 -307
  87. package/js/select.ts +216 -216
  88. package/js/sidebar-nav.js +41 -0
  89. package/js/sidebar-nav.ts +66 -0
  90. package/js/stepper.js +80 -0
  91. package/js/stepper.ts +104 -0
  92. package/js/table.ts +452 -452
  93. package/js/tabs.ts +279 -279
  94. package/js/theme.js +17 -6
  95. package/js/theme.ts +234 -224
  96. package/js/timepicker.js +21 -8
  97. package/js/toast.ts +137 -137
  98. package/js/tooltip.js +6 -60
  99. package/js/tooltip.ts +184 -251
  100. package/js/tsconfig.json +18 -18
  101. package/js/utils.ts +83 -83
  102. package/js/virtual-dropdown.js +25 -25
  103. package/js/virtual-dropdown.ts +365 -365
  104. package/package.json +39 -39
  105. package/fonts/Outfit-VariableFont_wght.woff +0 -0
  106. package/fonts/material-icons.woff2 +0 -0
  107. package/icons/activity-outline.svg +0 -1
  108. package/icons/alert-circle-outline.svg +0 -1
  109. package/icons/alert-triangle-outline.svg +0 -1
  110. package/icons/archive-outline.svg +0 -1
  111. package/icons/arrow-back-outline.svg +0 -1
  112. package/icons/arrow-circle-down-outline.svg +0 -1
  113. package/icons/arrow-circle-left-outline.svg +0 -1
  114. package/icons/arrow-circle-right-outline.svg +0 -1
  115. package/icons/arrow-circle-up-outline.svg +0 -1
  116. package/icons/arrow-down-outline.svg +0 -1
  117. package/icons/arrow-downward-outline.svg +0 -1
  118. package/icons/arrow-forward-outline.svg +0 -1
  119. package/icons/arrow-ios-back-outline.svg +0 -1
  120. package/icons/arrow-ios-downward-outline.svg +0 -1
  121. package/icons/arrow-ios-forward-outline.svg +0 -1
  122. package/icons/arrow-ios-upward-outline.svg +0 -1
  123. package/icons/arrow-left-outline.svg +0 -1
  124. package/icons/arrow-right-outline.svg +0 -1
  125. package/icons/arrow-up-outline.svg +0 -1
  126. package/icons/arrow-upward-outline.svg +0 -1
  127. package/icons/arrowhead-down-outline.svg +0 -1
  128. package/icons/arrowhead-left-outline.svg +0 -1
  129. package/icons/arrowhead-right-outline.svg +0 -1
  130. package/icons/arrowhead-up-outline.svg +0 -1
  131. package/icons/at-outline.svg +0 -1
  132. package/icons/attach-2-outline.svg +0 -1
  133. package/icons/attach-outline.svg +0 -1
  134. package/icons/award-outline.svg +0 -1
  135. package/icons/backspace-outline.svg +0 -1
  136. package/icons/bar-chart-2-outline.svg +0 -1
  137. package/icons/bar-chart-outline.svg +0 -1
  138. package/icons/battery-outline.svg +0 -1
  139. package/icons/behance-outline.svg +0 -1
  140. package/icons/bell-off-outline.svg +0 -1
  141. package/icons/bell-outline.svg +0 -1
  142. package/icons/bluetooth-outline.svg +0 -1
  143. package/icons/book-open-outline.svg +0 -1
  144. package/icons/book-outline.svg +0 -1
  145. package/icons/bookmark-outline.svg +0 -1
  146. package/icons/briefcase-outline.svg +0 -1
  147. package/icons/browser-outline.svg +0 -1
  148. package/icons/brush-outline.svg +0 -1
  149. package/icons/bulb-outline.svg +0 -1
  150. package/icons/calendar-outline.svg +0 -1
  151. package/icons/camera-outline.svg +0 -1
  152. package/icons/car-outline.svg +0 -1
  153. package/icons/cast-outline.svg +0 -1
  154. package/icons/charging-outline.svg +0 -1
  155. package/icons/checkmark-circle-2-outline.svg +0 -1
  156. package/icons/checkmark-circle-outline.svg +0 -1
  157. package/icons/checkmark-outline.svg +0 -1
  158. package/icons/checkmark-square-2-outline.svg +0 -1
  159. package/icons/checkmark-square-outline.svg +0 -1
  160. package/icons/chevron-down-outline.svg +0 -1
  161. package/icons/chevron-left-outline.svg +0 -1
  162. package/icons/chevron-right-outline.svg +0 -1
  163. package/icons/chevron-up-outline.svg +0 -1
  164. package/icons/clipboard-outline.svg +0 -1
  165. package/icons/clock-outline.svg +0 -1
  166. package/icons/close-circle-outline.svg +0 -1
  167. package/icons/close-outline.svg +0 -1
  168. package/icons/close-square-outline.svg +0 -1
  169. package/icons/cloud-download-outline.svg +0 -1
  170. package/icons/cloud-upload-outline.svg +0 -1
  171. package/icons/code-download-outline.svg +0 -1
  172. package/icons/code-outline.svg +0 -1
  173. package/icons/collapse-outline.svg +0 -1
  174. package/icons/color-palette-outline.svg +0 -1
  175. package/icons/color-picker-outline.svg +0 -1
  176. package/icons/compass-outline.svg +0 -1
  177. package/icons/copy-outline.svg +0 -1
  178. package/icons/corner-down-left-outline.svg +0 -1
  179. package/icons/corner-down-right-outline.svg +0 -1
  180. package/icons/corner-left-down-outline.svg +0 -1
  181. package/icons/corner-left-up-outline.svg +0 -1
  182. package/icons/corner-right-down-outline.svg +0 -1
  183. package/icons/corner-right-up-outline.svg +0 -1
  184. package/icons/corner-up-left-outline.svg +0 -1
  185. package/icons/corner-up-right-outline.svg +0 -1
  186. package/icons/credit-card-outline.svg +0 -1
  187. package/icons/crop-outline.svg +0 -1
  188. package/icons/cube-outline.svg +0 -1
  189. package/icons/diagonal-arrow-left-down-outline.svg +0 -1
  190. package/icons/diagonal-arrow-left-up-outline.svg +0 -1
  191. package/icons/diagonal-arrow-right-down-outline.svg +0 -1
  192. package/icons/diagonal-arrow-right-up-outline.svg +0 -1
  193. package/icons/done-all-outline.svg +0 -1
  194. package/icons/download-outline.svg +0 -1
  195. package/icons/droplet-off-outline.svg +0 -1
  196. package/icons/droplet-outline.svg +0 -1
  197. package/icons/edit-2-outline.svg +0 -1
  198. package/icons/edit-outline.svg +0 -1
  199. package/icons/email-outline.svg +0 -1
  200. package/icons/expand-outline.svg +0 -1
  201. package/icons/external-link-outline.svg +0 -1
  202. package/icons/eye-off-2-outline.svg +0 -1
  203. package/icons/eye-off-outline.svg +0 -1
  204. package/icons/eye-outline.svg +0 -1
  205. package/icons/facebook-outline.svg +0 -1
  206. package/icons/file-add-outline.svg +0 -1
  207. package/icons/file-outline.svg +0 -1
  208. package/icons/file-remove-outline.svg +0 -1
  209. package/icons/file-text-outline.svg +0 -1
  210. package/icons/film-outline.svg +0 -1
  211. package/icons/flag-outline.svg +0 -1
  212. package/icons/flash-off-outline.svg +0 -1
  213. package/icons/flash-outline.svg +0 -1
  214. package/icons/flip-2-outline.svg +0 -1
  215. package/icons/flip-outline.svg +0 -1
  216. package/icons/folder-add-outline.svg +0 -1
  217. package/icons/folder-outline.svg +0 -1
  218. package/icons/folder-remove-outline.svg +0 -1
  219. package/icons/funnel-outline.svg +0 -1
  220. package/icons/gift-outline.svg +0 -1
  221. package/icons/github-outline.svg +0 -1
  222. package/icons/globe-2-outline.svg +0 -1
  223. package/icons/globe-outline.svg +0 -1
  224. package/icons/google-outline.svg +0 -1
  225. package/icons/grid-outline.svg +0 -1
  226. package/icons/hard-drive-outline.svg +0 -1
  227. package/icons/hash-outline.svg +0 -1
  228. package/icons/headphones-outline.svg +0 -1
  229. package/icons/heart-outline.svg +0 -1
  230. package/icons/home-outline.svg +0 -1
  231. package/icons/image-outline.svg +0 -1
  232. package/icons/inbox-outline.svg +0 -1
  233. package/icons/info-outline.svg +0 -1
  234. package/icons/keypad-outline.svg +0 -1
  235. package/icons/layers-outline.svg +0 -1
  236. package/icons/layout-outline.svg +0 -1
  237. package/icons/link-2-outline.svg +0 -1
  238. package/icons/link-outline.svg +0 -1
  239. package/icons/linkedin-outline.svg +0 -1
  240. package/icons/list-outline.svg +0 -1
  241. package/icons/loader-outline.svg +0 -1
  242. package/icons/lock-outline.svg +0 -1
  243. package/icons/log-in-outline.svg +0 -1
  244. package/icons/log-out-outline.svg +0 -1
  245. package/icons/map-outline.svg +0 -1
  246. package/icons/maximize-outline.svg +0 -1
  247. package/icons/menu-2-outline.svg +0 -1
  248. package/icons/menu-arrow-outline.svg +0 -1
  249. package/icons/menu-outline.svg +0 -1
  250. package/icons/message-circle-outline.svg +0 -1
  251. package/icons/message-square-outline.svg +0 -1
  252. package/icons/mic-off-outline.svg +0 -1
  253. package/icons/mic-outline.svg +0 -1
  254. package/icons/minimize-outline.svg +0 -1
  255. package/icons/minus-circle-outline.svg +0 -1
  256. package/icons/minus-outline.svg +0 -1
  257. package/icons/minus-square-outline.svg +0 -1
  258. package/icons/monitor-outline.svg +0 -1
  259. package/icons/moon-outline.svg +0 -1
  260. package/icons/more-horizontal-outline.svg +0 -1
  261. package/icons/more-vertical-outline.svg +0 -1
  262. package/icons/move-outline.svg +0 -1
  263. package/icons/music-outline.svg +0 -1
  264. package/icons/navigation-2-outline.svg +0 -1
  265. package/icons/navigation-outline.svg +0 -1
  266. package/icons/npm-outline.svg +0 -1
  267. package/icons/options-2-outline.svg +0 -1
  268. package/icons/options-outline.svg +0 -1
  269. package/icons/pantone-outline.svg +0 -1
  270. package/icons/paper-plane-outline.svg +0 -1
  271. package/icons/pause-circle-outline.svg +0 -1
  272. package/icons/people-outline.svg +0 -1
  273. package/icons/percent-outline.svg +0 -1
  274. package/icons/person-add-outline.svg +0 -1
  275. package/icons/person-delete-outline.svg +0 -1
  276. package/icons/person-done-outline.svg +0 -1
  277. package/icons/person-outline.svg +0 -1
  278. package/icons/person-remove-outline.svg +0 -1
  279. package/icons/phone-call-outline.svg +0 -1
  280. package/icons/phone-missed-outline.svg +0 -1
  281. package/icons/phone-off-outline.svg +0 -1
  282. package/icons/phone-outline.svg +0 -1
  283. package/icons/pie-chart-outline.svg +0 -1
  284. package/icons/pin-outline.svg +0 -1
  285. package/icons/play-circle-outline.svg +0 -1
  286. package/icons/plus-circle-outline.svg +0 -1
  287. package/icons/plus-outline.svg +0 -1
  288. package/icons/plus-square-outline.svg +0 -1
  289. package/icons/power-outline.svg +0 -1
  290. package/icons/pricetags-outline.svg +0 -1
  291. package/icons/printer-outline.svg +0 -1
  292. package/icons/question-mark-circle-outline.svg +0 -1
  293. package/icons/question-mark-outline.svg +0 -1
  294. package/icons/radio-button-off-outline.svg +0 -1
  295. package/icons/radio-button-on-outline.svg +0 -1
  296. package/icons/radio-outline.svg +0 -1
  297. package/icons/recording-outline.svg +0 -1
  298. package/icons/refresh-outline.svg +0 -1
  299. package/icons/repeat-outline.svg +0 -1
  300. package/icons/rewind-left-outline.svg +0 -1
  301. package/icons/rewind-right-outline.svg +0 -1
  302. package/icons/save-outline.svg +0 -1
  303. package/icons/scissors-outline.svg +0 -1
  304. package/icons/search-outline.svg +0 -1
  305. package/icons/settings-2-outline.svg +0 -1
  306. package/icons/settings-outline.svg +0 -1
  307. package/icons/shake-outline.svg +0 -1
  308. package/icons/share-outline.svg +0 -1
  309. package/icons/shield-off-outline.svg +0 -1
  310. package/icons/shield-outline.svg +0 -1
  311. package/icons/shopping-bag-outline.svg +0 -1
  312. package/icons/shopping-cart-outline.svg +0 -1
  313. package/icons/shuffle-2-outline.svg +0 -1
  314. package/icons/shuffle-outline.svg +0 -1
  315. package/icons/skip-back-outline.svg +0 -1
  316. package/icons/skip-forward-outline.svg +0 -1
  317. package/icons/slash-outline.svg +0 -1
  318. package/icons/smartphone-outline.svg +0 -1
  319. package/icons/smiling-face-outline.svg +0 -1
  320. package/icons/speaker-outline.svg +0 -1
  321. package/icons/square-outline.svg +0 -1
  322. package/icons/star-outline.svg +0 -1
  323. package/icons/stop-circle-outline.svg +0 -1
  324. package/icons/sun-outline.svg +0 -1
  325. package/icons/swap-outline.svg +0 -1
  326. package/icons/sync-outline.svg +0 -1
  327. package/icons/text-outline.svg +0 -1
  328. package/icons/thermometer-minus-outline.svg +0 -1
  329. package/icons/thermometer-outline.svg +0 -1
  330. package/icons/thermometer-plus-outline.svg +0 -1
  331. package/icons/toggle-left-outline.svg +0 -1
  332. package/icons/toggle-right-outline.svg +0 -1
  333. package/icons/trash-2-outline.svg +0 -1
  334. package/icons/trash-outline.svg +0 -1
  335. package/icons/trending-down-outline.svg +0 -1
  336. package/icons/trending-up-outline.svg +0 -1
  337. package/icons/tv-outline.svg +0 -1
  338. package/icons/twitter-outline.svg +0 -1
  339. package/icons/umbrella-outline.svg +0 -1
  340. package/icons/undo-outline.svg +0 -1
  341. package/icons/unlock-outline.svg +0 -1
  342. package/icons/upload-outline.svg +0 -1
  343. package/icons/video-off-outline.svg +0 -1
  344. package/icons/video-outline.svg +0 -1
  345. package/icons/volume-down-outline.svg +0 -1
  346. package/icons/volume-mute-outline.svg +0 -1
  347. package/icons/volume-off-outline.svg +0 -1
  348. package/icons/volume-up-outline.svg +0 -1
  349. package/icons/wifi-off-outline.svg +0 -1
  350. package/icons/wifi-outline.svg +0 -1
  351. package/js/index.js +0 -718
  352. package/js/index.ts +0 -873
package/js/gallery.ts CHANGED
@@ -1,236 +1,245 @@
1
- interface ImageData {
2
- src: string;
3
- title: string;
4
- desc: string;
5
- }
6
-
7
- interface MasonryGalleryOptions {
8
- minColumnWidth?: number;
9
- scrollThreshold?: number;
10
- loaderSelector?: string;
11
- reload?: number;
12
- fetchFunction?: () => Promise<ImageData[]>;
13
- }
14
-
15
- class MasonryGallery {
16
- private container: HTMLElement;
17
- private readonly loader: HTMLElement | null;
18
- private options: Required<Omit<MasonryGalleryOptions, "loaderSelector">>;
19
- private columns: HTMLDivElement[] = [];
20
- private isFetching: boolean = false;
21
- private resizeObserver: ResizeObserver | null = null;
22
- private abortController: AbortController | null = null;
23
- private reloaded = 0;
24
-
25
- constructor(containerId: string, options: MasonryGalleryOptions = {}) {
26
- const container = document.getElementById(containerId);
27
- if (!container) {
28
- throw new Error(`Container with id "${containerId}" not found`);
29
- }
30
- this.container = container;
31
- this.loader = document.querySelector(options.loaderSelector || ".loader");
32
-
33
- this.options = {
34
- minColumnWidth: options.minColumnWidth ?? 250,
35
- scrollThreshold: options.scrollThreshold ?? 100,
36
- reload: 2,
37
- fetchFunction: options.fetchFunction ?? this.fetchMockImages,
38
- };
39
-
40
- this.init();
41
- }
42
-
43
- private init(): void {
44
- this.setupLayout();
45
- this.loadMoreImages();
46
- this.addEventListeners();
47
- }
48
-
49
- private setupLayout(): void {
50
- const containerWidth = this.container.getBoundingClientRect().width;
51
- const numColumns = Math.max(
52
- 1,
53
- Math.floor(containerWidth / this.options.minColumnWidth),
54
- );
55
-
56
- if (this.columns.length !== numColumns) {
57
- this.container.innerHTML = "";
58
- this.columns = [];
59
-
60
- for (let i = 0; i < numColumns; i++) {
61
- const col = document.createElement("div");
62
- col.className = "masonry-column";
63
- this.container.appendChild(col);
64
- this.columns.push(col);
65
- }
66
- }
67
- }
68
-
69
- private addEventListeners(): void {
70
- let resizeTimeout: number;
71
- window.addEventListener("resize", () => {
72
- clearTimeout(resizeTimeout);
73
- resizeTimeout = setTimeout(() => {
74
- this.reLayout();
75
- }, 200);
76
- });
77
-
78
- this.abortController = new AbortController();
79
- window.addEventListener("scroll", this.handleScroll, {
80
- passive: true,
81
- signal: this.abortController.signal,
82
- });
83
- }
84
-
85
- private reLayout(): void {
86
- const items: HTMLElement[] = [];
87
- this.columns.forEach((col) => {
88
- Array.from(col.children).forEach((child) => {
89
- items.push(child as HTMLElement);
90
- });
91
- col.innerHTML = "";
92
- });
93
-
94
- const availableWidth = Math.min(1200, window.innerWidth - 40);
95
- const numColumns = Math.max(
96
- 1,
97
- Math.floor(availableWidth / this.options.minColumnWidth),
98
- );
99
-
100
- if (this.columns.length !== numColumns) {
101
- this.container.innerHTML = "";
102
- this.columns = [];
103
-
104
- for (let i = 0; i < numColumns; i++) {
105
- const col = document.createElement("div");
106
- col.className = "masonry-column";
107
- this.container.appendChild(col);
108
- this.columns.push(col);
109
- }
110
- }
111
-
112
- items.forEach((item) => {
113
- this.addToShortestColumn(item);
114
- });
115
- }
116
-
117
- private handleScroll = (): void => {
118
- if (this.isFetching) return;
119
-
120
- const rect = this.container.getBoundingClientRect();
121
- if (rect.bottom > 0 && rect.bottom <= window.innerHeight + this.options.scrollThreshold) {
122
- this.loadMoreImages();
123
- }
124
- };
125
-
126
- private async loadMoreImages(isAutoFill = false): Promise<void> {
127
- if (!isAutoFill) this.reloaded++;
128
- if (this.options.reload > 0 && this.reloaded > this.options.reload) {
129
- console.warn("Maximum reload limit reached.");
130
- return;
131
- }
132
- if (this.isFetching) return;
133
-
134
- this.isFetching = true;
135
- this.toggleLoader(true);
136
-
137
- try {
138
- const newImages = await this.options.fetchFunction();
139
- this.renderImages(newImages);
140
- } catch (error) {
141
- throw new Error("Error loading images: " + error);
142
- } finally {
143
- this.isFetching = false;
144
- this.toggleLoader(false);
145
- // If the rendered content doesn't fill the viewport, auto-load the next
146
- // batch without waiting for a scroll event (multi-column layout is shorter)
147
- requestAnimationFrame(() => {
148
- const rect = this.container.getBoundingClientRect();
149
- if (rect.bottom <= window.innerHeight + this.options.scrollThreshold) {
150
- this.loadMoreImages(true);
151
- }
152
- });
153
- }
154
- }
155
-
156
- private toggleLoader(show: boolean): void {
157
- if (this.loader) {
158
- this.loader.classList.toggle("hidden", !show);
159
- }
160
- }
161
-
162
- private fetchMockImages = (): Promise<ImageData[]> => {
163
- throw Error("Method not implemented.");
164
- };
165
-
166
- private renderImages(imageDataList: ImageData[]): void {
167
- imageDataList.forEach((data) => {
168
- const item = this.createCard(data);
169
- this.addToShortestColumn(item);
170
-
171
- requestAnimationFrame(() => {
172
- const img = item.querySelector("img");
173
- if (img) {
174
- img.addEventListener("load", () => img.classList.add("loaded"), {
175
- once: true,
176
- });
177
- if (img.complete) {
178
- img.classList.add("loaded");
179
- }
180
- }
181
- });
182
- });
183
- }
184
-
185
- private createCard(data: ImageData): HTMLDivElement {
186
- const div = document.createElement("div");
187
- div.className = "masonry-item";
188
-
189
- div.innerHTML = `
190
- <img src="${this.escapeHtml(data.src)}" alt="${this.escapeHtml(data.title)}" loading="lazy">
191
- <div class="masonry-item-info">
192
- <h3 class="masonry-item-title">${this.escapeHtml(data.title)}</h3>
193
- <p class="masonry-item-desc">${this.escapeHtml(data.desc)}</p>
194
- </div>
195
- `;
196
-
197
- return div;
198
- }
199
-
200
- private escapeHtml(text: string): string {
201
- const div = document.createElement("div");
202
- div.textContent = text;
203
- return div.innerHTML;
204
- }
205
-
206
- private addToShortestColumn(element: HTMLElement): void {
207
- if (this.columns.length === 0) return;
208
-
209
- let shortestCol = this.columns[0];
210
- let minHeight = shortestCol.offsetHeight;
211
-
212
- for (let i = 1; i < this.columns.length; i++) {
213
- const h = this.columns[i].offsetHeight;
214
- if (h < minHeight) {
215
- minHeight = h;
216
- shortestCol = this.columns[i];
217
- }
218
- }
219
-
220
- shortestCol.appendChild(element);
221
- }
222
-
223
- public destroy(): void {
224
- if (this.resizeObserver) {
225
- this.resizeObserver.disconnect();
226
- this.resizeObserver = null;
227
- }
228
-
229
- if (this.abortController) {
230
- this.abortController.abort();
231
- this.abortController = null;
232
- }
233
- }
234
- }
235
-
236
- export { MasonryGallery, ImageData };
1
+ interface ImageData {
2
+ src: string;
3
+ title: string;
4
+ desc: string;
5
+ }
6
+
7
+ interface MasonryGalleryOptions {
8
+ minColumnWidth?: number;
9
+ scrollThreshold?: number;
10
+ loaderSelector?: string;
11
+ reload?: number;
12
+ fetchFunction?: () => Promise<ImageData[]>;
13
+ }
14
+
15
+ class MasonryGallery {
16
+ private container: HTMLElement;
17
+ private readonly loader: HTMLElement | null;
18
+ private options: Required<Omit<MasonryGalleryOptions, "loaderSelector">>;
19
+ private columns: HTMLDivElement[] = [];
20
+ private isFetching: boolean = false;
21
+ private resizeObserver: ResizeObserver | null = null;
22
+ private abortController: AbortController | null = null;
23
+ private reloaded = 0;
24
+
25
+ constructor(containerId: string, options: MasonryGalleryOptions = {}) {
26
+ const container = document.getElementById(containerId);
27
+ if (!container) {
28
+ throw new Error(`Container with id "${containerId}" not found`);
29
+ }
30
+ this.container = container;
31
+ this.loader = document.querySelector(options.loaderSelector || ".loader");
32
+
33
+ this.options = {
34
+ minColumnWidth: options.minColumnWidth ?? 250,
35
+ scrollThreshold: options.scrollThreshold ?? 100,
36
+ reload: 2,
37
+ fetchFunction: options.fetchFunction ?? this.fetchMockImages,
38
+ };
39
+
40
+ this.init();
41
+ }
42
+
43
+ private init(): void {
44
+ this.setupLayout();
45
+ this.loadMoreImages();
46
+ this.addEventListeners();
47
+ }
48
+
49
+ private setupLayout(): void {
50
+ const containerWidth = this.container.getBoundingClientRect().width;
51
+ const numColumns = Math.max(
52
+ 1,
53
+ Math.floor(containerWidth / this.options.minColumnWidth),
54
+ );
55
+
56
+ if (this.columns.length !== numColumns) {
57
+ this.container.innerHTML = "";
58
+ this.columns = [];
59
+
60
+ for (let i = 0; i < numColumns; i++) {
61
+ const col = document.createElement("div");
62
+ col.className = "masonry-column";
63
+ this.container.appendChild(col);
64
+ this.columns.push(col);
65
+ }
66
+ }
67
+ }
68
+
69
+ private addEventListeners(): void {
70
+ let resizeTimeout: number;
71
+ window.addEventListener("resize", () => {
72
+ clearTimeout(resizeTimeout);
73
+ resizeTimeout = setTimeout(() => {
74
+ this.reLayout();
75
+ }, 200);
76
+ });
77
+
78
+ this.abortController = new AbortController();
79
+ window.addEventListener("scroll", this.handleScroll, {
80
+ passive: true,
81
+ signal: this.abortController.signal,
82
+ });
83
+ }
84
+
85
+ private reLayout(): void {
86
+ const items: HTMLElement[] = [];
87
+ this.columns.forEach((col) => {
88
+ Array.from(col.children).forEach((child) => {
89
+ items.push(child as HTMLElement);
90
+ });
91
+ col.innerHTML = "";
92
+ });
93
+
94
+ const availableWidth = Math.min(1200, window.innerWidth - 40);
95
+ const numColumns = Math.max(
96
+ 1,
97
+ Math.floor(availableWidth / this.options.minColumnWidth),
98
+ );
99
+
100
+ if (this.columns.length !== numColumns) {
101
+ this.container.innerHTML = "";
102
+ this.columns = [];
103
+
104
+ for (let i = 0; i < numColumns; i++) {
105
+ const col = document.createElement("div");
106
+ col.className = "masonry-column";
107
+ this.container.appendChild(col);
108
+ this.columns.push(col);
109
+ }
110
+ }
111
+
112
+ items.forEach((item) => {
113
+ this.addToShortestColumn(item);
114
+ });
115
+ }
116
+
117
+ private handleScroll = (): void => {
118
+ if (this.isFetching) return;
119
+
120
+ const rect = this.container.getBoundingClientRect();
121
+ if (rect.bottom > 0 && rect.bottom <= window.innerHeight + this.options.scrollThreshold) {
122
+ this.loadMoreImages();
123
+ }
124
+ };
125
+
126
+ private async loadMoreImages(isAutoFill = false): Promise<void> {
127
+ if (!isAutoFill) this.reloaded++;
128
+ if (this.options.reload > 0 && this.reloaded > this.options.reload) {
129
+ console.warn("Maximum reload limit reached.");
130
+ return;
131
+ }
132
+ if (this.isFetching) return;
133
+
134
+ this.isFetching = true;
135
+ this.toggleLoader(true);
136
+
137
+ try {
138
+ const newImages = await this.options.fetchFunction();
139
+ this.renderImages(newImages);
140
+ } catch (error) {
141
+ throw new Error("Error loading images: " + error);
142
+ } finally {
143
+ this.isFetching = false;
144
+ this.toggleLoader(false);
145
+ // If the rendered content doesn't fill the viewport, auto-load the next
146
+ // batch without waiting for a scroll event (multi-column layout is shorter)
147
+ requestAnimationFrame(() => {
148
+ const rect = this.container.getBoundingClientRect();
149
+ if (rect.bottom <= window.innerHeight + this.options.scrollThreshold) {
150
+ this.loadMoreImages(true);
151
+ }
152
+ });
153
+ }
154
+ }
155
+
156
+ private toggleLoader(show: boolean): void {
157
+ if (this.loader) {
158
+ this.loader.classList.toggle("hidden", !show);
159
+ }
160
+ }
161
+
162
+ private fetchMockImages = (): Promise<ImageData[]> => {
163
+ throw Error("Method not implemented.");
164
+ };
165
+
166
+ private renderImages(imageDataList: ImageData[]): void {
167
+ // Sort columns by current height so we start filling from the shortest.
168
+ // Then round-robin across them — this avoids the problem where unloaded
169
+ // images (0 height) cause offsetHeight-based distribution to pile all
170
+ // new items into a single column.
171
+ const sorted = [...this.columns].sort(
172
+ (a, b) => a.offsetHeight - b.offsetHeight,
173
+ );
174
+
175
+ imageDataList.forEach((data, index) => {
176
+ const item = this.createCard(data);
177
+ const col = sorted[index % sorted.length];
178
+ col.appendChild(item);
179
+
180
+ requestAnimationFrame(() => {
181
+ const img = item.querySelector("img");
182
+ if (img) {
183
+ img.addEventListener("load", () => img.classList.add("loaded"), {
184
+ once: true,
185
+ });
186
+ if (img.complete) {
187
+ img.classList.add("loaded");
188
+ }
189
+ }
190
+ });
191
+ });
192
+ }
193
+
194
+ private createCard(data: ImageData): HTMLDivElement {
195
+ const div = document.createElement("div");
196
+ div.className = "masonry-item";
197
+
198
+ div.innerHTML = `
199
+ <img src="${this.escapeHtml(data.src)}" alt="${this.escapeHtml(data.title)}" loading="lazy">
200
+ <div class="masonry-item-info">
201
+ <h3 class="masonry-item-title">${this.escapeHtml(data.title)}</h3>
202
+ <p class="masonry-item-desc">${this.escapeHtml(data.desc)}</p>
203
+ </div>
204
+ `;
205
+
206
+ return div;
207
+ }
208
+
209
+ private escapeHtml(text: string): string {
210
+ const div = document.createElement("div");
211
+ div.textContent = text;
212
+ return div.innerHTML;
213
+ }
214
+
215
+ private addToShortestColumn(element: HTMLElement): void {
216
+ if (this.columns.length === 0) return;
217
+
218
+ let shortestCol = this.columns[0];
219
+ let minHeight = shortestCol.offsetHeight;
220
+
221
+ for (let i = 1; i < this.columns.length; i++) {
222
+ const h = this.columns[i].offsetHeight;
223
+ if (h < minHeight) {
224
+ minHeight = h;
225
+ shortestCol = this.columns[i];
226
+ }
227
+ }
228
+
229
+ shortestCol.appendChild(element);
230
+ }
231
+
232
+ public destroy(): void {
233
+ if (this.resizeObserver) {
234
+ this.resizeObserver.disconnect();
235
+ this.resizeObserver = null;
236
+ }
237
+
238
+ if (this.abortController) {
239
+ this.abortController.abort();
240
+ this.abortController = null;
241
+ }
242
+ }
243
+ }
244
+
245
+ export { MasonryGallery, ImageData };