Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ New Multiple-Select Options:
- `autoAdjustDropHeight` will automatically adjust the drop (up or down) height with available space
- `autoAdjustDropPosition` will automatically choose best position (top/bottom) with available space
- `autoAdjustDropWidthByTextSize` automatically set the drop width size from the widest list option width
- `useSelectOptionLabel` will use the `<option label="">` (from select option value) that can be used to display shorter selected option values.
- example: value "1,3" instead of "January,March"
- `useSelectOptionLabelToHtml` similar to `useSelectOptionLabel` but also renders HTML.

## Contributions

Expand Down
4 changes: 2 additions & 2 deletions demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
},
"devDependencies": {
"multiple-select-vanilla": "workspace:*",
"sass": "^1.58.0",
"sass": "^1.58.3",
"typescript": "^4.9.5",
"vite": "^4.1.1"
"vite": "^4.1.2"
}
}
2 changes: 2 additions & 0 deletions demo/src/app-routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import Options27 from './options/options27';
import Options28 from './options/options28';
import Options29 from './options/options29';
import Options30 from './options/options30';
import Options31 from './options/options31';
import Methods01 from './methods/methods01';
import Methods02 from './methods/methods02';
import Methods03 from './methods/methods03';
Expand Down Expand Up @@ -109,6 +110,7 @@ export const exampleRouting = [
{ name: 'options28', view: '/src/options/options28.html', viewModel: Options28, title: 'Label Template' },
{ name: 'options29', view: '/src/options/options29.html', viewModel: Options29, title: 'Auto-Adjust Drop Position' },
{ name: 'options30', view: '/src/options/options30.html', viewModel: Options30, title: 'Auto-Adjust Drop Height/Width' },
{ name: 'options31', view: '/src/options/options31.html', viewModel: Options31, title: 'Use Select Option as Label' },
],
},
{
Expand Down
4 changes: 2 additions & 2 deletions demo/src/options/options30.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ <h2 class="bd-title">
<label class="col-sm-3 text-end">Adjust drop width by option list content</label>

<div class="col-sm-9">
<select id="select1" multiple="multiple" data-width="75" class="my-container">
<select id="select1" multiple="multiple" data-width="75">
<option value="1">January</option>
<option value="2">February</option>
<option value="3">March</option>
Expand All @@ -51,7 +51,7 @@ <h2 class="bd-title">
<label class="col-sm-3 text-end">Resize drop height (width: 200px)</label>

<div class="col-sm-9">
<select id="select2" multiple="multiple" data-width="200" class="my-container">
<select id="select2" multiple="multiple" data-width="200">
<option value="1">January</option>
<option value="2">February</option>
<option value="3">March</option>
Expand Down
7 changes: 5 additions & 2 deletions demo/src/options/options30.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { multipleSelect, MultipleSelectInstance } from 'multiple-select-vanilla'
export default class Example {
ms1?: MultipleSelectInstance;
ms2?: MultipleSelectInstance;
ms3?: MultipleSelectInstance;

mount() {
this.ms1 = multipleSelect('#select1', {
Expand All @@ -12,14 +13,14 @@ export default class Example {
showOkButton: true,
}) as MultipleSelectInstance;

this.ms1 = multipleSelect('#select2', {
this.ms2 = multipleSelect('#select2', {
autoAdjustDropWidthByTextSize: true,
autoAdjustDropHeight: true,
position: 'top',
showOkButton: true,
}) as MultipleSelectInstance;

this.ms2 = multipleSelect('#select3', {
this.ms3 = multipleSelect('#select3', {
autoAdjustDropWidthByTextSize: true,
autoAdjustDropHeight: true,
filter: true,
Expand All @@ -31,7 +32,9 @@ export default class Example {
// destroy ms instance(s) to avoid DOM leaks
this.ms1?.destroy();
this.ms2?.destroy();
this.ms3?.destroy();
this.ms1 = undefined;
this.ms2 = undefined;
this.ms3 = undefined;
}
}
59 changes: 59 additions & 0 deletions demo/src/options/options31.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<div class="row mb-2">
<div class="col-md-12 title-desc">
<h2 class="bd-title">
Use Select Option as Label
<span class="float-end links">
Code <span class="fa fa-link"></span>
<span class="small">
<a
target="_blank"
href="https://github.com/ghiscoding/multiple-select-vanilla/blob/main/demo/src/options/options08.html"
>html</a
>
|
<a target="_blank" href="https://github.com/ghiscoding/multiple-select-vanilla/blob/main/demo/src/options/options08.ts"
>ts</a
>
</span>
</span>
</h2>
<div class="demo-subtitle">
Use <code>useSelectOptionLabel</code> to display select option value as
<code>&lt;option label=""&gt;&lt;/option&gt;</code> to display shorter text as the selected values in the parent select.
<br />
Use <code>useSelectOptionLabelToHtml</code> which is the same as "useSelectOptionLabel" but will also render html option
values.
</div>
</div>
</div>

<div>
<div class="mb-3 row">
<label class="col-sm-3 text-end">Use Select Option Label</label>

<div class="col-sm-9">
<select id="select1" multiple="multiple" data-width="150">
<option value="1">January</option>
<option value="2">February</option>
<option value="3" selected>March</option>
<option value="4">April</option>
<option value="5">May</option>
<option value="6" selected>June</option>
<option value="7">July</option>
<option value="8">August</option>
<option value="9">September</option>
<option value="10">October</option>
<option value="11">November</option>
<option value="12">December</option>
</select>
</div>
</div>

<div class="mb-3 row">
<label class="col-sm-3 text-end">Use Select Option Label & Render HTML</label>

<div class="col-sm-9">
<select id="select2" multiple="multiple" data-width="150"></select>
</div>
</div>
</div>
79 changes: 79 additions & 0 deletions demo/src/options/options31.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { multipleSelect, MultipleSelectInstance } from 'multiple-select-vanilla';

export default class Example {
ms1?: MultipleSelectInstance;
ms2?: MultipleSelectInstance;

mount() {
this.ms1 = multipleSelect('#select1', {
useSelectOptionLabel: true,
}) as MultipleSelectInstance;

this.ms2 = multipleSelect('#select2', {
useSelectOptionLabelToHtml: true,
data: [
{
text: '<i class="fa fa-star"></i> January',
value: '<i class="fa fa-star"></i>1',
selected: true,
},
{
text: 'February',
value: '2',
},
{
text: 'March',
value: 3,
},
{
text: 'April',
value: 4,
},
{
text: 'May',
value: 5,
},
{
text: 'June',
value: 6,
},
{
text: 'July',
value: 7,
},
{
text: 'August',
value: 8,
},
{
text: 'September',
value: 9,
},
{
text: 'October',
value: 10,
},
{
text: 'November',
value: 11,
},
{
text: 'December',
value: 12,
},
],
}) as MultipleSelectInstance;

// setTimeout(() => {
// this.ms2?.setSelects(['<i class="fa fa-star"></i>1', 2, 3]);
// }, 2000);
}

unmount() {
// destroy ms instance(s) to avoid DOM leaks
this.ms1?.destroy();
this.ms2?.destroy();
this.ms1 = undefined;
this.ms2 = undefined;
}
}
3 changes: 3 additions & 0 deletions lib/build-watch.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const env = process.env.NODE_ENV;
runCompilation(process.env.LERNA_FILE_CHANGES.split(','));

function runBuild() {
const startTime = new Date().getTime();
buildSync({
color: true,
entryPoints: ['./src/index.ts'],
Expand All @@ -23,6 +24,8 @@ function runBuild() {
// outfile: env === 'production' ? './dist/multiple-select.min.js' : './dist/multiple-select.js',
outfile: 'dist/esm/multiple-select.js',
});
const endTime = new Date().getTime();
console.info(`⚡️ Built in ${endTime - startTime}ms`);
}

async function runCompilation(changedFiles) {
Expand Down
2 changes: 1 addition & 1 deletion lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"npm-run-all2": "^6.0.4",
"postcss": "^8.4.21",
"postcss-cli": "^10.1.0",
"sass": "^1.58.1",
"sass": "^1.58.3",
"typescript": "^4.9.5"
}
}
79 changes: 52 additions & 27 deletions lib/src/MultipleSelectInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @author zhixin wen <wenzhixin2010@gmail.com>
*/
import Constants from './constants';
import { compareObjects, deepCopy, findByParam, removeDiacritics, removeUndefined, setDataKeys } from './utils';
import { compareObjects, deepCopy, findByParam, removeDiacritics, removeUndefined, setDataKeys, stripScripts } from './utils';
import {
calculateAvailableSpace,
createDomElement,
Expand Down Expand Up @@ -53,7 +53,7 @@ export class MultipleSelectInstance {
protected elm: HTMLSelectElement,
options?: Partial<Omit<MultipleSelectOption, 'onHardDestroy' | 'onAfterHardDestroy'>>
) {
this.options = Object.assign({}, Constants.DEFAULTS, this.elm.dataset, options);
this.options = Object.assign({}, Constants.DEFAULTS, this.elm.dataset, options) as MultipleSelectOption;
this._bindEventService = new BindingEventService({ distinctEvent: true });
}

Expand Down Expand Up @@ -534,7 +534,7 @@ export class MultipleSelectInstance {
<li class="${multiple} ${classes}" ${title} ${style}>
<label class="${row.disabled ? 'disabled' : ''}">
<input type="${type}"
value="${row.value}"
value="${encodeURI(row.value)}"
data-key="${row._key}"
${this.selectItemName}
${row.selected ? ' checked="checked"' : ''}
Expand Down Expand Up @@ -879,32 +879,51 @@ export class MultipleSelectInstance {
textSelects = valueSelects;
}

const spanElm = this.choiceElm?.querySelector('span') as HTMLSpanElement;
const spanElm = this.choiceElm?.querySelector<HTMLSpanElement>('span');
const sl = valueSelects.length;
let html = '';

if (sl === 0) {
spanElm.classList.add('ms-placeholder');
spanElm.innerHTML = this.options.placeholder || '';
} else if (sl < this.options.minimumCountSelected) {
html = textSelects.join(this.options.displayDelimiter);
} else if (this.options.formatAllSelected() && sl === this.dataTotal) {
html = this.options.formatAllSelected();
} else if (this.options.ellipsis && sl > this.options.minimumCountSelected) {
html = `${textSelects.slice(0, this.options.minimumCountSelected).join(this.options.displayDelimiter)}...`;
} else if (this.options.formatCountSelected(sl, this.dataTotal) && sl > this.options.minimumCountSelected) {
html = this.options.formatCountSelected(sl, this.dataTotal);
} else {
html = textSelects.join(this.options.displayDelimiter);
}
const getSelectOptionHtml = () => {
if (this.options.useSelectOptionLabel || this.options.useSelectOptionLabelToHtml) {
const labels = valueSelects.join(this.options.delimiter);
return this.options.useSelectOptionLabelToHtml ? stripScripts(labels) : labels;
} else {
return textSelects.join(this.options.displayDelimiter);
}
};

if (html) {
spanElm?.classList.remove('ms-placeholder');
spanElm.innerHTML = html;
}
if (spanElm) {
if (sl === 0) {
spanElm.classList.add('ms-placeholder');
spanElm.innerHTML = this.options.placeholder || '';
} else if (sl < this.options.minimumCountSelected) {
html = getSelectOptionHtml();
} else if (this.options.formatAllSelected() && sl === this.dataTotal) {
html = this.options.formatAllSelected();
} else if (this.options.ellipsis && sl > this.options.minimumCountSelected) {
html = `${textSelects.slice(0, this.options.minimumCountSelected).join(this.options.displayDelimiter)}...`;
} else if (this.options.formatCountSelected(sl, this.dataTotal) && sl > this.options.minimumCountSelected) {
html = this.options.formatCountSelected(sl, this.dataTotal);
} else {
html = getSelectOptionHtml();
}

if (this.options.displayTitle) {
spanElm.title = this.getSelects('text').join('');
if (html) {
spanElm?.classList.remove('ms-placeholder');
if (this.options.useSelectOptionLabelToHtml) {
spanElm.innerHTML = html;
} else {
spanElm.textContent = html;
}
}

if (this.options.displayTitle || this.options.addTitle) {
if (this.options.addTitle) {
console.warn('[Multiple-Select-Vanilla] Please note that the `addTitle` option was replaced with `displayTitle`.');
}
const selectType = this.options.useSelectOptionLabel || this.options.useSelectOptionLabelToHtml ? 'value' : 'text';
spanElm.title = this.getSelects(selectType).join('');
}
}

// set selects to select
Expand Down Expand Up @@ -1303,10 +1322,16 @@ export class MultipleSelectInstance {
}

// calculate the "Select All" element width, this text is configurable which is why we recalculate every time
const selectAllElm = this.dropElm.querySelector('.ms-select-all span') as HTMLSpanElement;
const selectAllSpanElm = this.dropElm.querySelector('.ms-select-all span') as HTMLSpanElement;
const dropUlElm = this.dropElm.querySelector('ul') as HTMLUListElement;

const selectAllElmWidth = selectAllElm.clientWidth + this.options.selectSidePadding;
let liPadding = 0;
const firstLiElm = this.dropElm.querySelector('li'); // get padding of 1st <li> element
if (firstLiElm) {
const { paddingLeft, paddingRight } = window.getComputedStyle(firstLiElm);
liPadding = parseFloat(paddingLeft) + parseFloat(paddingRight);
}
const selectAllElmWidth = selectAllSpanElm.clientWidth + liPadding;
const hasScrollbar = dropUlElm.scrollHeight > dropUlElm.clientHeight;
const scrollbarWidth = hasScrollbar ? this.getScrollbarWidth() : 0;
let contentWidth = 0;
Expand All @@ -1318,7 +1343,7 @@ export class MultipleSelectInstance {
});

// add a padding & include the browser scrollbar width
contentWidth += this.options.selectSidePadding + scrollbarWidth;
contentWidth += liPadding + scrollbarWidth;

// make sure the new calculated width is wide enough to include the "Select All" text (this text is configurable)
if (contentWidth < selectAllElmWidth) {
Expand Down
Loading