@dodlhuat/basix 1.3.5 → 1.4.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 (82) hide show
  1. package/README.md +92 -7
  2. package/css/checkbox.scss +15 -2
  3. package/css/color-picker.scss +278 -0
  4. package/css/datepicker.scss +5 -3
  5. package/css/push-menu.scss +7 -0
  6. package/css/stepper.scss +1 -1
  7. package/css/style.css +291 -6
  8. package/css/style.css.map +1 -1
  9. package/css/style.min.css +1 -1
  10. package/css/style.min.css.map +1 -1
  11. package/css/style.scss +1 -0
  12. package/css/table.scss +33 -0
  13. package/js/bottom-sheet.d.ts +0 -2
  14. package/js/bottom-sheet.js +0 -2
  15. package/js/calendar.d.ts +9 -17
  16. package/js/calendar.js +4 -7
  17. package/js/carousel.d.ts +0 -2
  18. package/js/carousel.js +1 -3
  19. package/js/chart.d.ts +6 -14
  20. package/js/chart.js +3 -5
  21. package/js/code-viewer.d.ts +2 -1
  22. package/js/code-viewer.js +8 -2
  23. package/js/color-picker.d.ts +50 -0
  24. package/js/color-picker.js +291 -0
  25. package/js/context-menu.d.ts +0 -3
  26. package/js/context-menu.js +0 -3
  27. package/js/datepicker.d.ts +0 -4
  28. package/js/datepicker.js +0 -1
  29. package/js/docs-nav.js +1 -0
  30. package/js/dropdown.d.ts +1 -4
  31. package/js/dropdown.js +0 -1
  32. package/js/editor.d.ts +0 -4
  33. package/js/editor.js +3 -5
  34. package/js/file-uploader.d.ts +0 -4
  35. package/js/file-uploader.js +0 -1
  36. package/js/flyout-menu.d.ts +0 -2
  37. package/js/flyout-menu.js +0 -1
  38. package/js/gallery.d.ts +1 -4
  39. package/js/gallery.js +18 -23
  40. package/js/group-picker.d.ts +0 -5
  41. package/js/group-picker.js +6 -8
  42. package/js/lightbox.d.ts +1 -4
  43. package/js/lightbox.js +6 -10
  44. package/js/modal.d.ts +0 -2
  45. package/js/modal.js +0 -1
  46. package/js/popover.d.ts +0 -3
  47. package/js/popover.js +0 -2
  48. package/js/position.d.ts +0 -13
  49. package/js/position.js +0 -12
  50. package/js/push-menu.d.ts +0 -3
  51. package/js/push-menu.js +6 -11
  52. package/js/range-slider.d.ts +0 -1
  53. package/js/range-slider.js +2 -3
  54. package/js/scroll.d.ts +0 -2
  55. package/js/scroll.js +5 -6
  56. package/js/scrollbar.d.ts +2 -10
  57. package/js/scrollbar.js +25 -42
  58. package/js/select.d.ts +2 -3
  59. package/js/select.js +6 -9
  60. package/js/sidebar-nav.d.ts +4 -15
  61. package/js/sidebar-nav.js +21 -35
  62. package/js/stepper.d.ts +5 -2
  63. package/js/stepper.js +42 -6
  64. package/js/table.d.ts +1 -56
  65. package/js/table.js +4 -56
  66. package/js/tabs.d.ts +1 -32
  67. package/js/tabs.js +7 -39
  68. package/js/theme.d.ts +0 -44
  69. package/js/theme.js +0 -44
  70. package/js/timepicker.d.ts +1 -4
  71. package/js/timepicker.js +1 -2
  72. package/js/toast.d.ts +0 -2
  73. package/js/toast.js +0 -1
  74. package/js/tooltip.d.ts +0 -4
  75. package/js/tooltip.js +0 -1
  76. package/js/tree.d.ts +0 -3
  77. package/js/tree.js +9 -11
  78. package/js/utils.d.ts +0 -10
  79. package/js/utils.js +2 -42
  80. package/js/virtual-dropdown.d.ts +3 -5
  81. package/js/virtual-dropdown.js +17 -66
  82. package/package.json +6 -2
