@elrayes/dual-listbox 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ahmed Elrayes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # @elrayes/dual-listbox
2
+
3
+ A zero-dependency (vanilla JS) dual-list box with grouping, search, select-all, and theming (Bootstrap 5.2 default, Tailwind optional) with TypeScript types.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @elrayes/dual-listbox
9
+ ```
10
+
11
+
12
+ ## Usage (ES Modules)
13
+
14
+ ```ts
15
+ import { DualListBox, useTheme, tailwindTheme} from '@elrayes/dual-listbox';
16
+ import { tailwindTheme } from '@elrayes/dual-listbox';
17
+ useTheme(tailwindTheme); // sets global default for all new instances
18
+ DualListBox.setTheme(tailwindTheme); // alias for useTheme()
19
+ import '@elrayes/dual-listbox/styles/core.css';
20
+ import '@elrayes/dual-listbox/themes/bootstrap.css'; // or tailwind.css
21
+
22
+ const items = [
23
+ { item: 'Apple', value: 1, group: 'Fruits' },
24
+ { item: 'Tomato', value: 2, group: 'Vegetables' },
25
+ ];
26
+
27
+ const dlb = new DualListBox('#dual-listbox-container', {
28
+ dataArray: items,
29
+ selectedItems: [],
30
+ onSubmit: (selected, unselected, all, selectedArray) => {
31
+ console.log(selectedArray);
32
+ },
33
+ });
34
+
35
+ // New methods (no duplicates)
36
+ const selectedItems = dlb.getSelectedItems();
37
+ const unselectedItems = dlb.getUnselectedItems();
38
+ const allItems = dlb.getAllItems();
39
+ ```
40
+
41
+ ## Usage (CommonJS)
42
+
43
+ ```js
44
+ const { DualListBox } = require('@elrayes/dual-listbox');
45
+ require('@elrayes/dual-listbox/styles/core.css');
46
+ require('@elrayes/dual-listbox/themes/bootstrap.css');
47
+
48
+ const dlb = new DualListBox('#dual-listbox-container', { /* options */ });
49
+ ```
50
+
51
+ ## Options (TypeScript)
52
+
53
+ ```ts
54
+ export interface DualListBoxOptions {
55
+ itemName?: string; // default "item"
56
+ groupName?: string; // default "group"
57
+ valueName?: string; // default "value"
58
+ inputName?: string; // form input name for hidden fields
59
+ tabNameText?: string;
60
+ rightTabNameText?: string;
61
+ searchPlaceholderText?: string;
62
+ includeButtonText?: string;
63
+ excludeButtonText?: string;
64
+ dataArray?: any[];
65
+ selectedItems?: any[];
66
+ hideEmptyGroups?: boolean;
67
+ submitForm?: boolean;
68
+ onSubmit?: (selected, unselected, allItems, selectedArray) => void | null;
69
+ theme?: Partial<DualListBoxTheme>;
70
+ }
71
+ ```
72
+
73
+ ## Theming
74
+
75
+ - Built-in theme maps classes to your CSS framework.
76
+ - Presets: Bootstrap 5.2 (defaultTheme) and Tailwind (tailwindTheme).
77
+ - You can set a global default for all new instances using `useTheme`:
78
+
79
+ ```ts
80
+ import { DualListBox, useTheme } from '@elrayes/dual-listbox';
81
+ import { tailwindTheme } from '@elrayes/dual-listbox/dist/themePresets';
82
+
83
+ useTheme(tailwindTheme); // applies to all new DualListBox instances
84
+
85
+ new DualListBox('#el'); // uses the global Tailwind theme
86
+ ```
87
+
88
+ - Or provide a custom theme per instance (an instance option has priority over global):
89
+
90
+ ```ts
91
+ new DualListBox('#el', {
92
+ theme: {
93
+ // override any class strings
94
+ btn: 'my-btn my-btn--primary',
95
+ }
96
+ });
97
+ ```
98
+
99
+ Note: For backward compatibility, `DualListBox.setTheme(tailwindTheme)` is still available and is an alias to `useTheme(tailwindTheme)`.
100
+
101
+ Alternatively, rely on CSS variables and write a stylesheet that targets `.dual-listbox`.
102
+
103
+ ## Laravel + Vite Integration
104
+
105
+ 1. Place the container in your Blade:
106
+
107
+ ```html
108
+ <div id="dual-listbox-container"></div>
109
+ ```
110
+
111
+ 2. Import and initialize in your app JS that Vite builds.
112
+
113
+ 3. Include core.css and choose a theme stylesheet.
114
+
115
+ ## API
116
+
117
+ - `new DualListBox(element, options)`
118
+ - `getSelectedValues(): Promise<(string|number)[]>` (legacy values API)
119
+ - Getters: `selected`, `unselected`, `allItems`, `selectedArray`
120
+ - New methods (return item objects without duplicates):
121
+ - `getSelectedItems()`
122
+ - `getUnselectedItems()`
123
+ - `getAllItems()`
124
+
125
+ ## License
126
+ MIT
@@ -0,0 +1,83 @@
1
+ type DualListBoxItem = Record<string, any> & {
2
+ [key: string]: any;
3
+ };
4
+ type OnSubmitHandler = (selected: Record<string, DualListBoxItem[]>, unselected: Record<string, DualListBoxItem[]>, allItems: DualListBoxItem[], selectedArray: (string | number)[]) => void;
5
+ interface DualListBoxTheme {
6
+ container: string;
7
+ row: string;
8
+ colLeft: string;
9
+ colCenter: string;
10
+ colRight: string;
11
+ card: string;
12
+ cardHeader: string;
13
+ cardBody: string;
14
+ cardFooter: string;
15
+ searchInput: string;
16
+ listGroup: string;
17
+ listItem: string;
18
+ formCheck: string;
19
+ formCheckInput: string;
20
+ formCheckLabel: string;
21
+ btn: string;
22
+ btnInclude: string;
23
+ btnExclude: string;
24
+ }
25
+ interface DualListBoxOptions {
26
+ itemName?: string;
27
+ groupName?: string;
28
+ valueName?: string;
29
+ inputName?: string;
30
+ tabNameText?: string;
31
+ rightTabNameText?: string;
32
+ searchPlaceholderText?: string;
33
+ includeButtonText?: string;
34
+ excludeButtonText?: string;
35
+ dataArray?: DualListBoxItem[];
36
+ selectedItems?: DualListBoxItem[];
37
+ hideEmptyGroups?: boolean;
38
+ submitForm?: boolean;
39
+ onSubmit?: OnSubmitHandler | null;
40
+ theme?: Partial<DualListBoxTheme>;
41
+ }
42
+
43
+ /**
44
+ * Set the global default theme for all new DualListBox instances.
45
+ * Instance option `theme` still has highest priority.
46
+ */
47
+ declare function useTheme(theme: Partial<DualListBoxTheme> | DualListBoxTheme): void;
48
+ declare class DualListBox {
49
+ private rootEl;
50
+ private formEl;
51
+ private instanceId;
52
+ private defaults;
53
+ private settings;
54
+ private groups;
55
+ private selectedGroups;
56
+ constructor(element: Element | string, options?: DualListBoxOptions);
57
+ private generateInstanceId;
58
+ private buildGroups;
59
+ private removeDuplicatesFromLeft;
60
+ private render;
61
+ private generateGroupedListHTML;
62
+ private updateSelectAllInfo;
63
+ private bindEvents;
64
+ private moveItems;
65
+ private toggleSelectAll;
66
+ private searchItems;
67
+ private appendSelectedGroupsOnSubmit;
68
+ getSelectedValues(): Promise<(string | number)[]>;
69
+ get selected(): Record<string, DualListBoxItem[]>;
70
+ get selectedArray(): (string | number)[];
71
+ get unselected(): Record<string, DualListBoxItem[]>;
72
+ get allItems(): DualListBoxItem[];
73
+ getSelectedItems(): DualListBoxItem[];
74
+ getUnselectedItems(): DualListBoxItem[];
75
+ getAllItems(): DualListBoxItem[];
76
+ }
77
+ declare function initDualListBox(selector: string | Element, options?: DualListBoxOptions): DualListBox;
78
+
79
+ declare const defaultTheme: DualListBoxTheme;
80
+ declare const bootstrapTheme: DualListBoxTheme;
81
+ declare const tailwindTheme: DualListBoxTheme;
82
+
83
+ export { DualListBox, type DualListBoxItem, type DualListBoxOptions, type DualListBoxTheme, bootstrapTheme, defaultTheme, initDualListBox, tailwindTheme, useTheme };
@@ -0,0 +1,83 @@
1
+ type DualListBoxItem = Record<string, any> & {
2
+ [key: string]: any;
3
+ };
4
+ type OnSubmitHandler = (selected: Record<string, DualListBoxItem[]>, unselected: Record<string, DualListBoxItem[]>, allItems: DualListBoxItem[], selectedArray: (string | number)[]) => void;
5
+ interface DualListBoxTheme {
6
+ container: string;
7
+ row: string;
8
+ colLeft: string;
9
+ colCenter: string;
10
+ colRight: string;
11
+ card: string;
12
+ cardHeader: string;
13
+ cardBody: string;
14
+ cardFooter: string;
15
+ searchInput: string;
16
+ listGroup: string;
17
+ listItem: string;
18
+ formCheck: string;
19
+ formCheckInput: string;
20
+ formCheckLabel: string;
21
+ btn: string;
22
+ btnInclude: string;
23
+ btnExclude: string;
24
+ }
25
+ interface DualListBoxOptions {
26
+ itemName?: string;
27
+ groupName?: string;
28
+ valueName?: string;
29
+ inputName?: string;
30
+ tabNameText?: string;
31
+ rightTabNameText?: string;
32
+ searchPlaceholderText?: string;
33
+ includeButtonText?: string;
34
+ excludeButtonText?: string;
35
+ dataArray?: DualListBoxItem[];
36
+ selectedItems?: DualListBoxItem[];
37
+ hideEmptyGroups?: boolean;
38
+ submitForm?: boolean;
39
+ onSubmit?: OnSubmitHandler | null;
40
+ theme?: Partial<DualListBoxTheme>;
41
+ }
42
+
43
+ /**
44
+ * Set the global default theme for all new DualListBox instances.
45
+ * Instance option `theme` still has highest priority.
46
+ */
47
+ declare function useTheme(theme: Partial<DualListBoxTheme> | DualListBoxTheme): void;
48
+ declare class DualListBox {
49
+ private rootEl;
50
+ private formEl;
51
+ private instanceId;
52
+ private defaults;
53
+ private settings;
54
+ private groups;
55
+ private selectedGroups;
56
+ constructor(element: Element | string, options?: DualListBoxOptions);
57
+ private generateInstanceId;
58
+ private buildGroups;
59
+ private removeDuplicatesFromLeft;
60
+ private render;
61
+ private generateGroupedListHTML;
62
+ private updateSelectAllInfo;
63
+ private bindEvents;
64
+ private moveItems;
65
+ private toggleSelectAll;
66
+ private searchItems;
67
+ private appendSelectedGroupsOnSubmit;
68
+ getSelectedValues(): Promise<(string | number)[]>;
69
+ get selected(): Record<string, DualListBoxItem[]>;
70
+ get selectedArray(): (string | number)[];
71
+ get unselected(): Record<string, DualListBoxItem[]>;
72
+ get allItems(): DualListBoxItem[];
73
+ getSelectedItems(): DualListBoxItem[];
74
+ getUnselectedItems(): DualListBoxItem[];
75
+ getAllItems(): DualListBoxItem[];
76
+ }
77
+ declare function initDualListBox(selector: string | Element, options?: DualListBoxOptions): DualListBox;
78
+
79
+ declare const defaultTheme: DualListBoxTheme;
80
+ declare const bootstrapTheme: DualListBoxTheme;
81
+ declare const tailwindTheme: DualListBoxTheme;
82
+
83
+ export { DualListBox, type DualListBoxItem, type DualListBoxOptions, type DualListBoxTheme, bootstrapTheme, defaultTheme, initDualListBox, tailwindTheme, useTheme };