diff --git a/docs-src/DocsSidebar.vue b/docs-src/DocsSidebar.vue index 005eb4b8..f64b719f 100644 --- a/docs-src/DocsSidebar.vue +++ b/docs-src/DocsSidebar.vue @@ -75,7 +75,7 @@ import DocsBrand from "./DocsBrand.vue"; import UiIcon from "@/UiIcon.vue"; import UiSelect from "@/UiSelect.vue"; -import version from "../build/version.mjs"; +import selectedVersion from "../build/version.mjs"; const versions = [ { label: "0.8.x", value: "0.8.9" }, @@ -87,8 +87,6 @@ const versions = [ { label: "next", value: "next" }, ]; -const selectedVersion = versions.find((v) => v.value === version) ?? versions[versions.length - 2]; - export default { components: { DocsBrand, diff --git a/docs-src/pages/UiDatepicker.vue b/docs-src/pages/UiDatepicker.vue index e6cac6b6..be58d62e 100644 --- a/docs-src/pages/UiDatepicker.vue +++ b/docs-src/pages/UiDatepicker.vue @@ -208,6 +208,14 @@ disabled placeholder="Select a date" >A Special Day + +

With a clear button

+ + A Special Day

API

@@ -452,6 +460,16 @@

Whether or not the datepicker is disabled. Set to true to disable the datepicker.

+ + + hasClearButton + Boolean + false + +

Whether or not the datepicker should include a value clear button.

+

Set to true to show a clear button. Clicking it will set the value to an empty one depending on the datepicker type.

+ + @@ -651,7 +669,8 @@ export default { picker13: null, picker1301: null, picker14: null, - picker15: new Date() + picker15: new Date(), + picker16: new Date() }; }, diff --git a/docs-src/pages/UiSelect.vue b/docs-src/pages/UiSelect.vue index c24d605b..4b81e98e 100644 --- a/docs-src/pages/UiSelect.vue +++ b/docs-src/pages/UiSelect.vue @@ -197,6 +197,16 @@ placeholder="Select a colour" :options="colourStrings" > + +

With a clear button

+ +

API

@@ -226,7 +236,7 @@ modelValue, v-model * - String, Number, Object or Array + String, Number, Boolean, or Array

The model the selected value or values sync to. Can be set initially for default value/values.

@@ -236,14 +246,16 @@ options - Array + Array, Object [] -

An array of options to show to the user.

+

An array or object of options to show to the user.

Can be a plain array, e.g. ['Red', 'Blue', 'Green'] as well as an array of objects.

For a plain array, the option is shown to the user and it is used for filtering.

-

For an array of objects, the label is shown to the user and is used for filtering, and the modelValue is submitted to the server. If provided, class, will be applied to the option element's class attribute. You can redefine these keys to fit your data using the keys prop.

+

For an array of objects, the label is shown to the user and is used for filtering, and the value is submitted to the server. If provided, class, will be applied to the option element's class attribute. You can redefine these keys to fit your data using the keys prop.

+ +

For an object, the value is shown to the user and is used for filtering, and the key is submitted to the server. The keys prop is ignored when using an Object.

The entire option is written to the model when the user makes a selection.

@@ -470,6 +482,16 @@

Whether or not the select is disabled. Set to true to disable the select.

+ + + hasClearButton + Boolean + false + +

Whether or not the select should include a value clear button.

+

Set to true to show a clear button. Clicking it will set the value to null.

+ + @@ -704,25 +726,10 @@ export default { select2o5: '', select3: 'Orange', select4: '', - select5: { - label: 'Lavender', - image: 'https://via.placeholder.com/64/e6e6fa/e6e6fa', - value: 'lavender' - }, + select5: 'lavender', select6: '', select7: '', - select8: [ - { - label: 'Orange', - image: 'https://via.placeholder.com/64/ffa500/ffa500', - value: 'orange' - }, - { - label: 'Lime', - image: 'https://via.placeholder.com/64/00ff00/00ff00', - value: 'lime' - } - ], + select8: ['orange', 'lime'], select9: [], select10: [], select10Touched: false, @@ -731,13 +738,11 @@ export default { select11Loading: false, select11NoResults: false, select11LoadingTimeout: null, - select12: { - name: 'Australia', - code: 'AU' - }, + select12: 'AU', select12o5: '', select13: '', select14: 'Peach', + select15: 'Peach', colours, colourStrings, countries diff --git a/src/UiDatepicker.vue b/src/UiDatepicker.vue index fee47ff6..827662c3 100644 --- a/src/UiDatepicker.vue +++ b/src/UiDatepicker.vue @@ -23,6 +23,19 @@ {{ label }} + + + + + +
{{ @@ -174,6 +187,10 @@ export default { type: Boolean, default: false, }, + hasClearButton: { + type: Boolean, + default: false, + }, help: String, error: String, disabled: { @@ -431,7 +448,7 @@ export default { padding-top: $ui-input-icon-margin-top--with-label; } - .ui-datepicker__dropdown-button { + .ui-datepicker__clear-button { top: $ui-input-button-margin-top--with-label; } } @@ -488,6 +505,19 @@ export default { width: 100%; } +.ui-datepicker__clear-button { + color: $ui-input-button-color; + cursor: pointer; + font-size: $ui-input-button-size; + position: absolute; + right: $ui-input-button-margin-top * 2; + top: $ui-input-button-margin-top; + + &:hover { + color: $ui-input-button-color--hover; + } +} + .ui-datepicker__icon-wrapper { flex-shrink: 0; margin-right: $ui-input-icon-margin-right; diff --git a/src/UiSelect.vue b/src/UiSelect.vue index 8c063083..a3cc0136 100644 --- a/src/UiSelect.vue +++ b/src/UiSelect.vue @@ -28,6 +28,21 @@ {{ label }}
+ + + + + +
{{ @@ -140,7 +155,6 @@ import UiProgressCircular from "./UiProgressCircular.vue"; import UiSelectOption from "./UiSelectOption.vue"; import RespondsToExternalClick from "./mixins/RespondsToExternalClick"; -import { looseIndexOf, looseEqual } from "./helpers/util"; import { scrollIntoView, resetScroll } from "./helpers/element-scroll"; import fuzzysearch from "fuzzysearch"; @@ -161,11 +175,10 @@ export default { name: String, tabindex: [String, Number], modelValue: { - type: [String, Number, Object, Array], - required: true, + type: [String, Number, Boolean, Array], }, options: { - type: Array, + type: [Array, Object], default() { return []; }, @@ -185,6 +198,10 @@ export default { type: String, default: "basic", // 'basic' or 'image' }, + hasClearButton: { + type: Boolean, + default: false, + }, multiple: { type: Boolean, default: false, @@ -306,12 +323,29 @@ export default { return Boolean(this.help) || Boolean(this.$slots.help); }, + preparedOptions() { + if (Array.isArray(this.options)) { + return this.options.map((option) => + typeof option !== "object" + ? { [this.keys.label]: option, [this.keys.value]: option } + : option + ); + } + + return Object.keys(this.options).map((value) => { + return { + label: this.options[value], + value, + }; + }); + }, + filteredOptions() { if (this.disableFilter) { - return this.options; + return this.preparedOptions; } - const options = this.options.filter((option) => { + const options = this.preparedOptions.filter((option) => { if (this.filter) { return this.filter(option, this.query, this.defaultFilter); } @@ -327,17 +361,22 @@ export default { }, displayText() { + const getLabel = (value) => { + const selectedOption = value + ? this.preparedOptions.find((o) => o[this.keys.value] === value) + : null; + return selectedOption ? selectedOption[this.keys.label] : ""; + }; + if (this.multiple) { if (this.modelValue.length > 0) { - return this.modelValue - .map((value) => value[this.keys.label] || value) - .join(this.multipleDelimiter); + return this.modelValue.map((value) => getLabel(value)).join(this.multipleDelimiter); } return ""; } - return this.modelValue ? this.modelValue[this.keys.label] || this.modelValue : ""; + return getLabel(this.modelValue); }, hasDisplayText() { @@ -360,14 +399,14 @@ export default { } if (Array.isArray(this.modelValue)) { - return this.modelValue.map((option) => option[this.keys.value] || option).join(","); + return this.modelValue.join(","); } - return this.modelValue[this.keys.value] || this.modelValue; + return this.modelValue; }, emptyValue() { - return this.multiple ? [] : ""; + return this.multiple ? [] : null; }, }, @@ -392,8 +431,7 @@ export default { created() { const invalidMultipleValue = this.multiple && !Array.isArray(this.modelValue); - const invalidEmptyValue = !this.modelValue && this.modelValue !== ""; - if (invalidMultipleValue || invalidEmptyValue) { + if (invalidMultipleValue) { this.$emit("update:modelValue", this.emptyValue); this.initialValue = JSON.stringify(this.emptyValue); } @@ -439,7 +477,7 @@ export default { if (this.multiple) { this.updateOption(option, { select: shouldSelect }); } else { - this.setValue(option); + this.setValue(option[this.keys.value]); this.selectedIndex = index; } @@ -460,19 +498,19 @@ export default { isOptionSelected(option) { if (this.multiple) { - return looseIndexOf(this.modelValue, option) > -1; + return this.modelValue.indexOf(option[this.keys.value]) > -1; } - return looseEqual(this.modelValue, option); + return this.modelValue === option[this.keys.value]; }, updateOption(option, options = { select: true }) { let value = []; let updated = false; - const i = looseIndexOf(this.modelValue, option); + const i = this.modelValue.indexOf(option[this.keys.value]); if (options.select && i < 0) { - value = this.modelValue.concat(option); + value = this.modelValue.concat(option[this.keys.value]); updated = true; } @@ -660,12 +698,14 @@ export default { } } + $ui-icon-size: 1em !default; + &.has-label { .ui-select__icon-wrapper { padding-top: $ui-input-icon-margin-top--with-label; } - .ui-select__dropdown-button { + .ui-select__clear-button { top: $ui-input-button-margin-top--with-label; } } @@ -732,6 +772,19 @@ export default { width: 100%; } +.ui-select__clear-button { + color: $ui-input-button-color; + cursor: pointer; + font-size: $ui-input-button-size; + position: absolute; + right: $ui-input-button-margin-top * 2; + top: $ui-input-button-margin-top; + + &:hover { + color: $ui-input-button-color--hover; + } +} + .ui-select__icon-wrapper { flex-shrink: 0; margin-right: $ui-input-icon-margin-right; diff --git a/src/styles/variables.scss b/src/styles/variables.scss index 10f96eb2..88ddb73f 100644 --- a/src/styles/variables.scss +++ b/src/styles/variables.scss @@ -73,7 +73,7 @@ $ui-input-button-color--hover : $primary-text-color !default; $ui-input-button-opacity--disabled : 0.6 !default; $ui-input-button-size : rem(18px) !default; $ui-input-button-margin-top : rem(7px) !default; -$ui-input-button-margin-top--with-label : rem(27px) !default; +$ui-input-button-margin-top--with-label : rem(25px) !default; // Input feedback, help and error text $ui-input-feedback-font-size : rem(14px) !default;