package/README.md CHANGED
@@ -1,10 +1,47 @@
1
- # Basix 1.3.5
1
+ # Basix 1.4.1
2
2
 
3
3
  Basix is intended as a starter for the rapid development of a design. Each design element can be added individually to
4
4
  include only the data required. It is using plain javascript / typescript and therefore is not dependent on any plugin.
5
5
 
6
6
  A demo can be found here: <a href="http://www.andibauer.at/basix/" target="_blank">http://www.andibauer.at/basix/</a>
7
7
 
8
+ ---
9
+
10
+ ## Migration Guide
11
+
12
+ ### 1.4.0 → 1.4.1
13
+
14
+ No breaking changes. Internal code quality pass across all TypeScript source files:
15
+
16
+ - **All components** — event listeners migrated to `AbortController` pattern; arrow class fields replaced with regular methods. `destroy()` now consistently calls `abortController.abort()` instead of individual `removeEventListener` calls.
17
+ - **Various** — optional chaining (`?.`) for callbacks, template literals over string concatenation, `querySelector<T>` generics over `as` casts, redundant type annotations removed.
18
+
19
+ ---
20
+
21
+ ### 1.3.5 → 1.4.0
22
+
23
+ #### Breaking changes
24
+
25
+ **`TimeSpanPicker` — option `toString` renamed to `toLabel`**
26
+
27
+ The `toString` option shadowed `Object.prototype.toString` and is now renamed to `toLabel`. Update any call sites that pass this option:
28
+
29
+ ```js
30
+ // Before (1.3.5)
31
+ new TimeSpanPicker('container', { toString: 'Bis' });
32
+
33
+ // After (1.4.0)
34
+ new TimeSpanPicker('container', { toLabel: 'Bis' });
35
+ ```
36
+
37
+ #### Bug fixes
38
+
39
+ - **`MasonryGallery`** — the `reload` option was previously hard-coded to `2` and ignored. It now respects the value you pass. The default remains `2`, so existing usage without the option is unaffected.
40
+ - **`Tabs`** — `destroy()` no longer operates on detached DOM nodes. Event listeners are now cleaned up via `AbortController`.
41
+ - **`gallery`** — image-load errors no longer cause an unhandled promise rejection; they are logged to `console.error` instead.
42
+
43
+ ---
44
+
8
45
  ### Benefits
9
46
 
10
47
  * lightweight
@@ -479,8 +516,19 @@ The Tabs component creates accessible tabbed interfaces. Supports horizontal/ver
479
516
 
480
517
  | Option | Type | Default | Description |
481
518
  |---|---|---|---|
482
- | `layout` | string | `'horizontal'` | Layout of the tabs, either `'horizontal'` or `'vertical'` |
483
- | `defaultTab` | integer | `0` | Index of the default active tab (0-based) |
519
+ | `layout` | string | `'horizontal'` | `'horizontal'` or `'vertical'` |
520
+ | `defaultTab` | number | `0` | Index of the initially active tab (0-based) |
521
+ | `menuPos` | string | `'top'` / `'left'` | Tab bar position: `'top'`, `'bottom'`, `'left'`, `'right'`. Defaults to `'top'` for horizontal layout, `'left'` for vertical. |
522
+ | `onChange` | function | — | Callback fired on tab change: `(index: number) => void` |
523
+
524
+ | Method | Description |
525
+ |---|---|
526
+ | `goToTab(index)` | Programmatically activate a tab and focus it |
527
+ | `getCurrentTab()` | Returns the index of the currently active tab |
528
+ | `getTabCount()` | Returns the total number of tabs |
529
+ | `enableTab(index)` | Re-enables a disabled tab |
530
+ | `disableTab(index)` | Disables a tab; if it was active, moves focus to the next enabled tab |
531
+ | `destroy()` | Removes event listeners and cleans up ARIA attributes |
484
532
 
485
533
  ### Timeline
486
534
 
