@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 +21 -0
- package/README.md +126 -0
- package/dist/index.d.mts +83 -0
- package/dist/index.d.ts +83 -0
- package/dist/index.js +437 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +430 -0
- package/dist/index.mjs.map +1 -0
- package/docs/mcp.md +36 -0
- package/package.json +43 -0
- package/styles/core.css +3 -0
- package/themes/bootstrap.css +2 -0
- package/themes/tailwind.css +2 -0
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
|
package/dist/index.d.mts
ADDED
|
@@ -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 };
|
package/dist/index.d.ts
ADDED
|
@@ -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 };
|