@@ -788,7 +836,7 @@ const picker = new TimeSpanPicker('my-container', {
788
836
  defaultStart: '09:00',
789
837
  defaultEnd: '17:00',
790
838
  fromString: 'Von', // optional — defaults to 'From'
791
- toString: 'Bis', // optional — defaults to 'To'
839
+ toLabel: 'Bis', // optional — defaults to 'To'
792
840
  onChange: (start, end) => console.log(start, end),
793
841
  });
794
842
 
@@ -799,6 +847,36 @@ picker.isValid(); // boolean
799
847
  picker.destroy();
800
848
  ```
801
849
 
850
+ ### Color Picker
851
+
852
+ The ColorPicker component renders a canvas-based HSB color picker with a hue slider and hex/RGB inputs. It builds its own DOM inside any container element and is fully mobile-friendly — drag gestures on the color field do not trigger page scroll.
853
+
854
+ ``` js
855
+ import { ColorPicker } from '@dodlhuat/basix/js/color-picker.js';
856
+
857
+ const picker = new ColorPicker('#my-picker', {
858
+ value: '#3d63dd',
859
+ onChange: (hex, rgb) => {
860
+ console.log(hex); // '#3d63dd'
861
+ console.log(rgb.r, rgb.g, rgb.b); // 61, 99, 221
862
+ },
863
+ });
864
+
865
+ picker.getValue(); // current hex string, e.g. '#3d63dd'
866
+ picker.setValue('#ff6b6b'); // set color programmatically
867
+ picker.destroy(); // removes listeners, disconnects ResizeObserver, clears DOM
868
+ ```
869
+
870
+ ``` html
871
+ <!-- Any empty container works — the component builds its own markup inside -->
872
+ <div id="my-picker"></div>
873
+ ```
874
+
875
+ | Option | Type | Default | Description |
876
+ |---|---|---|---|
877
+ | `value` | string | `'#ff0000'` | Initial hex color (e.g. `'#3d63dd'`). Must be a 6-digit hex string. |
878
+ | `onChange` | function | — | Called on every color change: `(hex: string, rgb: { r, g, b }) => void` |
879
+
802
880
  ### Select
803
881
 
804
882
  The Select component wraps a native `<select>` element with custom Basix styling. Supports single and multi-select.
@@ -813,9 +891,11 @@ The Select component wraps a native `<select>` element with custom Basix styling
813
891
  ``` js
814
892
  const sel = new Select('#my-select');
815
893
  sel.value(); // returns selected value string, or string[] for multi-select
894
+ sel.destroy(); // removes the document click listener
816
895
 
817
- // Or initialise by passing the element directly
818
- Select.init(document.querySelector('#my-select'));
896
+ // Static convenience form returns a cleanup function to remove the document listener
897
+ const cleanup = Select.init(document.querySelector('#my-select'));
898
+ cleanup?.(); // call when the select is removed from the DOM
819
899
  ```
820
900
 
821
901
  ### Code Viewer
@@ -823,9 +903,11 @@ Select.init(document.querySelector('#my-select'));
823
903
  The CodeViewer component renders syntax-highlighted code blocks inside any container. Supports JavaScript, HTML, and CSS.
824
904
 
825
905
  ``` js
826
- new CodeViewer('#output', '<div class="card">Hello</div>', 'html');
906
+ const viewer = new CodeViewer('#output', '<div class="card">Hello</div>', 'html');
827
907
  new CodeViewer('#output', 'const x = 42;', 'javascript');
828
908
  new CodeViewer('#output', '.card { padding: 1rem; }', 'css');
909
+
910
+ viewer.destroy(); // removes event listeners and clears the container
829
911
  ```
830
912
 
831
913
  ### Editor
@@ -914,6 +996,9 @@ editor1.destroy();
914
996
  | `clear` | Clears all content |
915
997
  | `clean` | Strips formatting from the current selection |
916
998
  | `toggle-code` | Shows / hides the HTML side panel |
999
+ | `apply-code` | Applies the HTML source panel content to the editable area |
1000
+ | `sanitize-code` | Sanitizes the HTML source, then applies it to the editable area |
1001
+ | `minify-code` | Minifies the HTML source (removes whitespace and newlines) |
917
1002
 
918
1003
  ### Custom Scrollbar
919
1004
 
package/css/checkbox.scss CHANGED
@@ -6,6 +6,13 @@
6
6
  position: absolute;
7
7
  opacity: 0;
8
8
  width: auto;
9
+ appearance: none;
10
+ -webkit-appearance: none;
11
+ border: none;
12
+ box-shadow: none;
13
+ min-height: 0;
14
+ padding: 0;
15
+ background: transparent;
9
16
 
10
17
  & + label {
11
18
  position: relative;
@@ -29,7 +36,7 @@
29
36
  transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease;
30
37
  }
31
38
 
32
- &:hover + label::before {
39
+ &:not(:disabled):hover + label::before {
33
40
  border-color: var(--accent-color);
34
41
  }
35
42
 
@@ -58,11 +65,12 @@
58
65
  &:disabled + label {
59
66
  color: var(--secondary-text);
60
67
  cursor: not-allowed;
68
+ pointer-events: none;
61
69
  }
62
70
 
63
71
  &:disabled + label::before {
64
72
  background-color: var(--secondary-background);
65
- border-color: var(--disabled);
73
+ border-color: color-mix(in srgb, var(--secondary-text) 35%, transparent);
66
74
  box-shadow: none;
67
75
  }
68
76
 
@@ -70,4 +78,9 @@
70
78
  background-color: var(--disabled);
71
79
  border-color: var(--disabled);
72
80
  }
81
+
82
+ &:disabled:checked + label::after {
83
+ border-right-color: color-mix(in srgb, var(--primary-text) 40%, transparent);
84
+ border-bottom-color: color-mix(in srgb, var(--primary-text) 40%, transparent);
85
+ }
73
86
  }
@@ -0,0 +1,278 @@
1
+ @use "properties";
2
+ @use "parameters" as *;
3
+
4
+ $thumb-size: 18px;
5
+ $thumb-size-touch: 22px;
6
+ $track-height: 12px;
7
+ $cursor-size: 14px;
8
+ $preview-size: 40px;
9
+
10
+ .color-picker {
11
+ width: 100%;
12
+ background: var(--background);
13
+ border: 1px solid var(--divider);
14
+ border-radius: $radius-lg;
15
+ padding: $spacing;
16
+ box-sizing: border-box;
17
+ display: flex;
18
+ flex-direction: column;
19
+ gap: calc($spacing * 0.75);
20
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
21
+ }
22
+
23
+ // ─── Saturation / brightness field ────────────────────────────────────────────
24
+
25
+ .color-picker__field {
26
+ position: relative;
27
+ overflow: hidden;
28
+ height: 180px;
29
+ border-radius: $border-radius;
30
+ cursor: crosshair;
31
+ touch-action: none;
32
+ flex-shrink: 0;
33
+
34
+ @media (min-width: 380px) {
35
+ height: 200px;
36
+ }
37
+ }
38
+
39
+ .color-picker__canvas {
40
+ display: block;
41
+ width: 100%;
42
+ height: 100%;
43
+ }
44
+
45
+ .color-picker__cursor {
46
+ position: absolute;
47
+ width: $cursor-size;
48
+ height: $cursor-size;
49
+ border-radius: 50%;
50
+ border: 2px solid #fff;
51
+ box-shadow:
52
+ 0 0 0 1px rgba(0, 0, 0, 0.3),
53
+ 0 1px 4px rgba(0, 0, 0, 0.4);
54
+ transform: translate(-50%, -50%);
55
+ pointer-events: none;
56
+ transition: none;
57
+
58
+ &--dark {
59
+ border-color: rgba(0, 0, 0, 0.6);
60
+ }
61
+ }
62
+
63
+ // ─── Controls row: hue slider + preview ───────────────────────────────────────
64
+
65
+ .color-picker__controls {
66
+ display: flex;
67
+ align-items: center;
68
+ gap: $spacing;
69
+ }
70
+
71
+ // Rainbow strip is the track div itself — input sits absolutely centred over it
72
+ // so the input never contributes to layout height.
73
+ .color-picker__hue-track {
74
+ flex: 1;
75
+ position: relative;
76
+ height: $track-height;
77
+ border-radius: calc($track-height / 2);
78
+ background: linear-gradient(
79
+ to right,
80
+ #f00 0%,
81
+ #ff0 17%,
82
+ #0f0 33%,
83
+ #0ff 50%,
84
+ #00f 67%,
85
+ #f0f 83%,
86
+ #f00 100%
87
+ );
88
+ }
89
+
90
+ .color-picker__hue-slider {
91
+ -webkit-appearance: none;
92
+ appearance: none;
93
+ position: absolute;
94
+ top: 50%;
95
+ left: 0;
96
+ transform: translateY(-50%);
97
+ width: 100%;
98
+ height: $thumb-size;
99
+ background: transparent;
100
+ cursor: pointer;
101
+ margin: 0;
102
+ padding: 0;
103
+ border: none;
104
+ outline: none;
105
+
106
+ &::-webkit-slider-runnable-track {
107
+ height: $thumb-size;
108
+ background: transparent;
109
+ }
110
+
111
+ &::-webkit-slider-thumb {
112
+ -webkit-appearance: none;
113
+ appearance: none;
114
+ width: $thumb-size;
115
+ height: $thumb-size;
116
+ border-radius: 50%;
117
+ background: #fff;
118
+ border: 2px solid #fff;
119
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
120
+ margin-top: 0;
121
+ transition: transform 0.12s ease, box-shadow 0.12s ease;
122
+ }
123
+
124
+ &::-moz-range-track {
125
+ height: $thumb-size;
126
+ background: transparent;
127
+ }
128
+
129
+ // ── Moz thumb ─────────────────────────────────────────────────────────────
130
+ &::-moz-range-thumb {
131
+ width: $thumb-size;
132
+ height: $thumb-size;
133
+ border-radius: 50%;
134
+ background: #fff;
135
+ border: 2px solid #fff;
136
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
137
+ transition: transform 0.12s ease, box-shadow 0.12s ease;
138
+ cursor: pointer;
139
+ }
140
+
141
+ &:focus {
142
+ outline: none;
143
+ }
144
+
145
+ &:focus-visible::-webkit-slider-thumb {
146
+ box-shadow:
147
+ 0 1px 3px rgba(0, 0, 0, 0.35),
148
+ 0 0 0 3px var(--accent-color-tint);
149
+ }
150
+
151
+ &:focus-visible::-moz-range-thumb {
152
+ box-shadow:
153
+ 0 1px 3px rgba(0, 0, 0, 0.35),
154
+ 0 0 0 3px var(--accent-color-tint);
155
+ }
156
+
157
+ &:hover::-webkit-slider-thumb {
158
+ transform: scale(1.15);
159
+ }
160
+
161
+ &:hover::-moz-range-thumb {
162
+ transform: scale(1.15);
163
+ }
164
+
165
+ &:active::-webkit-slider-thumb {
166
+ transform: scale(1.2);
167
+ }
168
+
169
+ &:active::-moz-range-thumb {
170
+ transform: scale(1.2);
171
+ }
172
+ }
173
+
174
+ .color-picker__preview {
175
+ width: $preview-size;
176
+ height: $preview-size;
177
+ border-radius: 50%;
178
+ border: 1px solid var(--divider);
179
+ flex-shrink: 0;
180
+ }
181
+
182
+ // ─── Text inputs ──────────────────────────────────────────────────────────────
183
+
184
+ .color-picker__inputs {
185
+ display: flex;
186
+ gap: calc($spacing * 0.5);
187
+ }
188
+
189
+ .color-picker__input-group {
190
+ display: flex;
191
+ flex-direction: column;
192
+ gap: 4px;
193
+ flex: 1;
194
+
195
+ &--hex {
196
+ flex: 2;
197
+ }
198
+
199
+ label {
200
+ font-size: 0.7rem;
201
+ font-weight: 500;
202
+ color: var(--secondary-text);
203
+ text-align: center;
204
+ line-height: 1;
205
+ user-select: none;
206
+ }
207
+ }
208
+
209
+ .color-picker__hex,
210
+ .color-picker__r,
211
+ .color-picker__g,
212
+ .color-picker__b {
213
+ width: 100%;
214
+ box-sizing: border-box;
215
+ padding: 0.35rem 0.4rem;
216
+ border: 1px solid var(--divider);
217
+ border-radius: $border-radius;
218
+ font-family: monospace;
219
+ font-size: 0.8rem;
220
+ text-align: center;
221
+ background: var(--secondary-background);
222
+ color: var(--primary-text);
223
+ transition: border-color 0.15s, box-shadow 0.15s;
224
+ outline: none;
225
+ -moz-appearance: textfield;
226
+
227
+ &::-webkit-inner-spin-button,
228
+ &::-webkit-outer-spin-button {
229
+ -webkit-appearance: none;
230
+ margin: 0;
231
+ }
232
+
233
+ &:focus {
234
+ border-color: var(--accent-color);
235
+ box-shadow: 0 0 0 2px var(--accent-color-tint);
236
+ }
237
+ }
238
+
239
+ // ─── Mobile ───────────────────────────────────────────────────────────────────
240
+
241
+ @media (max-width: 480px) {
242
+ .color-picker__field {
243
+ height: 160px;
244
+ }
245
+
246
+ .color-picker__hue-slider {
247
+ height: $thumb-size-touch;
248
+
249
+ &::-webkit-slider-runnable-track { height: $thumb-size-touch; }
250
+ &::-webkit-slider-thumb { width: $thumb-size-touch; height: $thumb-size-touch; }
251
+ &::-moz-range-track { height: $thumb-size-touch; }
252
+ &::-moz-range-thumb { width: $thumb-size-touch; height: $thumb-size-touch; }
253
+ }
254
+
255
+ .color-picker__hex,
256
+ .color-picker__r,
257
+ .color-picker__g,
258
+ .color-picker__b {
259
+ font-size: 0.75rem;
260
+ padding: 0.28rem 0.3rem;
261
+ }
262
+ }
263
+
264
+ @media (prefers-reduced-motion: reduce) {
265
+ .color-picker__hue-slider {
266
+ &::-webkit-slider-thumb,
267
+ &::-moz-range-thumb {
268
+ transition: none;
269
+ }
270
+ }
271
+
272
+ .color-picker__hex,
273
+ .color-picker__r,
274
+ .color-picker__g,
275
+ .color-picker__b {
276
+ transition: none;
277
+ }
278
+ }
@@ -104,8 +104,8 @@
104
104
  }
105
105
 
106
106
  .datepicker-day.in-range {
107
- background-color: var(--accent-color-lighten);
108
- color: var(--accent-color-text);
107
+ background-color: color-mix(in srgb, var(--accent-color) 15%, transparent);
108
+ color: var(--primary-text);
109
109
  border-radius: 0;
110
110
  }
111
111
 
@@ -128,7 +128,9 @@
128
128
  color: var(--accent-color);
129
129
  }
130
130
 
131
- .datepicker-day.selected.today {
131
+ .datepicker-day.selected.today,
132
+ .datepicker-day.range-start.today,
133
+ .datepicker-day.range-end.today {
132
134
  color: var(--accent-color-text);
133
135
  }
134
136
 
@@ -128,6 +128,13 @@ nav.push-menu {
128
128
 
129
129
  nav li {
130
130
  margin: 0;
131
+ list-style: none;
132
+ }
133
+
134
+ nav ul {
135
+ list-style: none;
136
+ padding: 0;
137
+ margin: 0;
131
138
  }
132
139
 
133
140
  nav a,
package/css/stepper.scss CHANGED
@@ -118,7 +118,7 @@ $connector-thickness: 2px;
118
118
  border-color: var(--accent-color);
119
119
  background: color-mix(in srgb, var(--accent-color) 10%, var(--background));
120
120
  color: var(--accent-color);
121
- animation: stepper-pulse 2.4s ease-out infinite;
121
+ animation: stepper-pulse 2.4s ease-out 3;
122
122
  }
123
123
 
124
124
  .stepper-title {